/* GENERATED by scripts/build_kc_css.py — do not edit.
   Edit the partials in assets/parts/ and rebuild. */

/* ===== 10-forms.css ===== */
/* ==========================================================================
   10-forms.css — SURFACE: global form controls (Group A foundation)
   --------------------------------------------------------------------------
   Owns the control vocabulary every other Group A surface inherits:
   checkout, my-account (login / register / lost password / edit account /
   address book), cart coupon, checkout coupon, the delivery-city modal, and
   the Contact-Form-7 newsletter. Buttons are NOT here — they are §2 of
   kc-redesign.css and stay there.

   DOM VERIFIED (fetched from staging, read, not assumed) --------------------
   /my-account/                 login + register: p.form-row.form-row-wide >
                                label + input.input-text; "Remember me" is
                                p.form-row.form-group > label > input[checkbox]
                                + span. Lost-password modal (#lost-pswd) and
                                the sitewide login popup (#nm-login-popup-wrap)
                                are printed into EVERY page's footer.
   /checkout/  (reached by adding 3 x #20115 — the store rejects checkout
                below the $60 minimum, which is why an empty-cart fetch
                returns "There are some issues with the items in your cart")
                                p.form-row.<modifiers> > label.required_field
                                (+ span.required / span.optional) >
                                span.woocommerce-input-wrapper > control.
                                Modifiers seen live: form-row-first/-last/-wide,
                                address-field, update_totals_on_change,
                                validate-required / -email / -phone / -postcode
                                / -state, and thwcfd-field-wrapper +
                                thwcfd-field-{text,tel,email,state,country,
                                textarea} (Checkout Field Editor Pro 2.1.9).
                                #billing_city / #billing_state /
                                #billing_country / #shipping_state are enhanced
                                by selectWoo 1.0.9 -> .select2-container, so
                                "the select" on checkout is a div, not a select.
                                Checkboxes: label.checkbox > input + span.
                                Radios: li.wc_payment_method > input + label.
   /cart/                       div.coupon > label + #coupon_code, and
                                .nm-coupon-inner > #nm-coupon-code.
   footer (all pages)           .modal-city .modal-body select.form-select
                                #city_delivery, and the wpcf7 newsletter
                                input.wpcf7-email + input.wpcf7-submit.

   THEME SELECTORS BEATEN (specificity given as id,class,type) ---------------
   savoy/style.css
     input[…],select,textarea                    (0,1,1) outline:0
     input[…]:focus,select:focus,textarea:focus  (0,2,1) outline:0  <- the
       removed focus ring. Restored in §4.
     @media(-webkit-min-device-pixel-ratio:0){select} (0,0,1) raster .gif arrow
     ::-webkit-input-placeholder                 opacity:.5
   savoy/assets/css/shop.css
     .form-row label                             (0,1,1) colour #282828
     .form-row label .required                   (0,2,1) opacity:.5
     .form-row.woocommerce-invalid .input-text   (0,2,1) border-bottom only
     .nm-validation-inline-notices .form-row
       .woocommerce-invalid-required-field:after (0,3,1) "Required field."
     .select2-container--default
       .select2-selection--single                (0,2,0) height:41px
       …--single:focus                           (0,3,1) outline:0
   savoy-child/assets/css/main.css
     input[…],input.input-text                   (0,1,1) height:56 radius:4
     .nm-login-popup-wrap .form-row-wide label    (0,2,1) floating label
     .nm-login-popup-wrap .form-row-wide.focus label (0,3,1)
     .nm-login-popup-wrap .form-row-wide input:not(…) (0,3,1) padding:20px 15px 0
     .woocommerce-account #customer_login .form-row-wide label (1,2,1) floating
     .woocommerce-account #customer_login .form-row-wide input:not(…) (1,3,1)
     .woocommerce-account #customer_login p:nth-of-type(3) input[checkbox]
                                                 (1,3,2) display:none
     .woocommerce-MyAccount-content .woocommerce-form-row label (0,2,1) floating
     .woocommerce-shipping-fields #shipping_{first_name,last_name,company,
       postcode}_field label                     (1,1,1) position:absolute
                                                 !important  <- the theme names
       four fields, but this checkout renders no company field (live /checkout/
       fetch: the string `shipping_company` does not appear), so only THREE of
       the nine shipping fields that DO render — first_name, last_name, country,
       address_1, address_2, state, city, postcode, phone — float. The other six,
       and every billing field, print a normal label. One checkout page ships two
       different label systems side by side: the "same input, three looks" bug.
       The rule also writes colour / text-transform / padding, so un-floating
       alone does not merge the two systems — see §3.
     .woocommerce-billing-fields .form-row:not(#billing_city_field)
       :not(#billing_state_field):not(#billing_country_field)
       :not(.address-field) input:not([type=file])  (3,4,1)  <- three ids
     .woocommerce-checkout .checkbox input        (0,2,1) display:none
     .woocommerce-checkout .checkbox span:before  (0,2,2) 16px #e7e7e7 box
     .modal-city .modal-content .form-select      (0,3,0)
   savoy-child/style.css
     body.woocommerce-checkout .page .woocommerce input  (0,3,1) + !important
       on background / border / border-radius  <- an !important wall. §3 beats
       it with !important at (0,5,2); that same rule also clears the (3,4,1)
       three-id billing selector above, which is why it is the only place in
       this file where !important carries a whole block.
     body.woocommerce-checkout .form-row label    (0,2,1) colour #727272
     body.woocommerce-checkout textarea.input-text (0,2,1)+!imp radius:10px
     body.woocommerce-checkout span.select2-selection (0,2,1)+!imp radius:10px
     body.woocommerce-checkout [type=radio]:checked + label:before (0,3,3)
                                                 +!important 16px #ddd circle
                                                 (beaten inside #shipping_method
                                                 only — see §10 on why #payment
                                                 is left to kc-redesign.css §7)
     body.woocommerce-checkout #shipping_method [type=radio]:checked
       + label:after                             (1,3,3) dot painted #AA2934
     body.woocommerce-checkout .checkbox span:before (0,3,3)
     .modal.modal-city.in .modal-dialog #city_delivery (1,3,1) 14px on mobile
   Savoy customiser inline <style> (in <head>)
     body                                        font-size:14px
     @media(max-width:768px){.wpcf7 .wpcf7-form-control} font-size:14px
   third-party/normalize.min.css 3.0.2
     button,input,optgroup,select,textarea{font:inherit}

   :is() is used to keep the scope lists readable. Its specificity is that of
   its MOST SPECIFIC argument, so each list has to be read rather than assumed.
   The three kinds used below:
     scope lists   (.form-row, .woocommerce-input-wrapper, …)  single classes
                   -> (0,1,0)
     control lists (input[type="text"], …, input.input-text, textarea, select)
                   -> (0,1,1), because input.input-text is a class AND a type.
                   NOT (0,1,0).
     §3's shipping-field lists (#shipping_first_name_field, …) are all ids
                   -> (1,0,0).
   Every total quoted in the comments below is derived on that basis. Both
   non-obvious cases make a rule MORE specific than a careless reading would
   suggest, so nothing here is under-specified — but a future edit that drops a
   compound selector into one of these lists moves the total again. Re-derive;
   do not trust the number already written.
   :is() is a weaker requirement than the :has() already shipping in
   kc-redesign.css §7/§22, so it adds no new browser floor.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. THE 16px FLOOR — the one change on this page that is worth money
   --------------------------------------------------------------------------
   normalize 3.0.2 gives every control `font: inherit`; Savoy's customiser
   inline CSS sets `body { font-size: 14px }`; nothing anywhere re-declares a
   font-size on a text control. So every field on checkout and my-account
   computes to 14px, and iOS Safari zooms the viewport the moment a customer
   taps one. 86.5% of this store's search traffic is mobile (GSC 90d) and
   61% of recent orders are e-Transfer, i.e. they all pass through this form.
   16px is the threshold — not 15.9px.
   Specificity: body(1 type) + .kc-rd(1 class) + :is scope(1 class)
              + :is control(1 class 1 type) = (0,3,2).
   -------------------------------------------------------------------------- */

body.kc-rd
:is(.form-row, .woocommerce-input-wrapper, .wpcf7, .checkout_coupon, .coupon,
    .nm-coupon, .woocommerce-address-fields, .woocommerce-EditAccountForm,
    .nm-order-track, .modal-body, .woocommerce-form-row)
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input[type="search"],
    input[type="url"], input[type="date"], input.input-text,
    input.woocommerce-Input, input.wpcf7-text, input.wpcf7-email,
    textarea, select) {
	font-family: var(--kc-font-body);
	font-size: 16px;
	line-height: 1.4;
	color: var(--text);
	-webkit-text-size-adjust: 100%;
}

/* The newsletter capture field is the only control the theme sizes explicitly,
   and it sizes it DOWN to 14px below 768px — the exact viewport where the zoom
   fires. (0,3,2) over the inline (0,2,0). */
body.kc-rd .wpcf7 input.wpcf7-form-control,
body.kc-rd .wpcf7 textarea.wpcf7-form-control,
body.kc-rd .wpcf7 select.wpcf7-form-control {
	font-size: 16px;
}

/* The city chooser in the delivery modal: (1,3,1) beaten with (1,4,2). */
body.kc-rd .modal.modal-city .modal-dialog select#city_delivery {
	font-size: 16px;
}


/* --------------------------------------------------------------------------
   2. FIELD SKIN + METRICS — one vocabulary, all tokens
   --------------------------------------------------------------------------
   Today the same input is drawn five different ways: border #E2E2E2 / #EBEBEB
   / #e7e7e7 / #ccc / #D1D1D1, radius 0 / 4px / 10px, background transparent
   or #fff. All of it collapses to --field-bg / --field-border / --field-radius.

   Height is deliberately NOT forced. The theme already lands every text field
   on 56px through six different selectors (one of them three ids deep); 56 is
   above the 48px floor, and rewriting it would be six specificity fights for a
   worse touch target. --field-height is applied as a MINIMUM instead, which
   needs no fight (used height = max(min-height, height)) and is what actually
   rescues the outliers: the 41px select2 box and the ~35px bare select.
   -------------------------------------------------------------------------- */

body.kc-rd
:is(.form-row, .woocommerce-input-wrapper, .wpcf7, .checkout_coupon, .coupon,
    .nm-coupon, .woocommerce-address-fields, .woocommerce-EditAccountForm,
    .nm-order-track, .modal-body, .woocommerce-form-row)
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input[type="search"],
    input[type="url"], input[type="date"], input.input-text,
    input.woocommerce-Input, input.wpcf7-text, input.wpcf7-email,
    textarea, select) {
	box-sizing: border-box;
	width: 100%;
	min-height: var(--field-height);
	background-color: var(--field-bg);
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
	box-shadow: none;
	transition: border-color .12s ease, box-shadow .12s ease;
}

/* Single-line controls centre their own text; the 20px top padding left over
   from the retired floating-label era pushes it 10px below centre. */
body.kc-rd
:is(.form-row, .woocommerce-input-wrapper, .wpcf7, .checkout_coupon, .coupon,
    .nm-coupon, .woocommerce-address-fields, .woocommerce-EditAccountForm,
    .nm-order-track, .modal-body, .woocommerce-form-row)
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input[type="search"],
    input[type="url"], input[type="date"], input.input-text,
    input.woocommerce-Input, input.wpcf7-text, input.wpcf7-email) {
	padding: 0 var(--kc-space-4);
}

body.kc-rd
:is(.form-row, .woocommerce-input-wrapper, .wpcf7, .woocommerce-address-fields,
    .woocommerce-EditAccountForm, .modal-body, .woocommerce-form-row)
textarea {
	padding: var(--kc-space-3) var(--kc-space-4);
	min-height: calc(var(--field-height) * 2);
	resize: vertical;
}

/* The wrapper WooCommerce puts round every checkout control is an inline
   <span>, so the field inside it sizes against an anonymous box. */
body.kc-rd .woocommerce-input-wrapper {
	display: block;
	width: 100%;
}

/* Codes are data, not prose: mono + tabular makes O/0 and I/1 separable while
   the customer is copying a coupon off a phone or an e-Transfer confirmation.
   text-transform is display-only — the submitted value is untouched, and both
   WooCommerce coupon codes and Canadian postal codes are case-insensitive. */
body.kc-rd input#coupon_code,
body.kc-rd input#nm-coupon-code,
body.kc-rd input[name="coupon_code"],
body.kc-rd input#billing_postcode,
body.kc-rd input#shipping_postcode {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	letter-spacing: .04em;
	text-transform: uppercase;
}


/* --------------------------------------------------------------------------
   3. CHECKOUT — the !important wall
   --------------------------------------------------------------------------
   `body.woocommerce-checkout .page .woocommerce input` (0,3,1) paints
   background / border / border-radius with !important, so nothing without
   !important can reach a checkout field, at any specificity. This block is
   the answer, at (0,5,2) + !important.

   It doubles as the answer to the three-id monster
   `.woocommerce-billing-fields .form-row:not(#billing_city_field)
    :not(#billing_state_field):not(#billing_country_field):not(.address-field)
    input:not([type=file])` (3,4,1) — !important outranks it regardless of ids,
   which is the only reason this file does not carry a :not(#hack) selector.

   Deliberately excludes checkbox / radio / submit: those are §8 and §9, and
   the theme's !important wall paints them too (a 10px radius on a radio).
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-checkout .woocommerce
:is(.form-row, .woocommerce-input-wrapper)
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input.input-text,
    textarea, select) {
	background-color: var(--field-bg) !important;
	border: 1px solid var(--field-border) !important;
	border-radius: var(--field-radius) !important;
}

body.kc-rd.woocommerce-checkout .woocommerce
:is(.form-row, .woocommerce-input-wrapper)
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input.input-text) {
	padding: 0 var(--kc-space-4) !important;
}

/* Three of the nine shipping fields this checkout renders still float their
   label with `position:absolute !important` (1,1,1) — first_name, last_name,
   postcode. (The theme's selector list names a fourth, #shipping_company_field;
   the Checkout Field Editor does not render it, so it is left out here rather
   than carried as a selector that matches nothing.) The other six, and every
   billing field, print a normal label above the box — the floating rules for
   those were commented out and never cleaned up.

   Un-floating alone does NOT merge the two label systems: the same theme rule
   also writes `padding: 0`, a raw `color: #2A2621` and `text-transform:
   capitalize`, and §6's generic label rule is only (0,3,2), which loses to an
   id. So the whole §6 label vocabulary is restated here — otherwise these three
   labels keep a theme hex, Title Case and no gap while every other label on the
   page is var(--text), sentence case, 14/600 with an 8px gap, and the bug just
   moves from position to colour. This is (1,2,2), which beats (1,1,1) on class
   count without !important; !important stays only on `position`, which is the
   one property the theme itself writes with !important. */
body.kc-rd .woocommerce-shipping-fields
:is(#shipping_first_name_field, #shipping_last_name_field,
    #shipping_postcode_field) > label {
	position: static !important;
	top: auto;
	left: auto;
	transform: none;
	opacity: 1;
	pointer-events: auto;
	display: block;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	line-height: 1.35;
	color: var(--text);
	text-transform: none;
	padding: 0 0 var(--kc-space-2);
	margin: 0;
}

/* Same fields, same leftover 20px top padding. (1,3,2). */
body.kc-rd .woocommerce-shipping-fields
:is(#shipping_first_name_field, #shipping_last_name_field,
    #shipping_postcode_field) input.input-text {
	padding: 0 var(--kc-space-4);
}


/* --------------------------------------------------------------------------
   4. FOCUS — Savoy sets outline:0 on every control and again on :focus.
   --------------------------------------------------------------------------
   Two states, because they answer different questions:
     :focus         quiet — "the caret is here" (mouse users get this too)
     :focus-visible loud  — the kit's 3px --trust ring, keyboard only
   3px ring at 3px offset sits inside the 8px label gap, so it never collides
   with the field above.
   -------------------------------------------------------------------------- */

body.kc-rd
:is(.form-row, .woocommerce-input-wrapper, .wpcf7, .checkout_coupon, .coupon,
    .nm-coupon, .woocommerce-address-fields, .woocommerce-EditAccountForm,
    .nm-order-track, .modal-body, .woocommerce-form-row)
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input[type="search"],
    input[type="url"], input[type="date"], input.input-text,
    input.woocommerce-Input, input.wpcf7-text, input.wpcf7-email,
    textarea, select):focus {
	border-color: var(--text);
	outline: 0;
}

body.kc-rd
:is(.form-row, .woocommerce-input-wrapper, .wpcf7, .checkout_coupon, .coupon,
    .nm-coupon, .woocommerce-address-fields, .woocommerce-EditAccountForm,
    .nm-order-track, .modal-body, .woocommerce-form-row)
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input[type="search"],
    input[type="url"], input[type="date"], input.input-text,
    input.woocommerce-Input, input.wpcf7-text, input.wpcf7-email,
    textarea, select):focus-visible {
	border-color: var(--text);
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}

/* Checkout again needs !important to get past the same wall — for BOTH states.
   The quiet :focus above is (0,4,2) with no !important, and §3 writes the
   `border` shorthand at (0,5,2) + !important over every checkout field, so on
   /checkout/ the border-colour change never lands; and since that same quiet
   rule also sets `outline: 0`, a mouse user clicking into a field there got no
   indication at all. Keyboard users were covered by the :focus-visible block
   below; this restores the pointer state on the store's highest-value form.
   (0,6,2): the two blocks differ only in the pseudo-class. */
body.kc-rd.woocommerce-checkout .woocommerce
:is(.form-row, .woocommerce-input-wrapper)
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input.input-text,
    textarea, select):focus {
	border-color: var(--text) !important;
}

body.kc-rd.woocommerce-checkout .woocommerce
:is(.form-row, .woocommerce-input-wrapper)
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input.input-text,
    textarea, select):focus-visible {
	border-color: var(--text) !important;
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}


/* --------------------------------------------------------------------------
   5. PLACEHOLDER, AUTOFILL, DISABLED
   --------------------------------------------------------------------------
   Placeholders here carry real examples ("House number and street name",
   "ASAP, 6:30pm, Next Day"), so --text-faint is too quiet for them;
   --text-muted keeps them clearly secondary to the typed value without
   becoming unreadable. Savoy's blanket opacity:.5 is cancelled so the token
   is the only thing deciding the colour.
   -------------------------------------------------------------------------- */

body.kc-rd .form-row ::placeholder,
body.kc-rd .woocommerce-input-wrapper ::placeholder,
body.kc-rd .wpcf7 ::placeholder,
body.kc-rd .coupon ::placeholder,
body.kc-rd .nm-coupon ::placeholder,
body.kc-rd .checkout_coupon ::placeholder {
	color: var(--text-muted);
	opacity: 1;
}

/* Savoy paints the autofill background with a literal `white` inset shadow,
   which is how a token-driven field silently reverts the moment Chrome fills
   it. Same trick, token value. */
body.kc-rd input:-webkit-autofill,
body.kc-rd input:-webkit-autofill:hover,
body.kc-rd input:-webkit-autofill:focus,
body.kc-rd textarea:-webkit-autofill,
body.kc-rd select:-webkit-autofill {
	-webkit-box-shadow: 0 0 0 1000px var(--field-bg) inset;
	box-shadow: 0 0 0 1000px var(--field-bg) inset;
	-webkit-text-fill-color: var(--text);
	caret-color: var(--text);
}

body.kc-rd .form-row input[disabled],
body.kc-rd .form-row select[disabled],
body.kc-rd .form-row textarea[disabled],
body.kc-rd .woocommerce-input-wrapper input[disabled],
body.kc-rd .woocommerce-input-wrapper select[disabled] {
	background-color: var(--surface-sunken);
	color: var(--text-muted);
	cursor: not-allowed;
}

body.kc-rd .form-row input[readonly],
body.kc-rd .woocommerce-input-wrapper input[readonly] {
	background-color: var(--surface-sunken);
}


/* --------------------------------------------------------------------------
   6. LABELS, REQUIRED / OPTIONAL MARKERS, HELP TEXT
   --------------------------------------------------------------------------
   Labels are Poppins, not the display face and not the mono label face: the
   kit puts "buttons, forms, navigation" squarely in the Body role, and mono
   is reserved for values (prices, weights, order numbers, codes).
   Colour is full ink. `body.woocommerce-checkout .form-row label` currently
   paints them #727272 — grey on the field names a customer needs to read to
   place a $133 order.

   The required marker is --text-muted, NOT --urgent. Every checkout field but
   two is required, so a tomato asterisk would put the error colour on screen
   twelve times before anything is actually wrong, and the kit is explicit that
   tomato twice on one screen means one of them is a mistake. The optional
   fields already say "(optional)" in words, which is the stronger signal.
   -------------------------------------------------------------------------- */

body.kc-rd
:is(.form-row, .woocommerce-address-fields, .woocommerce-EditAccountForm,
    .woocommerce-form-row)
> label:not(.woocommerce-form__label) {
	display: block;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	line-height: 1.35;
	color: var(--text);
	padding: 0 0 var(--kc-space-2);
	margin: 0;
}

/* FOUR floating-label islands, one selector each because each has to out-weigh
   a different theme rule. All four are un-floated so the login popup, the
   lost-password modal, the my-account form and checkout finally look like one
   product. The `.focus` class the child theme's build.js toggles becomes inert
   — harmless.

   #lost-pswd is the one that is easy to miss: `#lost-pswd .form-row label` and
   `#lost-pswd .user_login label` are (1,1,1), and the generic label rule above
   is (0,3,2), which loses on the id. Leaving it out was worse than leaving it
   alone — the generic rule still won `font-size` and `font-weight` (the theme
   rule declares neither), so the floating label rendered heavier at opacity .4
   on top of the input. The modal is printed into every page's footer, so this
   shows up on /cart/ as much as on /my-account/. (1,2,2) beats (1,1,1). */
body.kc-rd.woocommerce-account #customer_login .form-row-wide > label,
body.kc-rd .nm-login-popup-wrap .form-row-wide > label,
body.kc-rd .woocommerce-MyAccount-content .woocommerce-form-row > label,
body.kc-rd #lost-pswd :is(.form-row, .user_login) > label {
	position: static;
	top: auto;
	left: auto;
	transform: none;
	opacity: 1;
	pointer-events: auto;
	text-transform: none;
	font-size: 14px;
	font-weight: 600;
	color: var(--text);
	padding: 0 0 var(--kc-space-2);
}

/* Matching padding fix on the inputs those labels used to sit inside.
   (1,4,2) / (0,4,2) / (0,4,2) against (1,3,1) / (0,3,1) / (0,3,1). */
body.kc-rd.woocommerce-account #customer_login .form-row-wide input.input-text,
body.kc-rd .nm-login-popup-wrap .form-row-wide input.input-text,
body.kc-rd .woocommerce-MyAccount-content .woocommerce-form-row input.input-text {
	padding: 0 var(--kc-space-4);
	background-color: var(--field-bg);
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
}

body.kc-rd .form-row label .required,
body.kc-rd .form-row label abbr.required,
body.kc-rd .woocommerce-form-row label .required {
	color: var(--text-muted);
	font-weight: 400;
	opacity: 1;
	text-decoration: none;
	border-bottom: 0;
}

body.kc-rd .form-row label .optional,
body.kc-rd .woocommerce-form-row label .optional {
	color: var(--text-muted);
	font-weight: 400;
}

/* Help text under or beside a field. 14px, not 13: the kit's type scale has no
   13px step (Body is 15–16, 12 is reserved for the Metric/Label role), and the
   password rules are text a customer has to read to finish creating an account.
   14 matches the label size directly above it. */
body.kc-rd .woocommerce-password-hint,
body.kc-rd .form-row > em,
body.kc-rd .form-row > small,
body.kc-rd .woocommerce-MyAccount-content .form-row > span > em,
body.kc-rd #customer_details .create-account > p,
body.kc-rd .register > p:not(.form-row):not(.form-actions) {
	display: block;
	font-family: var(--kc-font-body);
	font-size: 14px;
	line-height: 1.5;
	color: var(--text-muted);
	padding-top: var(--kc-space-1);
}


/* --------------------------------------------------------------------------
   7. SELECT — native arrow
   --------------------------------------------------------------------------
   Savoy hides the OS arrow with -webkit-appearance:none and paints a 8x4px
   raster .gif back in, inside a -webkit-min-device-pixel-ratio media query —
   so Firefox gets no arrow at all, and the arrow that does appear cannot
   follow a token. Replaced with a two-gradient chevron in currentColor: no
   image, no request, and it inherits --text like everything else.
   -------------------------------------------------------------------------- */

body.kc-rd
:is(.form-row, .woocommerce-input-wrapper, .wpcf7, .woocommerce-address-fields,
    .woocommerce-EditAccountForm, .modal-body, .woocommerce-form-row) select {
	-webkit-appearance: none;
	-moz-appearance: none;
	appearance: none;
	padding: 0 calc(var(--kc-space-6) + var(--kc-space-2)) 0 var(--kc-space-4);
	background-color: var(--field-bg);
	background-repeat: no-repeat;
	background-size: 6px 6px, 6px 6px;
	background-position:
		calc(100% - 24px) calc(50% - 2px),
		calc(100% - 18px) calc(50% - 2px);
	background-image:
		linear-gradient(45deg, transparent 50%, currentColor 50%),
		linear-gradient(135deg, currentColor 50%, transparent 50%);
	cursor: pointer;
	text-overflow: ellipsis;
}

/* Checkout's !important wall repaints background — restate the arrow there. */
body.kc-rd.woocommerce-checkout .woocommerce
:is(.form-row, .woocommerce-input-wrapper) select {
	background-color: var(--field-bg) !important;
	background-repeat: no-repeat !important;
	background-size: 6px 6px, 6px 6px !important;
	background-position:
		calc(100% - 24px) calc(50% - 2px),
		calc(100% - 18px) calc(50% - 2px) !important;
	background-image:
		linear-gradient(45deg, transparent 50%, currentColor 50%),
		linear-gradient(135deg, currentColor 50%, transparent 50%) !important;
	padding: 0 calc(var(--kc-space-6) + var(--kc-space-2)) 0 var(--kc-space-4) !important;
}

/* Options render with the OS UI on most platforms, but Windows/Chrome and
   Android honour these and would otherwise inherit a transparent background. */
body.kc-rd .form-row select option,
body.kc-rd .woocommerce-input-wrapper select option,
body.kc-rd .modal-body select option {
	background-color: var(--field-bg);
	color: var(--text);
}


/* --------------------------------------------------------------------------
   8. SELECT2 / selectWoo — city, province, country
   --------------------------------------------------------------------------
   These four checkout fields are not <select> elements by the time a customer
   sees them; selectWoo swaps in a div with tabindex="0". Styling the native
   select alone would leave the most-used dropdowns on the page untouched.
   The dropdown itself is appended to <body>, which is still inside body.kc-rd,
   so the kill switch still removes it.
   -------------------------------------------------------------------------- */

body.kc-rd .select2-container--default .select2-selection--single {
	box-sizing: border-box;
	min-height: var(--field-height);
	display: flex;
	align-items: center;
	background-color: var(--field-bg);
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
	outline: 0;
}

/* Beats body.woocommerce-checkout span.select2-selection (0,2,1)+!important. */
body.kc-rd.woocommerce-checkout .woocommerce span.select2-selection--single {
	background-color: var(--field-bg) !important;
	border: 1px solid var(--field-border) !important;
	border-radius: var(--field-radius) !important;
}

body.kc-rd .select2-container--default .select2-selection--single
	.select2-selection__rendered {
	font-family: var(--kc-font-body);
	font-size: 16px;
	color: var(--text);
	padding: 0 calc(var(--kc-space-6) + var(--kc-space-2)) 0 var(--kc-space-4);
}

body.kc-rd .select2-container--default .select2-selection--single
	.select2-selection__placeholder {
	color: var(--text-muted);
}

body.kc-rd .select2-container--default .select2-selection--single
	.select2-selection__arrow {
	top: 0;
	right: var(--kc-space-3);
	width: var(--kc-space-4);
	pointer-events: none;
}

body.kc-rd .select2-container--default .select2-selection--single
	.select2-selection__arrow b {
	border-top-color: var(--text);
}

body.kc-rd .select2-container--default.select2-container--open
	.select2-selection--single .select2-selection__arrow b {
	border-bottom-color: var(--text);
}

/* The closed combobox is what receives keyboard focus. Savoy zeroes its
   outline at (0,3,1); this is (0,4,1) — three classes plus the pseudo-class,
   and `body` is the only type selector. */
body.kc-rd .select2-container--default .select2-selection--single:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}

body.kc-rd .select2-container--default.select2-container--open
	.select2-selection--single {
	border-color: var(--text);
}

body.kc-rd .select2-dropdown,
body.kc-rd .select2-container--open .select2-dropdown--above,
body.kc-rd .select2-container--open .select2-dropdown--below {
	background-color: var(--surface-raised);
	border: 1px solid var(--text);
	border-radius: var(--field-radius);
}

body.kc-rd .select2-results {
	background: var(--surface-raised);
	border-top: 0;
}

body.kc-rd .select2-container--default .select2-results__option {
	font-family: var(--kc-font-body);
	font-size: 16px;
	color: var(--text);
	padding: var(--kc-space-3) var(--kc-space-4);
	border-bottom: 1px solid var(--border);
	min-height: var(--field-height);
	display: flex;
	align-items: center;
	touch-action: manipulation;
}

body.kc-rd .select2-container--default .select2-results__option:last-child {
	border-bottom: 0;
}

/* The scroller is the .select2-results__options list (select2 ships it
   max-height:200px; overflow-y:auto — nothing in this file re-declares that),
   not .select2-dropdown, which is why the containment sits here. Same idiom
   as 40-nav-menu.css and 70-search.css: a flick that bottoms out must not
   chain to the checkout page behind the open dropdown. */
body.kc-rd .select2-container--default .select2-results > .select2-results__options {
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
}

/* --surface-sunken on --surface-raised is 1.17:1 — background alone cannot
   mark the active row (and this rule overrides select2's shipped high-contrast
   blue). The inset ink bar is ~15:1 on the sunken cream and the weight change
   is a second, non-chromatic channel; both stay visually distinct from the
   amber chosen row below. */
body.kc-rd .select2-container--default
	.select2-results__option--highlighted[aria-selected],
body.kc-rd .select2-container--default
	.select2-results__option--highlighted[data-selected] {
	background: var(--surface-sunken);
	color: var(--text);
	box-shadow: inset 3px 0 0 var(--text);
	font-weight: 600;
}

body.kc-rd .select2-container--default
	.select2-results__option[aria-selected="true"],
body.kc-rd .select2-container--default
	.select2-results__option[data-selected="true"] {
	background: var(--action);
	color: var(--on-action);
	font-weight: 600;
}

body.kc-rd .select2-container--default .select2-search--dropdown
	.select2-search__field {
	font-family: var(--kc-font-body);
	font-size: 16px;
	min-height: var(--field-height);
	padding: 0 var(--kc-space-3);
	color: var(--text);
	background-color: var(--field-bg);
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
}

body.kc-rd .select2-container--default .select2-search--dropdown
	.select2-search__field:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: -1px;
}


/* --------------------------------------------------------------------------
   9. CHECKBOX
   --------------------------------------------------------------------------
   Three separate hand-rolled checkbox systems exist, and all three set
   `display: none` on the real input. display:none removes a control from the
   tab order AND from the accessibility tree, so today "Create an account?",
   "Ship to a different address?" and "Remember me" cannot be reached or
   announced at all without a mouse. Fixed by swapping display:none for a
   transparent, full-size, still-focusable input laid over the drawn box —
   which also makes the whole label a tap target instead of a 16px square.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-checkout label.checkbox,
body.kc-rd #customer_login p.form-group > label,
body.kc-rd .nm-login-popup-wrap p.form-group > label {
	position: relative;
	display: flex;
	align-items: center;
	min-height: var(--btn-min-height);
	cursor: pointer;
	/* Kills double-tap-to-zoom on a full-width tappable row (a fast re-tap
	   otherwise zooms the form on iOS); pan and pinch are untouched. */
	touch-action: manipulation;
	color: var(--text);
}

/* Sized and placed to sit exactly under the drawn box rather than over the
   whole label: label-for association already gives the full label as a click
   target, and covering the label would swallow the links WooCommerce prints
   inside consent labels. */
body.kc-rd.woocommerce-checkout label.checkbox > input[type="checkbox"],
body.kc-rd.woocommerce-account #customer_login p.form-group > label > input[type="checkbox"],
body.kc-rd .nm-login-popup-wrap p.form-group > label > input[type="checkbox"] {
	display: block;
	position: absolute;
	left: 0;
	top: 50%;
	transform: translateY(-50%);
	width: 22px;
	height: 22px;
	margin: 0;
	padding: 0;
	opacity: 0;
	cursor: pointer;
	z-index: 1;
}

/* The drawn box. Scoped through .checkbox / .form-group so it can never hit
   the <span class="required">*</span> that lives inside a field label.

   `position: static` is load-bearing, not tidiness. On checkout the theme's box
   is already in flow, so margin-right spaces it. On my-account and in the login
   popup the theme writes `position: absolute; left: 0; top: 0` on this exact
   pseudo-element and pays for the space with `padding-left: 25px` on the span —
   an absolutely positioned box ignores margin-right, flex and display, so
   without this reset the same checkbox gets a 3px gap on one page and 12px on
   another, and the drawn box (pinned to the span's top) can drift away from the
   transparent input above, which is centred on the LABEL. Re-flowing the box is
   what makes the span's padding-left reset below correct rather than cosmetic:
   the two changes only make sense together. */
body.kc-rd.woocommerce-checkout label.checkbox > span::before,
body.kc-rd #customer_login p.form-group > label > span::before,
body.kc-rd .nm-login-popup-wrap p.form-group > label > span::before {
	content: "";
	position: static;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 22px;
	height: 22px;
	margin-right: var(--kc-space-3);
	background: var(--field-bg);
	border: 2px solid var(--field-border);
	border-radius: var(--kc-radius-sm);
	color: var(--on-action);
	font-size: 15px;
	line-height: 1;
	transform: none;
	transition: background-color .12s ease, border-color .12s ease;
}

body.kc-rd.woocommerce-checkout label.checkbox > input:checked + span::before,
body.kc-rd #customer_login p.form-group > label > input:checked + span::before,
body.kc-rd .nm-login-popup-wrap p.form-group > label > input:checked + span::before {
	content: "\2713";
	background: var(--action);
	border-color: var(--text);
	color: var(--on-action);
}

body.kc-rd.woocommerce-checkout label.checkbox > input:focus-visible + span::before,
body.kc-rd #customer_login p.form-group > label > input:focus-visible + span::before,
body.kc-rd .nm-login-popup-wrap p.form-group > label > input:focus-visible + span::before {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}

/* padding-left: 0 is the other half of the position reset above. The theme
   reserves 25px here for a box that is no longer absolutely positioned; leaving
   it would put the gap in twice. */
body.kc-rd.woocommerce-checkout label.checkbox > span,
body.kc-rd #customer_login p.form-group > label > span,
body.kc-rd .nm-login-popup-wrap p.form-group > label > span {
	display: flex;
	align-items: center;
	font-family: var(--kc-font-body);
	font-size: 15px;
	color: var(--text);
	padding-left: 0;
}

/* Everything that is still a real, un-hidden checkbox (register form, address
   book, anything a plugin prints) gets the accent colour and a 20px box
   instead of a second hand-rolled system. */
body.kc-rd .form-row input[type="checkbox"],
body.kc-rd .woocommerce-form-row input[type="checkbox"],
body.kc-rd .woocommerce-form__label > input.input-checkbox {
	accent-color: var(--action);
	width: 20px;
	height: 20px;
}

body.kc-rd .woocommerce-form__label-for-checkbox:not(.checkbox) {
	display: flex;
	align-items: center;
	gap: var(--kc-space-2);
	min-height: var(--btn-min-height);
	color: var(--text);
	cursor: pointer;
	touch-action: manipulation;
}


/* --------------------------------------------------------------------------
   10. RADIO
   --------------------------------------------------------------------------
   Checkout collapses every radio to 0x0 with opacity:0 and draws the control
   on the adjacent label's ::before/::after. That is survivable (the input is
   still focusable, unlike the checkboxes) but it means the focus ring has to
   be hung on the pseudo-element, and it means the circle is 16px of #ddd.

   #payment is deliberately NOT restyled here. kc-redesign.css §7 renders
   payment methods as selectable cards and the child theme strips the circle
   inside them to suit that; repainting a radio dial back into a card would be
   redesigning someone else's component. Only the missing focus ring is added
   there, on the card. So the circle rules below are scoped to #shipping_method
   and to plain .form-row radios.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-checkout #shipping_method
	input[type="radio"]:checked + label::before,
body.kc-rd.woocommerce-checkout #shipping_method
	input[type="radio"]:not(:checked) + label::before {
	/* !important is required here: the child theme writes width / height /
	   border / border-radius on this exact pseudo-element with !important at
	   (0,3,3). This is (1,4,4). */
	width: 20px !important;
	height: 20px !important;
	border: 2px solid var(--field-border) !important;
	border-radius: 50% !important;
	background: var(--field-bg);
	/* Centred with top+margin rather than a transform: the theme animates the
	   dot with transform:scale(0/1) at (1,3,3), and a transform here would
	   out-specify it and leave the unchecked dot permanently visible. */
	top: 50%;
	margin-top: -10px;
}

body.kc-rd.woocommerce-checkout #shipping_method
	input[type="radio"]:checked + label::before {
	border-color: var(--text) !important;
}

/* The shipping-method dot is painted #AA2934 — a second accent colour the kit
   does not have. (1,4,4) over (1,3,3). */
body.kc-rd.woocommerce-checkout #shipping_method
	input[type="radio"]:checked + label::after,
body.kc-rd.woocommerce-checkout #shipping_method
	input[type="radio"]:not(:checked) + label::after {
	width: 10px;
	height: 10px;
	top: 50%;
	margin-top: -5px;
	left: 5px;
	background: var(--text);
}

body.kc-rd.woocommerce-checkout #shipping_method input[type="radio"] + label {
	display: inline-flex;
	align-items: center;
	min-height: var(--btn-min-height);
	color: var(--text);
	cursor: pointer;
	touch-action: manipulation;
}

/* Focus. The input is 0x0, so the ring has to hang on something with a box. */
body.kc-rd.woocommerce-checkout #shipping_method
	input[type="radio"]:focus-visible + label::before {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}

/* Payment method cards: the radio inside is 0x0 and opacity:0, so today a
   keyboard user arrowing through payment options gets no visible feedback at
   all. The ring goes on the card, which is the thing that reads as selected. */
body.kc-rd .wc_payment_method:has(input[type="radio"]:focus-visible) {
	outline: 3px solid var(--trust);
	outline-offset: 2px;
}

/* Real radios outside checkout. */
body.kc-rd .form-row input[type="radio"],
body.kc-rd .woocommerce-form__label > input.input-radio {
	accent-color: var(--action);
}


/* --------------------------------------------------------------------------
   11. VALIDATION
   --------------------------------------------------------------------------
   WooCommerce's checkout.js puts woocommerce-invalid / -required-field /
   -email / -phone / woocommerce-validated on the .form-row.
   Savoy signals invalid with `border-bottom-color: #f27362` and nothing else —
   a 1px colour change on one edge, which is exactly the "colour alone" failure
   mode. Every invalid state below is border + word.

   Message ordering is deliberate: the generic case first, the specific formats
   next, "required" last. All four are equal specificity, so the last matching
   rule wins the `content`, and an empty required email should read "required",
   not "invalid format".

   TWO things had to be fixed here before any of that was true on /checkout/,
   the one page where validation actually decides whether an order lands:

   1. SPECIFICITY. §3 writes `border: 1px solid var(--field-border) !important`
      at (0,5,2) over every checkout text field. The generic invalid rule below
      is (0,4,2) + !important (worse for textarea/select at (0,3,2)), so between
      two !important declarations §3 simply won and the red border NEVER
      RENDERED ON CHECKOUT — degrading "border + word" back to word-only. The
      validated rule, which carries no !important at all, was dead there for the
      same reason. Hence the four checkout-scoped blocks below, at (0,6,2) for
      the controls and (0,6,1) for the select2 box.

   2. CONTRAST. --urgent (--kc-tomato-600) computes 4.14:1 on --surface, 4.28:1
      on --surface-raised, 3.65:1 on --surface-sunken. All below the AA floor
      for text under 18.66px, and this copy is small by design. So the MESSAGE
      text is --text (about 15:1 everywhere) and --urgent moves to a 2px bar and
      the field border, where it is a large-scale graphical object and 3:1
      applies. The error still reads as an error at a glance, and a customer who
      cannot separate tomato from cream now gets weight + bar + words instead of
      a colour they cannot see. Do NOT "fix" this by enlarging the copy to
      18.66px bold — that is louder than the kit's tomato budget allows.
      A darker text-safe tomato belongs in kc-redesign.css §0 as a semantic
      alias, not as a raw hex in this partial; this file owns no tokens.
   -------------------------------------------------------------------------- */

body.kc-rd .form-row.woocommerce-invalid input.input-text,
body.kc-rd .form-row.woocommerce-invalid textarea,
body.kc-rd .form-row.woocommerce-invalid select,
body.kc-rd .form-row.woocommerce-invalid .select2-selection--single,
body.kc-rd .wpcf7 .wpcf7-not-valid {
	/* !important: off checkout this is enough to beat the theme's plain
	   border-bottom-color. On checkout it is NOT — see the next block. */
	border: 2px solid var(--urgent) !important;
}

/* CHECKOUT: the same paint, specific enough to actually land. (0,6,2) for the
   controls and (0,6,1) for the select2 box, against §3's (0,5,2) + !important
   and §8's (0,4,2) + !important. The descendant combinator covers both DOM
   shapes checkout produces — control directly in p.form-row, and control inside
   span.woocommerce-input-wrapper inside p.form-row. */
body.kc-rd.woocommerce-checkout .woocommerce .form-row.woocommerce-invalid
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input.input-text,
    textarea, select) {
	border: 2px solid var(--urgent) !important;
}

body.kc-rd.woocommerce-checkout .woocommerce .form-row.woocommerce-invalid
	.select2-selection--single {
	border: 2px solid var(--urgent) !important;
}

/* `position: static` is not redundant. The theme ships
   `.nm-validation-inline-notices .form-row.woocommerce-invalid-required-field
   :after { position: absolute; left: 0 }` at (0,3,1), and that container class
   is on the real checkout markup. A rule only wins the properties it declares,
   so without this reset `position: absolute` survived: "This field is required."
   — by far the most common message — was taken out of flow, reserved no height
   and overlapped the row beneath, while "Enter a valid email address." and
   "Check this field." (no such theme rule) stayed in flow and pushed the layout.
   Four messages, two layout behaviours. Worse on shipping rows, where no
   ancestor .form-row is positioned, so the message anchored to something far
   away. All four flow identically now. */
body.kc-rd .form-row.woocommerce-invalid::after {
	position: static;
	left: auto;
	display: block;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	line-height: 1.4;
	color: var(--text);
	border-left: 2px solid var(--urgent);
	padding-top: var(--kc-space-1);
	padding-left: var(--kc-space-2);
	content: "Check this field.";
}

body.kc-rd .form-row.woocommerce-invalid-email::after {
	content: "Enter a valid email address.";
}

body.kc-rd .form-row.woocommerce-invalid-phone::after {
	content: "Enter a valid phone number.";
}

body.kc-rd .form-row.woocommerce-invalid-required-field::after {
	content: "This field is required.";
}

body.kc-rd .form-row.woocommerce-validated input.input-text,
body.kc-rd .form-row.woocommerce-validated textarea,
body.kc-rd .form-row.woocommerce-validated select,
body.kc-rd .form-row.woocommerce-validated .select2-selection--single {
	border-color: var(--trust);
}

/* Same story as the invalid state: §3's `border: … !important` at (0,5,2)
   swallows a plain border-color, so the validated state was invisible on the
   only page that sets the class. (0,6,2) / (0,6,1) + !important. */
body.kc-rd.woocommerce-checkout .woocommerce .form-row.woocommerce-validated
:is(input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input.input-text,
    textarea, select) {
	border-color: var(--trust) !important;
}

body.kc-rd.woocommerce-checkout .woocommerce .form-row.woocommerce-validated
	.select2-selection--single {
	border-color: var(--trust) !important;
}

body.kc-rd .form-row.woocommerce-validated::after {
	content: none;
}

/* Field-level WooCommerce error text that lands inside a row. The page-level
   .woocommerce-error notice list is the notices surface, not this one.
   Same treatment as the ::after messages: ink text on an --urgent bar, at 14px
   — which for .wpcf7-not-valid-tip also stops this rule (0,3,1) from SHRINKING
   the theme's 14px (0,1,1) down to 13. */
body.kc-rd .form-row .woocommerce-error,
body.kc-rd .wpcf7 .wpcf7-not-valid-tip {
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	line-height: 1.4;
	color: var(--text);
	margin: var(--kc-space-1) 0 0;
	padding: 0 0 0 var(--kc-space-2);
	list-style: none;
	background: none;
	border: 0;
	border-left: 2px solid var(--urgent);
}

/* Savoy's password meter is fine structurally; the colours are not tokens. */
body.kc-rd .woocommerce-password-strength {
	font-family: var(--label-font);
	font-size: 12px;
	letter-spacing: .06em;
	text-transform: uppercase;
	color: var(--text);
	background-color: var(--surface-sunken);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-sm);
	padding: var(--kc-space-1) var(--kc-space-3);
}

/* 12px is fine here — this is the Label role, a chip whose text is a word
   ("Very weak"), not prose. But --urgent on --surface-raised is 4.28:1, so the
   colour goes on the border (a graphical object, 3:1) and the word stays ink.
   The .good/.strong pair below is --on-trust on --trust = 7.8:1 and is left. */
body.kc-rd .woocommerce-password-strength.short,
body.kc-rd .woocommerce-password-strength.bad {
	color: var(--text);
	border-color: var(--urgent);
	background-color: var(--surface-raised);
}

body.kc-rd .woocommerce-password-strength.good,
body.kc-rd .woocommerce-password-strength.strong {
	color: var(--on-trust);
	background-color: var(--trust);
	border-color: var(--trust);
}


/* --------------------------------------------------------------------------
   12. ROW RHYTHM + 375px
   --------------------------------------------------------------------------
   Checkout already stacks first/last name below 480px; nothing else does. A
   48%-wide field on a 375px phone is ~165px, and "First name *" wraps inside
   its own label. Stacking is applied to every form, at 560px, so the address
   book and the register form behave like checkout.
   -------------------------------------------------------------------------- */

body.kc-rd .form-row {
	margin-bottom: var(--kc-space-5);
}

body.kc-rd .form-row:last-child {
	margin-bottom: var(--kc-space-4);
}

@media (max-width: 560px) {
	body.kc-rd .form-row-first,
	body.kc-rd .form-row-last {
		float: none;
		width: 100%;
	}

	body.kc-rd .form-row-first {
		margin-bottom: var(--kc-space-5);
	}

	/* Fields are the full gutter-to-gutter width on a phone; the 3px focus
	   ring must not be clipped by the page edge. */
	body.kc-rd .woocommerce-billing-fields,
	body.kc-rd .woocommerce-shipping-fields,
	body.kc-rd .woocommerce-additional-fields {
		padding-left: var(--kc-space-1);
		padding-right: var(--kc-space-1);
	}
}

/* NO cart-coupon block here, deliberately. There used to be one, stacking
   `.coupon` under 560px "because the cart coupon row breaks under ~420px" — it
   never ran and there was nothing to fix. Savoy's shop.css hides WooCommerce's
   default coupon outright:
     .shop_table.cart .actions .coupon { visibility:hidden; position:absolute;
       top:-1px; left:-1px; width:1px; height:1px; overflow:hidden }   (0,4,0)
   in no media query, and nothing in savoy-child or kc-redesign.css un-hides it.
   `body.kc-rd .coupon` is (0,2,1) and loses on every one of those properties,
   so the whole block was laying out a 1x1 invisible element. (60-cart-page.css
   documents the same thing from its own DOM read: "div.coupon <- visibility:
   hidden by Savoy".)
   The coupon customers actually use on /cart/ is #nm-coupon-code inside
   .nm-coupon-wrap > .nm-coupon-inner, which is 60-cart-page.css's surface, not
   this file's — repointing these rules at it would be editing someone else's
   file through a selector. `.coupon` stays in the §1/§2 scope lists: harmless
   there, and it still covers the field if the theme ever stops hiding it. */


/* --------------------------------------------------------------------------
   13. MOTION
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	/* The .wpcf7 / .modal-body entries close a scope gap: §2 hands its
	   transition to controls under an eleven-entry :is() list, but this list
	   originally named only .form-row / .woocommerce-input-wrapper — leaving
	   select#city_delivery (which sits directly in .modal-body, no .form-row
	   ancestor) and the newsletter fields still animating under
	   prefers-reduced-motion. */
	body.kc-rd .form-row input,
	body.kc-rd .form-row select,
	body.kc-rd .form-row textarea,
	body.kc-rd .woocommerce-input-wrapper input,
	body.kc-rd .woocommerce-input-wrapper select,
	body.kc-rd .woocommerce-input-wrapper textarea,
	body.kc-rd .wpcf7 input,
	body.kc-rd .wpcf7 textarea,
	body.kc-rd .wpcf7 select,
	body.kc-rd .modal-body input,
	body.kc-rd .modal-body select,
	body.kc-rd .select2-container--default .select2-selection--single,
	body.kc-rd label.checkbox > span::before,
	body.kc-rd p.form-group > label > span::before {
		transition: none;
	}
}

/* Canada-only store: a one-option Country select asks a question with no
   answer. The row keeps posting (input intact) — only the pixels go. The
   class is set in inc-checkout.php and only while exactly one country is
   allowed, so this rule is inert the day a second market opens. */
/* !important: WC's address-i18n JS writes display:block INLINE on locale
   application — the one thing a stylesheet cannot outrank politely. */
body.kc-rd #billing_country_field.kc-single-country { display: none !important; }

/* ===== 20-buttons.css ===== */
/* ==========================================================================
   20 · BUTTON SYSTEM — one vocabulary for every control on the site
   --------------------------------------------------------------------------
   SURFACE
     Cross-surface. Owns the *shape, state and variant* of every control.
     It does not own layout or copy — a surface partial that needs a button
     somewhere new adds .kc-btn-primary / -secondary / -tertiary / -destructive
     / -compact rather than re-inventing a fill.

   WHAT IS SHIPPED vs WHAT IS VOCABULARY — read this before believing a
   .kc-btn-* rule below is doing anything on the live site. Grepping every PHP
   and JS file under server-files/ on 2026-08-15, exactly one .kc-btn-* class
   is emitted anywhere:
       .kc-btn-secondary   inc-cards.php:104 ("View product", OOS card)
                           inc-account.php:69 ("Choose weight")
   Also real: .kc-loading (kc-redesign.js:163) and .kc-card-add.
   NOT emitted by anything today — .kc-btn, .kc-btn-primary, .kc-btn-tertiary,
   .kc-btn-destructive, .kc-btn-compact, and the [aria-busy="true"] hooks in
   §20.7. They match zero elements and are kept ON PURPOSE as the vocabulary
   surfaces 30+ opt into, so a new control gets a variant instead of a fresh
   fill. Each such block is tagged UNSHIPPED in its section header. Deleting
   them is fine once it is clear no surface wants them; what is NOT fine is
   reporting them as applied work.

   DOM VERIFIED on staging 2026-08-15 (curl + basic auth, real markup, no
   guessing). Every selector below was taken from one of these responses:
     /                                        200  438,584 B
     /shop/                                   200  318,874 B
     /product-category/weed/                  200  327,103 B
     /shop/weed/aaaa/blackberry-pie-delivery-gta/   200  303,893 B
     /cart/        (cart populated via session cookie)  200  219,805 B
     /checkout/    (same session)                       200  282,233 B
     /my-account/                             200  202,947 B
   Every element in the site that carries the bare `.button` token, counted
   across those pages:
       16  a.button.border                       (Create Account / Log in /
                                                  Continue Shopping / Update
                                                  cart / Apply coupon)
        3  a.button.product_type_simple.add_to_cart_button.ajax_add_to_cart
        1  a.button.wc-backward                  (Return to shop, empty cart)
       11  a.kc-card-add.button.kc-btn-secondary  (View product — OOS card)
       66  button.kc-card-add.button              (Add to cart — card)
        1  button.kc-sticky-btn.button            (mobile sticky ATC)
        1  button.nm-variable-add-to-cart-button.single_add_to_cart_button.button.alt
       17  button.woocommerce-Button.button       (Sign In / Register / Reset)
        5  button.close-button.button             (Cancel, lost-password modal)
   plus, not carrying `.button`:
       input#place_order.button.alt · a.checkout-button.button.alt.wc-forward
       input.button.alt[name=woocommerce_checkout_update_totals]  (in <noscript>)
       button.button[name=apply_coupon] · button.button[name=login] (checkout)
       button.button.border[name=update_cart] · input#nm-apply-coupon-btn.button.border
       a.nm-infload-btn ("Load More") · input.jwa-submit ("Check")
       input#submit.submit (review/comment) · input#button-expert_plag
       a.custom-realted-products-button ("See All")
       input.wpcf7-submit ("Subscribe")
       a.nm_btn.nm_btn_filled > span.nm_btn_title + span.nm_btn_bg  (promo CTAs;
         the paintable surface is the ABSOLUTELY POSITIONED .nm_btn_bg span,
         not the anchor — verified in savoy-child/assets/css/elements.css:1576)
       a.remove (cart line remove) · .woocommerce-remove-coupon

   COMPETING SELECTORS BEATEN (all read from the live stylesheets, not recalled)
     savoy/style.css:270   `.widget form>button, input[type=submit], .button`
                           (0,1,1) — 16px/16px line-height, 9px 15px 10px
                           padding => 35px tall. Under the 48px floor.
     savoy/style.css:288   `.button:hover  → opacity:.82` (0,1,0) — the theme's
                           only hover affordance is a fade; ours is the shadow.
     savoy/style.css:291   `input[type=submit]:active, :focus, .button:active,
                           .button:focus  → outline:none` (0,2,0)
     savoy-child/assets/css/main.css:186  `*  → outline: 0 none` (0,0,0)
                           => between these two the site currently has NO focus
                           ring anywhere. Both are beaten below at (0,3,1)+.
     savoy customizer INLINE <style> — emitted AFTER kc-redesign.css in <head>
                           (verified by byte offset: link at 55,599, block at
                           65,150). `.button,input[type=submit] → color:#fff;
                           background-color:#282828` (0,1,1) and
                           `.button.border → ...` (0,2,0). Source order can't
                           save us here, so every rule below is >= (0,2,1).
     savoy-child/style.css:1818  desktop PDP CTAs: `linear-gradient(#FFD900)
                           !important`, 3px border, 6px 6px shadow, 10px radius,
                           24px Gazella  (0,4,0)
     savoy-child/style.css:2288/2666  cart + checkout CTAs: #FFD900, 10px radius
                           `!important`  (0,5,1) / (1,1,1)
     savoy-child/style.css:4084  @media (max-width:768px) — the whole mobile
                           repaint. #6FCF97 green on: #place_order:hover (4128),
                           .nm-infload-btn (4184), .checkout-button + #place_order
                           (4202), .mobile-main-home/.new-arrivals-mobile
                           .nm_btn_bg + .jwa-submit + review #submit +
                           .nm-variable-add-to-cart + .custom-realted-products-
                           button (4326), .king-deals-products/.how-to-order-home
                           .nm_btn_bg (4453), .subscribe .wpcf7-submit (4606).
     kc-redesign.css §2 / §19.7  already unified SOME of the above on amber.
                           This file does not contradict that colour — it keeps
                           --action as the one primary fill and extends the same
                           spec to the CTAs §19.7 missed (simple-product ATC,
                           Load More, review submit, newsletter, promo CTAs).
     kc-redesign.css §20.3  reverted controls from Gazella to the body face.
                           NOTHING here re-applies a display face to a control.

   Everything is scoped `body.kc-rd` and dies with the kill switch.
   ========================================================================== */


/* --------------------------------------------------------------------------
   20.1 · CONTROL BASE — shape, type, touch target. No colour lives here.
   Colour is assigned per variant below, so a control can never be "half
   painted" by inheriting a fill it did not ask for.
   .nm_btn is deliberately excluded: its fill is a separate absolutely
   positioned span and its layout is load-bearing on the homepage hero, so it
   gets a minimal retheme in §20.9 instead of the full base.
   -------------------------------------------------------------------------- */

body.kc-rd .button,
body.kc-rd input[type=submit],
body.kc-rd .kc-btn,
body.kc-rd .nm-infload-btn,
body.kc-rd .jwa-submit,
body.kc-rd .custom-realted-products-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--kc-space-2);
	min-height: var(--btn-min-height);
	max-width: 100%;
	/* Vertical is on the scale. The two horizontal bounds are intentionally
	   NOT: they are the ends of a fluid curve, not a spacing step. 26px is the
	   kit's own control padding (brand-kit.html:193, `padding: 12px 26px`), and
	   14px is the narrow-viewport floor — snapping it up to --kc-space-4 (16px)
	   would widen every control on the site by 4px at the 375px design target
	   §20.10 is written around, which is a layout change traded for scale
	   tidiness. Left as measured values on purpose; do not "fix" to a token. */
	padding: var(--kc-space-3) clamp(14px, 4vw, 26px);
	font-family: var(--kc-font-body);
	font-size: 16px;
	font-weight: 700;
	line-height: 1.25;
	letter-spacing: .01em;
	text-align: center;
	text-transform: none;
	text-decoration: none;
	border-radius: var(--kc-radius-md);
	cursor: pointer;
	/* iOS Safari holds ~350ms on a zoomable viewport watching for double-tap;
	   `manipulation` opts these controls out and suppresses double-tap zoom on
	   a doubled Add-to-cart tap. Pan and pinch-zoom remain intact. */
	touch-action: manipulation;
	opacity: 1;
	transition: transform .12s ease, box-shadow .12s ease,
	            background-color .12s ease, color .12s ease, border-color .12s ease;
}

/* Savoy's only hover state is a fade (style.css:288). Ours is the shadow, so
   the fade has to go or every hover reads as "disabling". */
body.kc-rd .button:hover,
body.kc-rd input[type=submit]:hover,
body.kc-rd .nm-infload-btn:hover,
body.kc-rd .jwa-submit:hover,
body.kc-rd .custom-realted-products-button:hover { opacity: 1; }

/* Focus ring. Restores what `*  → outline:0 none` and `.button:focus
    → outline:none` removed site-wide. Never delete this block — it is the only
   feedback a keyboard user gets, and hover gives them nothing. */
body.kc-rd .button:focus-visible,
body.kc-rd input[type=submit]:focus-visible,
body.kc-rd .kc-btn:focus-visible,
body.kc-rd .nm-infload-btn:focus-visible,
body.kc-rd .jwa-submit:focus-visible,
body.kc-rd .custom-realted-products-button:focus-visible,
body.kc-rd a.nm_btn:focus-visible,
body.kc-rd a.remove:focus-visible,
body.kc-rd .woocommerce-remove-coupon:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}


/* --------------------------------------------------------------------------
   20.2 · PRIMARY — amber fill, ink border, 3px hard shadow.
   ONE per view. The list is short on purpose: it is exactly the commit action
   of each surface. Anything that merely navigates is secondary (§20.3).
     PDP        .single_add_to_cart_button  (+ .kc-sticky-btn mirror)
     card       .kc-card-add
     cart       .checkout-button
     checkout   #place_order
     side cart  .button.checkout
     account    .woocommerce-Button (the form's submit — login/register/reset)
   -------------------------------------------------------------------------- */

body.kc-rd .kc-btn-primary,
body.kc-rd .button.alt,
body.kc-rd .single_add_to_cart_button,
body.kc-rd .checkout-button,
body.kc-rd .kc-sticky-btn,
body.kc-rd .kc-card-add:not(.kc-btn-secondary),
body.kc-rd .woocommerce-Button.button,
body.kc-rd .nm-cart-panel .button.checkout,
body.kc-rd .woocommerce-mini-cart__buttons .button.checkout {
	/* 2026-08-27: 主行动按钮的文字永不折行。PDP 的买区是一个 flex 行,任何人往里插
	   一个兄弟元素(2026-08-27 是积分的「Earn N points」行)都会把 flex:1 1 0% 的按钮
	   挤到只剩内容最小宽度 —— 折行的 "Add / to / cart" 就是这么来的。nowrap 让最小内容
	   宽度 = 整句宽度,按钮宁可把别人挤走也不会自己折。 */
	white-space: nowrap;
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border: 2px solid var(--btn-primary-border);
	box-shadow: var(--btn-primary-shadow);
	/* UA tap highlight would wash the amber fill on every touch; :active below
	   is the deliberate press state. NOT set at §20.1 — .jwa-submit, See All and
	   the review #submit have no :active rule, so the UA highlight is their only
	   press feedback and must stay. */
	-webkit-tap-highlight-color: transparent;
}

/* WooCommerce's no-JS "Update totals" also carries .button.alt, which would
   put a second amber CTA above Place order for a no-JS session. It sits in
   <noscript> (verified in the checkout response), so nobody with JS sees it —
   demoted anyway, one selector is cheaper than the assumption. */
body.kc-rd input.button.alt[name="woocommerce_checkout_update_totals"] {
	background: transparent;
	color: var(--text);
	border: 2px solid var(--text);
	box-shadow: none;
	font-weight: 600;
}

/* Hover: the kit's state table moves 1px and drops the shadow to 2px. Press
   moves the full 3px so the button seats INTO its own shadow. kc-redesign.css
   §2/§19.7 put the 3px press value on :hover — corrected here (same
   specificity, later file, so it wins on order).
   Gated on (hover:hover): 86.5% of traffic is touch, where a plain :hover
   sticks after the tap and leaves the CTA looking permanently pressed.
   The last four selectors in the list are the heavy forms: kc-redesign.css §2
   paints card buttons at (0,4,3) and §19.7 the PDP/cart CTAs at (0,6,1), both
   with the 3px press value on :hover. Matching their specificity from a later
   file is what actually lands the 1px step.
   #place_order is listed EXPLICITLY in both blocks and must stay that way.
   kc-redesign.css:218 owns `body.kc-rd #place_order:hover` at (1,2,1) with the
   press value on it. `.button.alt:hover` does match the element (it ships as
   input#place_order.button.alt) but scores (0,4,1) — an ID beats any number of
   classes, so source order never gets a say. Only an ID-carrying selector of
   ours reaches it; `body.kc-rd.woocommerce-checkout #place_order:hover` is
   (1,3,1). Two rules are being beaten, not one — savoy-child/style.css:4152
   also throws it `translate(6px, 6px); box-shadow: none` at (1,1,1) inside
   @media (max-width:768px). Without these two lines the last control before
   money changes hands loses its shadow and drops 3px on desktop hover (6px
   under 768px), and on touch the tap latches :hover so Place order sits
   permanently seated and off-position afterwards. 86.5% of traffic is mobile.
   Covers all four quadrants: hover/no-hover x above/below 768px. */
@media (hover: hover) {
	body.kc-rd .kc-btn-primary:hover,
	body.kc-rd .button.alt:hover,
	body.kc-rd .single_add_to_cart_button:hover,
	body.kc-rd .checkout-button:hover,
	body.kc-rd .kc-sticky-btn:hover,
	body.kc-rd .kc-card-add:hover,
	body.kc-rd .woocommerce-Button.button:hover,
	body.kc-rd .nm-cart-panel .button.checkout:hover,
	body.kc-rd .woocommerce-mini-cart__buttons .button.checkout:hover,
	body.kc-rd ul.nm-products li.product .button:hover,
	body.kc-rd .nm-simple-add-to-cart-button.single_add_to_cart_button.button.alt:hover,
	body.kc-rd .nm-variable-add-to-cart-button.single_add_to_cart_button.button.alt:hover,
	body.kc-rd.woocommerce-cart .checkout-button.button.alt.wc-forward:hover,
	body.kc-rd.woocommerce-checkout #place_order:hover {
		transform: translate(1px, 1px);
		/* WANTED: --btn-primary-shadow-hover. This is the 2px sibling of
		   --btn-primary-shadow and belongs next to it in kc-redesign.css §0,
		   but a partial cannot declare :root (build_kc_css.py --check rejects
		   it), so the value is inlined here and in no other file. If §0 gains
		   the token, this is the single call site to rewire. */
		box-shadow: 2px 2px 0 var(--text);
		background: var(--btn-primary-bg);
		color: var(--btn-primary-text);
	}
}
/* Touch devices: neutralise the inherited sticky :hover from kc-redesign.css
   §2/§19.7, which would otherwise leave a tapped CTA seated forever. */
@media (hover: none) {
	body.kc-rd .single_add_to_cart_button:hover,
	body.kc-rd .checkout-button:hover,
	body.kc-rd .kc-sticky-btn:hover,
	body.kc-rd .kc-card-add:hover,
	body.kc-rd .nm-cart-panel .button.checkout:hover,
	body.kc-rd ul.nm-products li.product .button:hover,
	body.kc-rd .nm-simple-add-to-cart-button.single_add_to_cart_button.button.alt:hover,
	body.kc-rd .nm-variable-add-to-cart-button.single_add_to_cart_button.button.alt:hover,
	body.kc-rd.woocommerce-cart .checkout-button.button.alt.wc-forward:hover,
	body.kc-rd.woocommerce-checkout #place_order:hover {
		transform: none;
		box-shadow: var(--btn-primary-shadow);
	}
}

body.kc-rd .kc-btn-primary:active,
body.kc-rd .button.alt:active,
body.kc-rd .single_add_to_cart_button:active,
body.kc-rd .checkout-button:active,
body.kc-rd .kc-sticky-btn:active,
body.kc-rd .kc-card-add:active,
body.kc-rd .woocommerce-Button.button:active,
body.kc-rd .nm-cart-panel .button.checkout:active,
body.kc-rd .woocommerce-mini-cart__buttons .button.checkout:active {
	transform: translate(3px, 3px);   /* seats the 3px shadow exactly */
	box-shadow: 0 0 0 var(--text);
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
}

/* --- 20.2a The four CTAs the child theme still owns with !important -------
   Each of these is beaten on specificity; !important only where the losing
   rule itself carries !important (radius, and the two backgrounds the mobile
   media query paints green). Specificity is written next to each block. */

/* PDP · simple products. §19.7 covers .nm-variable-* but never listed
   .nm-simple-*, so a simple product's ATC is still #FFD900/24px/6px-shadow
   from savoy-child/style.css:1820 (0,4,0)+!important. Ours is (0,5,1). */
body.kc-rd .nm-simple-add-to-cart-button.single_add_to_cart_button.button.alt,
body.kc-rd .nm-variable-add-to-cart-button.single_add_to_cart_button.button.alt {
	display: inline-flex !important;   /* beats child :2217 display:inline-block */
	background: var(--btn-primary-bg) !important;
	color: var(--btn-primary-text) !important;
	border: 2px solid var(--btn-primary-border) !important;
	border-radius: var(--kc-radius-md) !important;  /* beats :1827 10px !important */
	box-shadow: var(--btn-primary-shadow);
	font-size: 16px;
	padding: var(--kc-space-3) var(--kc-space-5);
	margin-bottom: 0 !important;       /* beats :1828 margin-bottom:6px !important */
}

/* Cart · Proceed to Checkout. Child (0,5,1) desktop #FFD900 + 10px !important,
   mobile #6FCF97 !important. Ours (0,6,1), later file. */
body.kc-rd.woocommerce-cart .checkout-button.button.alt.wc-forward {
	background: var(--btn-primary-bg) !important;
	color: var(--btn-primary-text) !important;
	border: 2px solid var(--btn-primary-border) !important;
	border-radius: var(--kc-radius-md) !important;
	box-shadow: var(--btn-primary-shadow);
	padding: var(--kc-space-3) var(--kc-space-5);
	font-size: 16px;
}

/* Checkout · Place order. Child carries an ID, (1,1,1)+!important; §19.7 is
   (1,2,1). Ours matches §19.7 and lands later, so it wins ties; radius needs
   !important because :2668 has it. */
body.kc-rd.woocommerce-checkout #place_order {
	background: var(--btn-primary-bg) !important;
	color: var(--btn-primary-text) !important;
	border: 2px solid var(--btn-primary-border) !important;
	border-radius: var(--kc-radius-md) !important;
	box-shadow: var(--btn-primary-shadow);
	padding: var(--kc-space-3) var(--kc-space-5);
	font-size: 16px;
	min-height: var(--btn-min-height);
}
body.kc-rd.woocommerce-checkout #place_order:active {
	transform: translate(3px, 3px);
	box-shadow: 0 0 0 var(--text);
}
/* The child theme paints #place_order:hover green at (1,1,1)+!important inside
   the mobile query (style.css:4128) and #F9C43C at (1,1,1)+!important on
   desktop (:2682). Ours is (1,3,1)+!important. Colour only, and deliberately
   OUTSIDE any hover-media query — the child's repaint has to be beaten on
   touch as well as on desktop. The motion half of this state lives in the two
   §20.2 media blocks, which carry the same selector. */
body.kc-rd.woocommerce-checkout #place_order:hover {
	background: var(--btn-primary-bg) !important;
	color: var(--btn-primary-text) !important;
}


/* --------------------------------------------------------------------------
   20.3 · SECONDARY — ink outline on the page ground, no fill, no shadow.
   Quieter, NOT a smaller primary: same height, same type, less ink.
   This is where every "navigate somewhere else" control lands.
   `[name="login"]:not(.woocommerce-Button)` is the collapsed checkout login
   toggle; the :not() keeps the account page's own Sign In / Register — which
   ARE the primary of their form — out of this list.
   -------------------------------------------------------------------------- */

body.kc-rd .kc-btn-secondary,
body.kc-rd .button.border,
body.kc-rd .close-button.button,
body.kc-rd .button[name="apply_coupon"],
body.kc-rd .button[name="update_cart"],
body.kc-rd .button[name="login"]:not(.woocommerce-Button),
body.kc-rd .woocommerce-mini-cart__buttons .button:not(.checkout),
body.kc-rd .nm-infload-controls.button-mode a.nm-infload-btn {
	background: transparent;
	color: var(--text);
	border: 2px solid var(--text);
	box-shadow: none;
	font-weight: 600;
	border-radius: var(--kc-radius-md);
	/* Same as §20.2: this block ships its own :active press state, so the UA
	   tap highlight is redundant here. Kept for the §20.3 CTAs below that have
	   no :active rule (.jwa-submit / See All / review #submit). */
	-webkit-tap-highlight-color: transparent;
}
/* "View product" on an out-of-stock card. kc-redesign.css §2 paints every
   card button amber at (0,4,3)+!important, which currently out-ranks its own
   `.kc-btn-secondary` rule at (0,2,1)+!important — so the OOS card ships a
   full-strength amber CTA for a product nobody can buy. (0,5,3) settles it. */
body.kc-rd ul.nm-products li.product .button.kc-btn-secondary {
	background: transparent !important;
	color: var(--text) !important;
	border: 2px solid var(--text) !important;
	box-shadow: none !important;
}
body.kc-rd .kc-btn-secondary:hover,
body.kc-rd .button.border:hover,
body.kc-rd .close-button.button:hover,
body.kc-rd .button[name="apply_coupon"]:hover,
body.kc-rd .button[name="update_cart"]:hover,
body.kc-rd .button[name="login"]:not(.woocommerce-Button):hover,
body.kc-rd ul.nm-products li.product .button.kc-btn-secondary:hover,
body.kc-rd .nm-infload-controls.button-mode a.nm-infload-btn:hover {
	background: var(--surface-sunken);
	color: var(--text);
	border-color: var(--text);
	transform: none;
	box-shadow: none;
}
body.kc-rd .kc-btn-secondary:active,
body.kc-rd .button.border:active,
body.kc-rd .close-button.button:active,
body.kc-rd .nm-infload-controls.button-mode a.nm-infload-btn:active {
	background: var(--surface-sunken);
	transform: translate(1px, 1px);
}
/* Same battle as above, one state up: §2's card hover is (0,5,3)+!important
   on background and sets the 3px press transform. A secondary has no shadow
   to seat into, so it must not travel. */
body.kc-rd ul.nm-products li.product .button.kc-btn-secondary:hover,
body.kc-rd ul.nm-products li.product .button.kc-btn-secondary:active {
	background: var(--surface-sunken) !important;
	color: var(--text) !important;
	transform: none;
	box-shadow: none !important;
}

/* Load More. Desktop (0,3,0) #FFD900 + Gazella 24px + 6px shadow; mobile
   (0,4,1) #6FCF97. Ours is (0,4,2) via the element+class on the anchor, so no
   !important is needed in either breakpoint. Load More paginates a list — it
   is navigation, not a commit, so it is secondary, not a second amber CTA. */
body.kc-rd .nm-infload-controls.button-mode a.nm-infload-btn {
	font-size: 16px;
	margin-bottom: 0;
	text-indent: 0;
}

/* PDP · "Check" delivery-area probe. §19.7 painted it amber, which put a
   second primary on the PDP next to Add to cart. Same selector shape as §19.7
   (0,3,1)+!important, later file. */
body.kc-rd.single-product .jwa-submit {
	background: transparent !important;
	color: var(--text) !important;
	border: 2px solid var(--text) !important;
	border-radius: var(--kc-radius-md) !important;
	box-shadow: none;
	font-size: 16px;
}

/* PDP · "See All" under related products — a link to /shop, not a commit.
   §19.7 painted it amber at (0,2,1)+!important; ours is (0,3,2)+!important. */
body.kc-rd.single-product a.custom-realted-products-button {
	background: transparent !important;
	color: var(--text) !important;
	border: 2px solid var(--text) !important;
	border-radius: var(--kc-radius-md) !important;
	box-shadow: none;
	font-size: 16px;
}

/* PDP · review submit. Never touched by kc-redesign: still #FFD900 on desktop
   and #6FCF97 on mobile, both (1,2,1)+!important. Ours is (1,3,1). */
body.kc-rd.single-product .custom-product-tab-reviews.tab #submit {
	background: transparent !important;
	color: var(--text) !important;
	border: 2px solid var(--text) !important;
	border-radius: var(--kc-radius-md) !important;
	box-shadow: none !important;
	font-size: 16px !important;
	min-height: var(--btn-min-height);
}

/* MANDATORY, same reason as §20.9: savoy-child/style.css:1836 groups these
   three under `transform: translate(6px, 6px); box-shadow: none` on :hover.
   Stripping the fill above does not touch that transform, so without this
   block the Check probe, See All and the review Submit each throw themselves
   6px down-and-right with nothing underneath to seat into — and on touch the
   tap latches :hover, parking the control 6px off-position over whatever sits
   below it. Load More has the identical 6px travel (:1619) and is already
   reset at the tail of the secondary hover list; these three were missed.
   Specificity vs the child rule:
     .jwa-submit                  ours (0,4,1) vs theirs (0,3,1)
     a.custom-realted-products-*  ours (0,4,2) vs theirs (0,3,1)
     #submit                      ours (1,5,1) vs theirs (1,4,1)
   Colour is left to the three blocks above — their !important fills already
   hold through :hover, so re-stating them here would only add noise. */
body.kc-rd.single-product .jwa-submit:hover,
body.kc-rd.single-product a.custom-realted-products-button:hover,
body.kc-rd.single-product .custom-product-tab-reviews.tab #submit:hover {
	transform: none;
	box-shadow: none;
}


/* --------------------------------------------------------------------------
   20.4 · TERTIARY / TEXT — no chrome at all. For the escape hatch that must
   exist but must not compete: Continue shopping, Return to shop.
   `.kc-btn-tertiary` is UNSHIPPED — no DOM carries it yet; added for surfaces
   30+ to opt into. The live work in this section is .button.wc-backward and
   a.nm-cart-continue-button, both of which exist on the cart.
   -------------------------------------------------------------------------- */

body.kc-rd .kc-btn-tertiary,
body.kc-rd .button.wc-backward,
body.kc-rd a.nm-cart-continue-button {
	background: transparent;
	color: var(--text);
	border: 0;
	box-shadow: none;
	font-weight: 600;
	padding: var(--kc-space-3) var(--kc-space-2);
	text-decoration: underline;
	text-underline-offset: 4px;
	text-decoration-thickness: 1px;
}
body.kc-rd .kc-btn-tertiary:hover,
body.kc-rd .button.wc-backward:hover,
body.kc-rd a.nm-cart-continue-button:hover {
	color: var(--trust);
	background: transparent;
	text-decoration-thickness: 2px;
}
/* savoy-child/style.css:2327 washes Continue Shopping out with
   `filter: contrast(0.3)` at (0,2,1). The kit's rule is that nothing a
   customer has to decide about is greyed; ours is (0,3,2). */
body.kc-rd.woocommerce-cart a.nm-cart-continue-button { filter: none; }


/* --------------------------------------------------------------------------
   20.5 · DESTRUCTIVE — tomato, and quiet. Per the kit, --urgent appears once
   per screen; a filled tomato button would fight the amber CTA, so destructive
   is the tertiary shape in the urgent colour.
   `.kc-btn-destructive` is UNSHIPPED — no DOM carries it yet; added for
   surfaces 30+ to opt into. Everything this section actually delivers today is
   the a.remove / .woocommerce-remove-coupon colour and tap target below.
   a.remove ships as a 16x16 icon (savoy/assets/css/shop.css:4541) inside an
   absolutely positioned cell (savoy-child/style.css:2524) — growing its box
   would push into the product title, so the tap target is widened with an
   invisible ::after overlay instead. 16px + 14px on each side = 44px, zero
   layout change — which now holds literally, see the offset reset below.
   -------------------------------------------------------------------------- */

body.kc-rd .kc-btn-destructive {
	background: transparent;
	color: var(--urgent);
	border: 2px solid var(--urgent);
	box-shadow: none;
	font-weight: 600;
}
body.kc-rd .kc-btn-destructive:hover { background: transparent; color: var(--urgent); border-color: var(--urgent); }
body.kc-rd .kc-btn-destructive:active { transform: translate(1px, 1px); }
body.kc-rd .kc-btn-destructive:focus-visible { outline: 3px solid var(--trust); outline-offset: 3px; }

body.kc-rd a.remove,
body.kc-rd .woocommerce-remove-coupon {
	position: relative;
	color: var(--urgent);
}
/* ...but `position: relative` is not free. savoy/assets/css/shop.css:4939 parks
   `top: 24px; right: 5px` on `.shop_table.cart .product-remove a` inside
   `@media all and (max-width: 400px)`. Those offsets are inert today because
   nothing positions that anchor (shop.css:4541 sets only display/size/colour;
   savoy-child/assets/css/main.css:2737 only colour/transition; and
   savoy-child/style.css:2524 positions the CELL, not the link). The rule above
   switches them on, which would drag the icon 24px down and 5px left at every
   width <= 400px — i.e. across the whole 375px design target — into a cell the
   child theme has already pulled out of flow over the row content. Neutralised
   here rather than by dropping `position`, because the ::after tap target has
   to resolve against the anchor: hanging it off td.product-remove instead
   would put the overlay on the cell, where a tap does not reach the link.
   Theirs is (0,3,1); this is (0,5,2), so it lands in both breakpoints.
   Verify at 375px AND at 400px — the offending query is max-width, not min.
   The side-cart remove is untouched by all of this: savoy/style.css:2431
   already holds it at `position: absolute` with (1,2,0), which outranks the
   (0,2,2) rule above. */
body.kc-rd .shop_table.cart .product-remove a.remove {
	top: auto;
	right: auto;
}
body.kc-rd a.remove::after,
body.kc-rd .woocommerce-remove-coupon::after {
	content: "";
	position: absolute;
	/* Derived, not arbitrary, and deliberately not a --kc-space-* step: the
	   icon is 16px and the floor is 44px, so the halo is (44-16)/2 = 14 on each
	   side. Change the icon size or the floor and this recomputes; it is not a
	   spacing choice to be harmonised. */
	inset: -14px;
}
body.kc-rd a.remove:hover,
body.kc-rd .woocommerce-remove-coupon:hover { color: var(--urgent); opacity: 1; }


/* --------------------------------------------------------------------------
   20.6 · DISABLED — flat, borderless, unmistakably inert.
   Deliberate deviation from the kit demo, which pairs --border with
   --text-faint: that lands at ~2.9:1 and "Sold out" is information the
   customer needs to read, not decoration. --text-muted keeps the same
   disabled read at ~4.9:1.
   -------------------------------------------------------------------------- */

body.kc-rd .button[disabled],
body.kc-rd .button.disabled,
body.kc-rd input[type=submit][disabled],
body.kc-rd .kc-btn[disabled],
body.kc-rd .kc-btn.disabled,
body.kc-rd .kc-btn-primary[disabled],
body.kc-rd .nm-simple-add-to-cart-button.single_add_to_cart_button.button.alt.disabled,
body.kc-rd .nm-variable-add-to-cart-button.single_add_to_cart_button.button.alt.disabled {
	background: var(--border) !important;
	color: var(--text-muted) !important;
	border: 2px solid var(--border) !important;
	box-shadow: none !important;
	transform: none !important;
	filter: none !important;      /* beats child :1847 filter: contrast(0.5) */
	opacity: 1;
	cursor: not-allowed;
	pointer-events: auto;         /* keep it focusable so the label is readable */
}
body.kc-rd .button[disabled]:hover,
body.kc-rd .button.disabled:hover,
body.kc-rd input[type=submit][disabled]:hover,
body.kc-rd .nm-variable-add-to-cart-button.single_add_to_cart_button.button.alt.disabled:hover {
	transform: none !important;
	box-shadow: none !important;
	background: var(--border) !important;
}


/* --------------------------------------------------------------------------
   20.7 · BUSY — the request is in flight. NOT disabled: the money colour
   stays, because going grey mid-add reads as failure. The button seats into
   its shadow (it is "held down") and grows a spinner.
   Hooks: WooCommerce/Savoy add `.loading`; kc-redesign.js adds `.kc-loading`
   (assets/kc-redesign.js:163) and swaps the label to "Adding…" — those two are
   live. The `[aria-busy="true"]` selectors are UNSHIPPED: nothing sets that
   attribute today, they are here so a surface can adopt the accessible hook
   without re-deriving the visual.
   -------------------------------------------------------------------------- */

body.kc-rd .button.loading,
body.kc-rd .button.kc-loading,
body.kc-rd .kc-btn.kc-loading,
body.kc-rd .single_add_to_cart_button.loading,
body.kc-rd [aria-busy="true"].button,
body.kc-rd [aria-busy="true"].kc-btn {
	cursor: progress;
	pointer-events: none;
	transform: translate(3px, 3px);
	box-shadow: 0 0 0 var(--text);
}
/* Spinner only on real elements — a replaced <input type=submit> renders no
   pseudo-element, so #place_order relies on the seated state alone. */
body.kc-rd button.button.loading::after,
body.kc-rd a.button.loading::after,
body.kc-rd button.kc-btn.kc-loading::after,
body.kc-rd button[aria-busy="true"].button::after {
	content: "";
	width: 14px;
	height: 14px;
	flex: 0 0 auto;
	border: 2px solid currentColor;
	border-right-color: transparent;
	border-radius: 50%;
	animation: kc-btn-spin .7s linear infinite;
}
@keyframes kc-btn-spin { to { transform: rotate(360deg); } }


/* --------------------------------------------------------------------------
   20.8 · SIZES — default and compact. Compact trims type and side padding
   only; the 48px floor is never traded away, because the thing being made
   compact is the ink, not the target.
   `.kc-btn-compact` is UNSHIPPED — no DOM carries it yet; added for surfaces
   30+ to opt into. #nm-apply-coupon-btn is the one real element here.
   -------------------------------------------------------------------------- */

/* .kc-card-add is deliberately NOT in this list. kc-redesign.css:764-765 holds
   it at `padding: 10px 12px !important; font-size: 13px !important`, and
   !important beats a non-important declaration at any specificity — so naming
   it here would have bought nothing while reading as though the 14px applied.
   The card grid owns its own density; only what this file can actually deliver
   is claimed. */
body.kc-rd .kc-btn-compact,
body.kc-rd #nm-apply-coupon-btn.button {
	min-height: var(--btn-min-height);
	padding-left: var(--kc-space-3);
	padding-right: var(--kc-space-3);
	font-size: 14px;
	letter-spacing: .02em;
}
/* What this file DOES enforce on the card button: the touch floor, plus the
   tracking (nothing upstream sets letter-spacing on it).
   OPEN DECISION for the cards owner — not this file's to make: the card label
   ships at 13px, and "Add to cart" / "View product" is the most numerous
   control on the site (66 on one shop page) and the text a customer reads to
   decide. Either raise kc-redesign.css:765 to 14px or record 13px as accepted;
   it cannot be fixed from here, because the !important lives in that file. */
body.kc-rd .kc-card-add {
	min-height: var(--btn-min-height);
	letter-spacing: .02em;
}


/* --------------------------------------------------------------------------
   20.9 · SAVOY PROMO BUTTONS (.nm_btn) — homepage / brand surface.
   Three problems in one component: a THIRD yellow (#FFD900, neither --action
   nor the green), a 6px shadow with a 6px press translate, and a mobile
   repaint to #6FCF97. Retheme only — the anchor's layout, padding and the
   hero's arrow pseudo-element are left exactly as they are.
   Hero = primary (it is the one CTA of the brand surface). The section-level
   "See All" / "Go Shopping" links are navigation, so they are secondary —
   otherwise the homepage carries six amber CTAs and Add to cart stops being
   the loudest thing on the page.
   -------------------------------------------------------------------------- */

/* §20.1 deliberately excludes .nm_btn, so it needs its own touch-action —
   this is the homepage hero CTA. */
body.kc-rd a.nm_btn { min-height: var(--btn-min-height); touch-action: manipulation; }

/* Primary: desktop hero + mobile hero */
body.kc-rd .bannerheader-right-button .nm_btn .nm_btn_bg,
body.kc-rd .mobile-main-home .nm_btn .nm_btn_bg {
	background: var(--btn-primary-bg) !important;   /* :4319 uses !important */
	border: 2px solid var(--btn-primary-border);
	border-radius: var(--kc-radius-md);
	box-shadow: var(--btn-primary-shadow);
	opacity: 1;
}
/* Secondary: section "See All" / "Go Shopping" */
body.kc-rd .new-arrivals-class .nm_btn .nm_btn_bg,
body.kc-rd .new-arrivals-mobile .nm_btn .nm_btn_bg,
body.kc-rd .king-deals-products .nm_btn .nm_btn_bg,
body.kc-rd .how-to-order-home .nm_btn .nm_btn_bg {
	background: transparent !important;
	border: 2px solid var(--text);
	border-radius: var(--kc-radius-md);
	box-shadow: none;
	opacity: 1;
}
body.kc-rd .bannerheader-right-button .nm_btn_title,
body.kc-rd .mobile-main-home .nm_btn_title,
body.kc-rd .new-arrivals-class .nm_btn_title,
body.kc-rd .new-arrivals-mobile .nm_btn_title,
body.kc-rd .king-deals-products .nm_btn_title,
body.kc-rd .how-to-order-home .nm_btn_title {
	color: var(--text);
	font-family: var(--kc-font-body);   /* re-states §20.3: never the display face */
	font-weight: 700;
	font-size: 16px;
}
/* The theme presses these 6px to meet a 6px shadow (:637, elements.css:1630
   fades .nm_btn_bg to .73). With a 3px shadow the travel must be 3px or the
   button slides out from under it. Ours is (0,5,2) vs their (0,4,1)/(0,3,0). */
body.kc-rd .bannerheader-right-button a.nm_btn.nm_btn_filled:active,
body.kc-rd .mobile-main-home a.nm_btn.nm_btn_filled:active {
	transform: translate(3px, 3px);
}
body.kc-rd .bannerheader-right-button a.nm_btn.nm_btn_filled:active > .nm_btn_bg,
body.kc-rd .mobile-main-home a.nm_btn.nm_btn_filled:active > .nm_btn_bg {
	box-shadow: none;
	opacity: 1;
}
body.kc-rd .bannerheader-right-button a.nm_btn.nm_btn_filled:hover,
body.kc-rd .mobile-main-home a.nm_btn.nm_btn_filled:hover,
body.kc-rd .new-arrivals-class a.nm_btn.nm_btn_filled:hover,
body.kc-rd .king-deals-products a.nm_btn.nm_btn_filled:hover,
body.kc-rd .how-to-order-home a.nm_btn.nm_btn_filled:hover { transform: none; }
body.kc-rd .nm_btn.nm_btn_filled:hover > .nm_btn_bg { opacity: 1; }

/* Newsletter "Subscribe". #FFD900 desktop (:953, 0,4,0) / #6FCF97 mobile
   (:4599, 0,4,0). Ours (0,5,1). It is the commit action of that block, so it
   stays primary — the block is its own view. */
body.kc-rd .subscribe .wpcf7-form-control.has-spinner.wpcf7-submit {
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border: 2px solid var(--btn-primary-border);
	border-radius: var(--kc-radius-md);
	box-shadow: var(--btn-primary-shadow);
	font-family: var(--kc-font-body);
	font-size: 16px;
	font-weight: 700;
	min-height: var(--btn-min-height);
}
body.kc-rd .subscribe .wpcf7-form-control.has-spinner.wpcf7-submit:hover {
	transform: none;
	box-shadow: var(--btn-primary-shadow);
	background: var(--btn-primary-bg);
}
body.kc-rd .subscribe .wpcf7-form-control.has-spinner.wpcf7-submit:active {
	transform: translate(3px, 3px);
	box-shadow: 0 0 0 var(--text);
}


/* --------------------------------------------------------------------------
   20.10 · SMALL SCREENS — 375px is the design target (86.5% mobile, GSC 90d).
   The commit action goes full width; the 3px shadow is kept inside the box so
   it can never produce a horizontal scrollbar on a 375px viewport.
   -------------------------------------------------------------------------- */

@media (max-width: 600px) {
	body.kc-rd .nm-simple-add-to-cart-button.single_add_to_cart_button.button.alt,
	body.kc-rd .nm-variable-add-to-cart-button.single_add_to_cart_button.button.alt {
		width: calc(100% - 4px) !important;   /* beats child :2217 inline-block sizing */
	}
	body.kc-rd.woocommerce-cart .checkout-button.button.alt.wc-forward {
		width: calc(100% - 4px);
		margin-right: 0;
	}
	/* Long labels ("Proceed to Checkout") must wrap, not clip: the base sets
	   line-height 1.25 against Savoy's 16px hard value, and min-height keeps
	   a one-line button on the 48px floor either way. */
	body.kc-rd .button,
	body.kc-rd input[type=submit],
	body.kc-rd .kc-btn { white-space: normal; }

	/* 8px minimum between adjacent targets — the cart actions row packs
	   Apply coupon / Continue shopping / Update cart edge to edge. */
	body.kc-rd .shop_table.cart .actions .button,
	body.kc-rd .shop_table.cart .actions input[type=submit] { margin-bottom: var(--kc-space-2); }
}


/* --------------------------------------------------------------------------
   20.11 · REDUCED MOTION — the press state is the affordance, so it stays.
   What goes is the tweening and the spinner rotation.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	body.kc-rd .button,
	body.kc-rd input[type=submit],
	body.kc-rd .kc-btn,
	body.kc-rd .nm-infload-btn,
	body.kc-rd .jwa-submit,
	body.kc-rd .custom-realted-products-button,
	body.kc-rd a.nm_btn,
	body.kc-rd .nm_btn_bg,
	body.kc-rd .subscribe .wpcf7-form-control.has-spinner.wpcf7-submit {
		transition: none !important;
	}
	body.kc-rd button.button.loading::after,
	body.kc-rd a.button.loading::after,
	body.kc-rd button.kc-btn.kc-loading::after,
	body.kc-rd button[aria-busy="true"].button::after {
		animation: none;
		border-right-color: currentColor;
		opacity: .45;
	}
}

/* ===== 22-catalog-head.css ===== */
/* ==========================================================================
   22 — Catalog page head (owner: catalog-head-v3)
   Title → count/description → sort → quick chips: one rhythm instead of
   three strangers. Presentation only; H1, URLs and ordering behaviour are
   untouched (inc-catalog.php renders the count/description and renames the
   sort option labels).
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Masthead: breadcrumb → H1 → count as ONE compact group (owner round 8:
   "面包屑/H1/计数并成紧凑一组" — the old stack burned 76px of breadcrumb
   padding plus 18/18 gaps). H1 TEXT is untouched; only size/spacing.

   TEMP !important (two): savoy-child/style.css ~1577 pins the archive H1
   with `font-size: 50px !important; margin-bottom: 60px !important` — that
   legacy block is reported for deletion (docs/redesign/catalog-head-v3.md +
   catalog-round8.md, "needs orchestrator"). Until it dies, only !important
   outranks !important. Once deleted, drop the flags here (values stay).
   -------------------------------------------------------------------------- */
body.kc-rd.archive #nm-shop h1.title {
	font-size: 40px !important;      /* was 50px; Gazella brand floor is 28px */
	margin-bottom: 8px !important;   /* was 60px legacy / 18px round 3 */
	padding-bottom: 12px;            /* amber dash sits at the padding edge */
}
@media (max-width: 991px) {
	body.kc-rd.archive #nm-shop h1.title { font-size: 30px !important; }
}

/* Breadcrumb: one quiet 11px line right above the H1 instead of a 76px
   band (30px padding top AND bottom, measured). The trailing crumb is the
   page itself — the H1 8px below already says it, and on /shop/ the triple
   read "HOME PAGE / SHOP + Shop + count" was the owner's exact complaint —
   so the current-page crumb hides VISUALLY ONLY: the RDFa BreadcrumbList
   markup stays complete in the DOM. NavXT separators are bare "/" text
   nodes (unstylable, undeletable from CSS), so the container mutes all
   text (font-size:0) and the crumb spans restore their own size; the
   separator is redrawn as ::after on every crumb except the last visible
   one (:nth-last-of-type counts the hidden current crumb too, hence -n+2). */
body.kc-rd.archive #nm-shop .breadcrumbs {
	display: flex;              /* desk had it from style.css:1571; phone was
	                               a left-hanging block — centre both ends */
	justify-content: center;
	flex-wrap: wrap;
	font-size: 0;
	line-height: 1;
	padding: 14px 15px 4px;
}
body.kc-rd.archive #nm-shop .breadcrumbs > span {
	font-size: 11px;
	line-height: 1.5;
	letter-spacing: .08em;
}
body.kc-rd.archive #nm-shop .breadcrumbs > span:last-of-type { display: none; }
body.kc-rd.archive #nm-shop .breadcrumbs > span:not(:nth-last-of-type(-n+2))::after {
	content: "/";
	margin: 0 7px;
	color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   2. Count + description block (rendered under the H1 by inc-catalog.php)
   -------------------------------------------------------------------------- */
body.kc-rd .kc-cat-head {
	text-align: center;
	margin: 0 0 14px;   /* 18 → 14: part of the round-8 masthead tightening */
	padding: 0 var(--kc-space-4);
}
body.kc-rd .kc-cat-count {
	font-family: var(--kc-font-body);
	font-size: 12px;
	font-weight: 500;
	letter-spacing: .08em;
	text-transform: uppercase;
	font-variant-numeric: tabular-nums;
	color: var(--text-faint);
	margin: 0;
}
body.kc-rd .kc-cat-desc {
	max-width: var(--measure);
	margin: 10px auto 0;
}
body.kc-rd .kc-cat-desc-text {
	font-family: var(--kc-font-body);
	font-size: 14px;
	line-height: 1.65;
	color: var(--text-muted);
}
body.kc-rd .kc-cat-desc-text p { margin: 0 0 8px; }
body.kc-rd .kc-cat-desc-text p:last-child { margin-bottom: 0; }
/* Clamp is opt-in from JS (22-catalog.js): no JS = full text, no dead end. */
body.kc-rd .kc-cat-desc.is-clamped .kc-cat-desc-text {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
}
body.kc-rd .kc-cat-desc-more {
	display: inline-block;
	background: none;
	border: 0;
	padding: 10px 12px; /* small type, real 44px-ish hit area */
	margin-top: 2px;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 3px;
	cursor: pointer;
}
body.kc-rd .kc-cat-desc-more:focus-visible { outline: 2px solid var(--trust); outline-offset: 2px; }

/* --------------------------------------------------------------------------
   3. Sort control: stock Woo dropdown → quiet token field.
   Same form, same select, same URL params — clothes only.
   Legacy layers this supersedes (report lists them for deletion):
   style.css ~1620 (select 18px), ~1640 (.archive-page-sort-by-title 18px),
   ~1648 (margin 25px), ~4012 (mobile padding-left).
   -------------------------------------------------------------------------- */
body.kc-rd .archive-page-sort-by {
	display: flex;
	align-items: center;
	justify-content: flex-end;
	gap: 10px;
	margin: 0 0 16px;
	padding: 0 15px;
}
/* Keep the template's real "Sort by:" text; dress it as a quiet label.
   Closed state reads "Sort by:  Featured ▾". */
body.kc-rd .archive-page-sort-by-title,
body.kc-rd .archive-page-sort-by .archive-page-sort-by-title {
	font-family: var(--kc-font-body);
	font-size: 12px;
	font-weight: 600;
	letter-spacing: .06em;
	text-transform: uppercase;
	color: var(--text-muted);
	padding: 0;
}
/* form.woocommerce-ordering IS the pill (span sits outside it) */
body.kc-rd #nm-shop .archive-page-sort-by form.woocommerce-ordering {
	position: relative;
	display: inline-flex;
	align-items: center;
	margin: 0;
	background: var(--field-bg);
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
	min-height: 44px;
}
body.kc-rd #nm-shop .archive-page-sort-by form.woocommerce-ordering select.orderby {
	-webkit-appearance: none;
	-moz-appearance: none;
	appearance: none;
	background: transparent;
	border: 0;
	margin: 0;
	padding: 0 36px 0 14px;
	min-height: 42px;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 500;
	color: var(--text);
	cursor: pointer;
}
body.kc-rd #nm-shop .archive-page-sort-by form.woocommerce-ordering select.orderby:focus-visible {
	outline: 2px solid var(--trust);
	outline-offset: 2px;
	border-radius: var(--kc-radius-sm);
}
/* Chevron: CSS-drawn so it inherits token colour (no baked-hex data URI). */
body.kc-rd #nm-shop .archive-page-sort-by form.woocommerce-ordering::after {
	content: "";
	position: absolute;
	right: 14px;
	top: 50%;
	width: 8px;
	height: 8px;
	margin-top: -6px;
	border-right: 2px solid var(--text);
	border-bottom: 2px solid var(--text);
	transform: rotate(45deg);
	pointer-events: none;
}
@media (max-width: 991px) {
	/* Gutter math (measured): .nm-row has no padding; the card's visual box
	   starts at 15px. Label at 15, pill right edge at 360 — same rail. */
	body.kc-rd .archive-page-sort-by { margin: 0 15px 14px; padding: 0; }
	body.kc-rd #nm-shop .archive-page-sort-by form.woocommerce-ordering { flex: 1 1 auto; }
	body.kc-rd #nm-shop .archive-page-sort-by form.woocommerce-ordering select.orderby { width: 100%; }
	/* The filters sidebar collapses to display:none on phone but its bootstrap
	   column keeps padding-top: 24px — a ghost gap between count and sort. */
	body.kc-rd #nm-shop .nm-shop-sidebar-col { padding-top: 0; padding-bottom: 0; }
}

/* --------------------------------------------------------------------------
   4. Results bar — category side ONLY.
   The search-page chip ("Search results for “x”") already has a finished
   design owned by the search surface (70-search.css §70.9, deliberately
   scoped body.kc-rd.search … .is-search) — nothing here may touch it, so
   everything below scopes to the category/tag classes the same way.
   Category/tag pages: the whole bar is suppressed server-side unless filters
   are active (inc-catalog.php §4); when it IS rendered for "Filters active
   (n)", the redundant "Showing X" li still hides here, and the reset control
   gets the token skin (Savoy's is border+strikethrough-on-hover grey).
   -------------------------------------------------------------------------- */
body.kc-rd .nm-shop-results-bar.is-category li.nm-shop-search-taxonomy-reset,
body.kc-rd .nm-shop-results-bar.is-tag li.nm-shop-search-taxonomy-reset { display: none; }

body.kc-rd .nm-shop-results-bar.is-category,
body.kc-rd .nm-shop-results-bar.is-tag { margin-bottom: 14px; }
body.kc-rd .nm-shop-results-bar.is-category ul,
body.kc-rd .nm-shop-results-bar.is-tag ul { margin: 0; padding: 0; }
body.kc-rd.archive #nm-shop .nm-shop-results-bar.has-filters a#nm-shop-filters-reset {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding: 6px 14px 6px 37px; /* left room for Savoy's own x glyph */
	background: var(--surface-sunken);
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	font-family: var(--kc-font-body);
	font-size: 13px;
	color: var(--text-muted);
}
body.kc-rd.archive #nm-shop .nm-shop-results-bar.has-filters a#nm-shop-filters-reset span { color: var(--text); font-weight: 600; }
body.kc-rd.archive #nm-shop .nm-shop-results-bar.has-filters a#nm-shop-filters-reset:hover { border-color: var(--text); color: var(--text); text-decoration: none; }

/* --------------------------------------------------------------------------
   5. Quick chips: colour discipline + rhythm.
   DOM belongs to inc-cards.php (untouched); these override the amber link
   colour they inherit out here. Idle chip = quiet ink on white; the active
   chip is the customer's own place in the tree = pine (brand law: green is
   the customer's choice). Amber stays reserved for Add to cart.
   -------------------------------------------------------------------------- */
body.kc-rd .kc-quick-chips { margin-bottom: 18px; }
body.kc-rd .kc-quick-chips > .kc-tier {
	background: var(--surface-raised);
	border: 1px solid var(--border);
	color: var(--text);
	font-weight: 500;
}
body.kc-rd .kc-quick-chips > .kc-tier span {
	color: var(--text-faint);
	font-variant-numeric: tabular-nums;
	font-weight: 500;
}
body.kc-rd .kc-quick-chips > .kc-tier:hover { border-color: var(--border-strong); color: var(--text); }
body.kc-rd .kc-quick-chips > .kc-tier.kc-tier-active {
	background: var(--trust);
	border-color: var(--trust);
	color: var(--on-trust);
}
body.kc-rd .kc-quick-chips > .kc-tier.kc-tier-active span { color: var(--on-trust); opacity: .75; }
body.kc-rd .kc-quick-chips > .kc-tier:focus-visible { outline: 2px solid var(--trust); outline-offset: 2px; }
@media (max-width: 991px) {
	/* Align the scroll row's start with the 15px content rail (chips were
	   flush against the screen edge; cards and sort sit at 15px). */
	body.kc-rd .kc-quick-chips {
		margin-bottom: 16px;
		padding-left: 15px;
		padding-right: 15px;
		scroll-padding-left: 15px;
	}
}

/* --------------------------------------------------------------------------
   6. "Pine plaque" masthead — owner picked mock D on the 2026-09-01 board
   (staging kc-boards/cat-head-mobile-20260901.html), then extended it to
   desktop same day ("电脑端也需要改 不能只有手機端改").
   Category/tag pages only; /shop/ and search keep their own heads.
   There is no wrapper div to paint, so the plaque is three stacked
   full-width blocks (breadcrumb → H1 → count) each painting the same pine,
   with the margins between them zeroed so the seams close. The description
   is NOT in the plaque: 22-catalog.js §3 moves it below the product grid
   (all viewports). Until it moves — and with no JS at all — it stays
   hidden here while Savoy\'s own column-tail copy
   (.product-category-description) keeps showing, so the text is always
   both in the DOM and reachable.
   ------------------------------------------------------------------------ */
body.kc-rd.tax-product_cat #nm-shop .breadcrumbs,
body.kc-rd.tax-product_tag #nm-shop .breadcrumbs {
	background: var(--trust);
	padding-top: 14px;
	margin: 0;
}
/* "span" not "> span": the crumb text lives in a nested RDFa
   span[property=name] that a legacy layer paints ink (#2A2621, measured) —
   the anchor\'s colour never reaches it by inheritance. */
body.kc-rd.tax-product_cat #nm-shop .breadcrumbs span,
body.kc-rd.tax-product_tag #nm-shop .breadcrumbs span,
body.kc-rd.tax-product_cat #nm-shop .breadcrumbs a,
body.kc-rd.tax-product_tag #nm-shop .breadcrumbs a { color: rgba(255, 251, 242, .65); }
body.kc-rd.tax-product_cat #nm-shop .breadcrumbs a:hover,
body.kc-rd.tax-product_tag #nm-shop .breadcrumbs a:hover { color: var(--surface); }
body.kc-rd.tax-product_cat #nm-shop .breadcrumbs > span::after,
body.kc-rd.tax-product_tag #nm-shop .breadcrumbs > span::after { color: rgba(255, 251, 242, .35); }

body.kc-rd.tax-product_cat #nm-shop h1.title,
body.kc-rd.tax-product_tag #nm-shop h1.title {
	background: var(--trust);
	color: var(--surface);
	margin: 0 !important;            /* §1\'s 8px cream seam would split the plaque */
	padding-bottom: 2px;
}
/* The amber dash reads as a stain on pine; the amber count IS the accent. */
body.kc-rd.tax-product_cat #nm-shop h1.title::after,
body.kc-rd.tax-product_tag #nm-shop h1.title::after { display: none; }

body.kc-rd.tax-product_cat .kc-cat-head,
body.kc-rd.tax-product_tag .kc-cat-head {
	background: var(--trust);
	margin-bottom: 0;
	padding-bottom: 30px;            /* phone: room for the chips to ride the hem */
}
body.kc-rd.tax-product_cat .kc-cat-count,
body.kc-rd.tax-product_tag .kc-cat-count {
	color: var(--action);
	letter-spacing: .1em;
	padding: 5px 0 0;
	/* 26-cat-final.css §26.1 draws ledger rules around the count; on the
	   pine plaque they read as scratches. Higher specificity, no flags. */
	border-top: 0;
	border-bottom: 0;
}
/* Hidden only while it still sits inside the plaque (pre-move / no-JS);
   the relocation pulls it out of .kc-cat-head, this stops matching, and
   §6b takes over. */
body.kc-rd.tax-product_cat .kc-cat-head .kc-cat-desc,
body.kc-rd.tax-product_tag .kc-cat-head .kc-cat-desc { display: none; }

/* ---- phone refinements ---- */
@media (max-width: 991px) {
	body.kc-rd.tax-product_cat #nm-shop h1.title,
	body.kc-rd.tax-product_tag #nm-shop h1.title {
		font-size: 27px !important;  /* §1\'s phone floor was 30px !important */
	}
	body.kc-rd.tax-product_cat .kc-cat-count,
	body.kc-rd.tax-product_tag .kc-cat-count { font-size: 11px; }

	/* Chips ride the plaque hem. The pull-up CANNOT live on the chips:
	   .nm-shop-products-col is overflow:hidden (Savoy), so anything hoisted
	   above the column\'s box top gets clipped — measured: the chips\' top
	   26px simply vanished. Hoist the whole column instead (its own box
	   moves, nothing crosses the clip edge), gated on the chip row actually
	   having chips so empty pages keep a plain plaque hem. */
	body.kc-rd.tax-product_cat #nm-shop .nm-shop-products-col:has(.kc-quick-chips .kc-tier),
	body.kc-rd.tax-product_tag #nm-shop .nm-shop-products-col:has(.kc-quick-chips .kc-tier) {
		margin-top: -26px;
	}
	body.kc-rd.tax-product_cat .kc-quick-chips,
	body.kc-rd.tax-product_tag .kc-quick-chips {
		position: relative;
		z-index: 2;
	}
	body.kc-rd.tax-product_cat .kc-quick-chips > .kc-tier,
	body.kc-rd.tax-product_tag .kc-quick-chips > .kc-tier {
		box-shadow: 0 2px 6px rgba(31, 26, 23, .14);
	}
	/* On pine, the "you are here" chip flips to amber (pine-on-pine vanishes) */
	body.kc-rd.tax-product_cat .kc-quick-chips > .kc-tier.kc-tier-active,
	body.kc-rd.tax-product_tag .kc-quick-chips > .kc-tier.kc-tier-active {
		background: var(--action);
		border-color: var(--action);
		color: var(--on-action);
	}
	body.kc-rd.tax-product_cat .kc-quick-chips > .kc-tier.kc-tier-active span,
	body.kc-rd.tax-product_tag .kc-quick-chips > .kc-tier.kc-tier-active span {
		color: var(--on-action);
		opacity: .6;
	}
}

/* ---- desktop refinements ---- */
@media (min-width: 992px) {
	/* Full bleed: breadcrumb and H1 sit in a 1250px centred box (measured:
	   margin ~87px each side at 1440) that would leave cream gutters beside
	   the pine. The text is centre-aligned anyway, so releasing the box
	   changes nothing but the paint. .kc-cat-head is already full-width. */
	body.kc-rd.tax-product_cat #nm-shop .breadcrumbs,
	body.kc-rd.tax-product_tag #nm-shop .breadcrumbs,
	body.kc-rd.tax-product_cat #nm-shop h1.title,
	body.kc-rd.tax-product_tag #nm-shop h1.title {
		max-width: none;
		margin-left: 0;
		margin-right: 0;
		width: 100%;
	}
	body.kc-rd.tax-product_cat #nm-shop .breadcrumbs,
	body.kc-rd.tax-product_tag #nm-shop .breadcrumbs { padding-top: 20px; }
	body.kc-rd.tax-product_cat #nm-shop h1.title,
	body.kc-rd.tax-product_tag #nm-shop h1.title { padding-bottom: 8px; }
	/* No chips overlap on desktop: plain hem + a breath before the columns. */
	body.kc-rd.tax-product_cat .kc-cat-head,
	body.kc-rd.tax-product_tag .kc-cat-head {
		padding-bottom: 26px;
		margin-bottom: 22px;
	}
}

/* 6b. The relocated description = quiet About block under the grid (all
   viewports since the desktop extension). Scoped to the class the JS adds
   when it moves the node. Clamp keeps working wherever the node lives.
   The label div exists only to serve this block; everywhere else it stays
   dark. */
body.kc-rd .kc-cat-desc-label { display: none; }
body.kc-rd .kc-cat-desc--footer {
	max-width: var(--measure);
	margin: 30px auto 6px;
	padding: 22px 15px 0;
	border-top: 1px solid var(--border);
	text-align: left;
}
body.kc-rd .kc-cat-desc--footer .kc-cat-desc-label {
	display: block;
	font-family: var(--kc-font-display);
	font-size: 19px;
	margin: 0 0 8px;
	color: var(--text);
}
body.kc-rd .kc-cat-desc--footer .kc-cat-desc-more { padding-left: 0; }
/* Savoy natively prints the same term description at the column tail
   (.product-category-description) — the page has always carried the text
   twice (and desktop showed BOTH copies before this work). Once the About
   block has taken over (footer class present = JS ran), the raw duplicate
   would sit right above it as an unclamped wall; hide it then and only
   then, so the no-JS page still shows the theme\'s copy. */
body.kc-rd.tax-product_cat #nm-shop .nm-shop-products-col:has(.kc-cat-desc--footer) .product-category-description,
body.kc-rd.tax-product_tag #nm-shop .nm-shop-products-col:has(.kc-cat-desc--footer) .product-category-description { display: none; }

/* ===== 23-catalog-page.css ===== */
/* ==========================================================================
   23 — Catalog page, below the grid (owner: catalog-page / catalog-round8)
   Round 8: the Load More button is RETIRED server-side (owner call —
   nm_theme_options shop_infinite_load 'button' → '0', products_per_page
   24 → 48, both logged in docs/redesign/catalog-round8.md). Savoy now
   renders its stock numbered pagination, which this file dresses in the
   token language. The old §23.1/23.2 infload state fixes died with the
   button and are deleted (their target markup no longer renders); the
   loader tint, empty state and grid rhythm stay. Presentation only —
   URLs, the crawlable /page/N/ links, ordering behaviour and copy are
   untouched.
   ========================================================================== */

/* --------------------------------------------------------------------------
   23.1 Numbered pagination, token skin.
   Markup is Savoy's stock loop/pagination.php: nav.woocommerce-pagination
   > ul.page-numbers > li > a/span(.current|.prev|.next|.dots). Chip-nav
   language: 44px round chips, ink border; CURRENT page = solid ink (system
   state, not a choice — pine stays the customer's, amber stays Add to
   cart's). Left generic (not .archive-scoped) on purpose: the same
   template paginates product search, and the infload retirement is global
   — a naked stock pagination there would be the only unskinned control
   on the site. (Search surface notified in the round-8 report.)
   -------------------------------------------------------------------------- */
body.kc-rd .woocommerce-pagination.nm-pagination {
	margin: 28px 0 10px;
	text-align: center;
}
body.kc-rd .woocommerce-pagination ul.page-numbers {
	display: flex;
	justify-content: center;
	flex-wrap: wrap;
	gap: 8px;
	margin: 0;
	padding: 0 15px;
	list-style: none;
	border: 0;
}
body.kc-rd .woocommerce-pagination ul.page-numbers li {
	float: none;
	margin: 0;
	border: 0;
}
body.kc-rd .woocommerce-pagination .page-numbers li a.page-numbers,
body.kc-rd .woocommerce-pagination .page-numbers li span.page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
	padding: 0 12px;
	border: 1.5px solid var(--text);
	border-radius: 999px;
	background: var(--surface-raised);
	color: var(--text);
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
	line-height: 1;
	text-decoration: none;
}
body.kc-rd .woocommerce-pagination .page-numbers li a.page-numbers:hover {
	background: var(--surface-sunken);
	text-decoration: none;
}
body.kc-rd .woocommerce-pagination .page-numbers li a.page-numbers:focus-visible {
	outline: 2px solid var(--trust);
	outline-offset: 2px;
}
/* Current page: solid ink pill — same voice as the active grade chip. */
body.kc-rd .woocommerce-pagination .page-numbers li span.page-numbers.current {
	background: var(--text);
	border-color: var(--text);
	color: var(--surface);
}
/* The "…" gap is typography, not a control. */
body.kc-rd .woocommerce-pagination .page-numbers li span.page-numbers.dots {
	border: 0;
	background: none;
	color: var(--text-faint);
	min-width: 24px;
	padding: 0;
}
/* Prev/next arrows (Savoy ships nm-font glyphs inside the anchor). */
body.kc-rd .woocommerce-pagination .page-numbers li a.prev,
body.kc-rd .woocommerce-pagination .page-numbers li a.next {
	font-size: 15px;
}
/* Theme legacy `.page-numbers { width:100%; overflow:hidden }` hits the
   ANCHORS too (they carry the same class as the ul) — measured: the next
   arrow stretched to a 218px pill while its li collapsed to 0. #nm-shop
   outranks the block-level theme rules that also zeroed the padding. */
body.kc-rd #nm-shop .woocommerce-pagination ul.page-numbers li {
	flex: 0 0 auto;
}
body.kc-rd #nm-shop .woocommerce-pagination ul.page-numbers li a.page-numbers,
body.kc-rd #nm-shop .woocommerce-pagination ul.page-numbers li span.page-numbers {
	width: auto;
	overflow: visible;
	padding: 0 12px;
	margin: 0;
}
/* Savoy absolutely positions prev/next at the column edges (measured:
   a.next position:absolute right:0 — the arrow tore away from the group).
   In the centred chip row they are ordinary chips. */
body.kc-rd #nm-shop .woocommerce-pagination ul.page-numbers li a.prev,
body.kc-rd #nm-shop .woocommerce-pagination ul.page-numbers li a.next {
	position: static;
}

/* --------------------------------------------------------------------------
   23.2 Loader tint.
   The pulsing dot (#nm-shop-products-overlay during sort/filter AJAX)
   ships #dc9814 from the customizer's inline CSS — the old theme gold,
   not the brand amber. Token it. Scoped under #nm-shop so the
   header-search loader and non-catalog loaders keep their own owners.
   -------------------------------------------------------------------------- */
body.kc-rd #nm-shop .nm-loader::after {
	background: var(--action);
}

/* --------------------------------------------------------------------------
   23.3 Progress line: "Showing 1–48 of 79" (rendered by inc-catalog.php §8
   under the pagination; only exists when there is more than one page).
   -------------------------------------------------------------------------- */
body.kc-rd .kc-page-progress {
	text-align: center;
	margin: 0 15px 8px;
	font-family: var(--kc-font-body);
	font-size: 12px;
	font-weight: 500;
	letter-spacing: .08em;
	text-transform: uppercase;
	font-variant-numeric: tabular-nums;
	color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   23.4 Empty state (archive scope only — the search surface owns its own
   empty results and is deliberately untouched).
   Stock: a bare left-aligned 18px h3 floating in the column, plus the
   template's hard-coded "Sort by:" label orphaned at the top right — Woo
   renders no sort control when there is nothing to sort, but the label is
   static HTML and stays. The "Filters active (n)" reset chip (already
   token-skinned in 22) is the escape hatch; centre it above the message so
   the state reads as one designed moment, not debris. No new links, no new
   copy. All state detection is :has-based and degrades to today's rendering.
   -------------------------------------------------------------------------- */
body.kc-rd.archive #nm-shop:has(.nm-shop-no-products) .archive-page-sort-by {
	display: none;
}
body.kc-rd.archive #nm-shop .nm-shop-no-products {
	text-align: center;
	padding: 32px 16px 88px;
}
body.kc-rd.archive #nm-shop .nm-shop-no-products h3 {
	display: block;
	max-width: var(--measure);
	margin: 0 auto;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 500;
	line-height: 1.6;
	color: var(--text-muted);
}
body.kc-rd.archive #nm-shop-browse-wrap:has(.nm-shop-no-products) .nm-shop-results-bar ul {
	display: flex;
	justify-content: center;
	margin: 0 0 6px;
	padding: 0;
	list-style: none;
}
body.kc-rd.archive #nm-shop-browse-wrap:has(.nm-shop-no-products) .nm-shop-results-bar li {
	float: none;
}

/* --------------------------------------------------------------------------
   23.5 Grid vertical rhythm (desktop only).
   Measured: the visual gutter between card columns is 30px (15px li padding
   each side) but between rows only 24px — the field reads slightly
   compressed vertically. Even it out. Archive-scoped so the homepage rails,
   PDP related/upsell grids and other surfaces' product lists keep their own
   spacing. Phone rhythm (14px) is set by kc-redesign.css §mobile with
   !important — orchestrator's file, reported not fought.
   -------------------------------------------------------------------------- */
@media (min-width: 992px) {
	body.kc-rd.archive #nm-shop ul.nm-products > li.product {
		padding-bottom: 30px;
	}
}

/* ===== 24-buy-value.css ===== */
/* ==========================================================================
   24. BUY-SIDE VALUE — static tier badges + the quantity line
   ==========================================================================
   owner 2026-08-23, verbatim: the discount badge must NOT move when the
   customer switches 7G/14G/28G, and the "2×14G=$160" anchor arithmetic
   sentence is gone. Each tier chip now wears its OWN static −X% (vs the
   smallest in-stock tier's $/g), printed server-side (inc-pdp.php
   kc_rd_tier_badges) and never repainted by JS. This file styles what
   remains: the badge's label-ness, the phone-card reduction, the quantity
   line and the sticky-bar total.

   Two hard constraints this file must never break:
     - card height and CTA baseline are IDENTICAL to before (24 cards, both
       viewports). The card slot .kc-save keeps its exact metrics.
     - nothing here may read as a purchasable control (P6: only Add to cart
       spends money) — no border, no shadow, no hover, no pointer.
   ========================================================================== */

/* --- 24.1 The quantity line ---------------------------------------------- */
/* Lives INSIDE the qty/CTA flex row, so it needs its own full-width row or it
   squeezes the stepper out of existence — and the stepper vanishing still
   passes a getBoundingClientRect() check (pcd.md §6.1). flex + width + order
   are all three load-bearing.
   The height is reserved at every quantity: if the line appeared only from
   qty 2 the stepper would jump ~18px down under the customer's finger between
   the first and second tap on "+". */
body.kc-rd.single-product form.cart .kc-qline {
	flex: 0 0 100%;
	width: 100%;
	order: -1;
	min-height: 1.2em;
	margin: 0 0 2px;
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	line-height: 1.2;
	color: var(--text-muted);
	font-variant-numeric: tabular-nums;
}
body.kc-rd.single-product form.cart .kc-qline b {
	font-weight: 700;
	color: var(--text);
}
/* the pine "you save" treatment — dormant until rule 44 stops eating the
   free gift at qty >= 3 (pcd-d2.md §7); the JS switches wording then. */
body.kc-rd.single-product form.cart .kc-qline i {
	font-style: normal;
	font-weight: 700;
	color: var(--trust);
}

/* --- 24.2 Sticky bar: say which number that is --------------------------- */
body.kc-rd .kc-sticky-priceline {
	display: flex;
	align-items: baseline;
	min-width: 0;
}
body.kc-rd .kc-sticky-qty {
	margin-left: 5px;
	font-size: 11px;
	font-weight: 600;
	color: var(--text-muted);
	font-variant-numeric: tabular-nums;
}
body.kc-rd .kc-sticky-qty:empty { display: none; }

/* --- 24.3 The badge is a label, never a control -------------------------- */
/* .kc-tier-off carries the pine pill from §26.y (visible on BOTH viewports);
   this only restates that it must not grow affordances. It sits on a
   <button>, so it inherits the chip's cursor — that is the chip's own tap
   target, not a second one. */
body.kc-rd .kc-tier .kc-tier-vs {
	pointer-events: none;
	box-shadow: none;
	font-variant-numeric: tabular-nums;
}

/* --- 24.4 Phone cards: one badge, the best tier's ------------------------ */
/* Card chips at 375 are weight-only and ~50px wide; a floating badge on two
   adjacent chips reads as clutter at that size, so the phone card keeps only
   the best-$/g tier's badge (the dispatch's fallback). The PDP's weight
   pills are full-size on phones and keep every badge; this rule scopes to
   .kc-buy-card so it cannot touch the PDP. Desktop cards show all badges. */
@media (max-width: 520px) {
	body.kc-rd .kc-buy-card .kc-tier .kc-tier-vs:not(.kc-tier-vs-best) { display: none; }
}

/* --- 24.45 Unbuyable-card CTA: the button's metrics, exactly ------------- */
/* The "View product" fallback (kc_rd_card_unbuyable_html) is an <a> carrying
   .button, so §20.1's link floor (--btn-min-height: 48px) hits it, while the
   sibling <button> CTAs render 3.4em ≈ 44.1px via their higher-specificity
   base rule — the card runs 4px tall and the row's bottom edge drifts.
   a.kc-cta-link ("Choose options") already carries this exact carve-out in
   the core file; this is the same rule for the one anchor variant it missed.
   44.1px still clears the ≥44px touch floor. Loads after 20-buttons.css and
   out-specifies it — no !important needed. */
body.kc-rd .kc-buy-card a.kc-cta.kc-btn-secondary {
	min-height: 3.4em;
}

/* --- 24.5 Card hint line: overflow guarantee ----------------------------- */
/* .kc-save is back to the selected tier's $/g (no sentences), which always
   fits — but the clamp stays as the guarantee: no future string can ever
   make a card taller, which is what the grid's CTA baseline depends on. */
body.kc-rd .kc-buy-card .kc-save {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* ==========================================================================
   24.6  PRICE TRUTH — the card eyebrow, and cards with nothing to sell
   ==========================================================================
   Both halves of this block exist because a price reached the screen without
   passing through the pricing authority (inc-pricing.php). One came from a
   taxonomy NAME, the other from a rebuilt variation range. See that file's
   header for the mechanism.
   ========================================================================== */

/* --- 24.61 Retire the legacy card eyebrow -------------------------------
   savoy-child/woocommerce/content-product.php:62 echoes
   jwaProduct::getProductCategory(), which prints wp_get_post_terms()[0]->name
   verbatim — so renaming a category "OZ Deal $99" put a price claim on every
   card in it, and five $120 ounces wore a $99 badge (audit M8). There is no
   PHP hook on that echo, so the legacy node is retired here and
   inc-pricing.php prints .kc-card-cat in its place with a LIVE number.

   The selector deliberately keys on li.product[data-product-id]: that
   attribute is written by content-product.php, which is exactly the markup
   whose badge we replace. The city-page product slider (functions.php:657)
   hand-writes the same .label-category inside .products WITHOUT that
   attribute, and it is not a WooCommerce loop, so our hook never fires there
   — it keeps its own badge untouched.

   Specificity (0,4,2) beats the legacy (0,3,0) at main.css:374 and the dead
   (0,4,0) at main.css:5564, so no !important. Both legacy rules are now
   unreachable for loop cards: ORCHESTRATOR, they should be deleted with the
   .label-category markup itself rather than left to rot under this override.
   ---------------------------------------------------------------------- */
body.kc-rd li.product[data-product-id] .label-category { display: none; }

/* --- 24.62 .kc-card-cat: the legacy eyebrow's exact metrics --------------
   Measured on staging before the swap (both viewports, three cards):
   absolute 8/8, z-index 1, span 4px 8px, 11px/11px, weight 600,
   letter-spacing .88px (= .08em), radius 2px, uppercase, Poppins, white on
   rgb(42,38,33). Everything is reproduced 1:1 except the ink, which moves to
   the --text token because a partial may not carry a raw brand hex
   — a 4-point darkening on an 11px chip, invisible side by side. */
body.kc-rd .kc-card-cat {
	position: absolute;
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
	align-items: center;
	left: 8px;
	top: 8px;
	right: auto;
	z-index: 1;
}
body.kc-rd .kc-card-cat span {
	display: inline-block;
	padding: 4px 8px;
	line-height: 1;
	border-radius: var(--kc-radius-sm);
	background: var(--text);
	color: var(--kc-white);
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 11px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/* --- 24.63 "Out of stock" instead of a 125px void (audit M9) -------------
   Island Pink / Jokerz Candy LSO / Purple Candy Rockstar are flagged instock
   with zero buyable variations, so the card printed &nbsp; into every slot:
   image, title, 125px of nothing, then a small "View product". It read as a
   broken card. The label now fills the slot that each viewport already
   reserved — desktop the .kc-tiers box (49px), phone the .kc-pline (13px) —
   so cardH 397/340 and ctaTop 338/278 are unchanged to the pixel.
   It is a status, not a control: no border, no shadow, no pointer (P6). */
body.kc-rd .kc-buy-oos .kc-pline-price.kc-pline-oos {
	font-size: 12px;
	font-weight: 600;
	line-height: 1.1;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--text-muted);
}
body.kc-rd .kc-buy-oos .kc-oos-slot {
	display: none;
	font-size: 12px;
	font-weight: 600;
	line-height: 1.1;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--text-muted);
	pointer-events: none;
}
@media (min-width: 521px) {
	/* Desktop reserves .kc-tiers and hides .kc-pline on unbuyable cards
	   (kc-redesign.css:2265). Same specificity as that rule (0,4,1) and this
	   file loads later, so the tiers box simply gains its label. */
	body.kc-rd .kc-buy-card.kc-buy-oos .kc-tiers-empty {
		display: flex;
		align-items: center;
		justify-content: flex-start;
		/* kc-redesign.css:2026 makes EVERY .kc-tiers-empty visibility:hidden —
		   that is what reserved the 49px void in the first place. This is the
		   one case that has something to say inside it. */
		visibility: visible;
	}
	body.kc-rd .kc-buy-card.kc-buy-oos .kc-oos-slot {
		display: inline-block;
		visibility: visible;
	}
}

/* --- 24.64 Card labels, final layout (owner picked A, 2026-08-29) --------
   strain pill bottom-left ON the photo · discount flag top-right OUTSIDE the
   photo · shelf on the product-name line. The two photo corners are opposite
   on purpose ("这样对称"), and the shelf shares a line that already existed,
   which is what closes the gap that used to sit under the name.
   ------------------------------------------------------------------- */

/* 24.64a The shelf sits at the right-hand end of the name line.
   NOT with flex, and NOT by releasing the title's height reserve — both were
   tried on production and both are wrong here: flex blockifies the inner title
   and kills the -webkit-line-clamp (the bug 25-card-mobile.css §25.45 exists to
   fix), and releasing the 36.8px reserve un-aligns the Add buttons of two cards
   in a row. So the shelf comes out of flow instead, with a right gutter on the
   box so a long name can never run under it.

   The gutter is scoped to .kc-has-shelf — the class inc-cards.php adds only
   when a shelf is actually printed. Unconditional, it cost 62px of title width
   on the hand-written city-page sliders, which never print one.

   Specificity: 25-card-mobile.css owns this element at (0,4,3) and sorts AFTER
   this file, so an identical selector would lose; the details class makes this
   (0,5,3). */
body.kc-rd ul.nm-products li.product.kc-has-shelf .nm-shop-loop-details .nm-shop-loop-title {
	position: relative;
	padding-right: 62px;
}
body.kc-rd ul.nm-products li.product .nm-shop-loop-details .kc-card-shelf {
	position: absolute;
	top: 2px;
	right: 0;
	max-width: 60px;
	overflow: hidden;
	text-overflow: ellipsis;
}
body.kc-rd .kc-card-shelf {
	line-height: 1.3;
	font-family: var(--kc-font-body);
	font-weight: 700;
	font-size: 10.5px;
	letter-spacing: 0.07em;
	text-transform: uppercase;
	color: var(--kc-ink-soft, #6b625a);
	white-space: nowrap;
}
/* PHONES DO NOT GET THE SHELF. The gutter is a fixed reservation out of a box
   that is 208px wide at 500px and 146px at 375px. Measured on
   /product-category/edible/gummies/ at a 250px card, counting products whose
   visible title is identical after the two-line clamp:
     gutter 62px -> 4 groups, 12 products      gutter 0 -> 2 groups, 8 products
   Those 8 are not mine — "200mg THC Gummies – Bubble Tea" and "…– Preppy
   Pineapple" share twenty characters before they differ, and the clamp predates
   this work. The other 4 are: I took the width that told them apart, to print a
   label that on a category archive repeats the page's own heading. On a phone
   the name is worth more than the shelf. */
@media (max-width: 768px) {
	body.kc-rd ul.nm-products li.product.kc-has-shelf .nm-shop-loop-details .nm-shop-loop-title {
		padding-right: 0;
	}
	body.kc-rd ul.nm-products li.product .nm-shop-loop-details .kc-card-shelf {
		display: none;
	}
}
@media (min-width: 769px) {
	body.kc-rd ul.nm-products li.product.kc-has-shelf .nm-shop-loop-details .nm-shop-loop-title {
		padding-right: 84px;
	}
	body.kc-rd ul.nm-products li.product .nm-shop-loop-details .kc-card-shelf {
		max-width: 78px;
	}
}

/* 24.64b Strain pill: bottom-left, on the photo.
   The pill is printed inside .nm-shop-loop-thumbnail-link, and that anchor is
   position:relative — so `bottom:8px` measured from the ANCHOR, whose height is
   the image's. Savoy lazy-loads with a placeholder of a different shape, so
   until a card scrolled into view its anchor was the placeholder's height
   (258px against a 208px frame) and the pill sat 50px BELOW the frame, inside
   overflow:hidden, i.e. invisible. Making the anchor static hands the
   containing block to the thumbnail, a fixed square that never moves. Checked
   before changing it: nothing else in the theme positions against that anchor,
   and the only other element inside it is the <img>. */
body.kc-rd li.product .nm-shop-loop-thumbnail-link {
	position: static;
}
body.kc-rd li.product .kc-type-chip {
	position: absolute;
	left: 8px;
	bottom: 8px;
	top: auto;
	right: auto;
	z-index: 2;
}

/* --- 24.65 The discount flag ---------------------------------------------
   Ink ground, brand-yellow numerals: the pairing the site already uses on the
   header and on the Add-to-cart button's border and shadow. Red was tried and
   rejected ("用红色不好看"); yellow-on-ink is loud enough without borrowing a
   colour the brand does not own, and it collides with none of the three strain
   pills nor with the yellow CTA. The swallowtail bottom edge is what makes it
   read as a flag pinned to the card rather than a sticker lying on it.

   IT DOES NOT OVERHANG THE CARD. The first build hung it 13px above the
   wrapper, which looked right and then got eaten by every clipping container it
   met: the home rails (overflow-x:auto forces overflow-y:hidden) took 9px off
   all nine, and the city-page slick sliders — which insert their own
   overflow:hidden box between the <ul> and the <li> — would have taken 12 of
   27. Padding whichever container was currently biting is a patch that has to
   be re-aimed each time a new one appears; reserving the height inside the card
   ends the whole class of it. Cards grow 16px, uniformly, so nothing
   misaligns.

   It must also stay clear of the picture: measured across all 77 in-stock
   images, 24 carry content in that corner and 9 are edge-to-edge packaging with
   no margin at all, so a badge inside the frame covers product on a third of
   the catalogue. translateY(-100%) lifts the tab by its OWN height — no
   per-breakpoint pixel guessing — and the +3px puts just its base back over the
   frame so it reads as pinned to the photo. */
/* kc-redesign.css sets `padding: 10px 12px 14px` on this element at (0,4,3);
   a lower-specificity rule silently lost and the flag ended up 19px INSIDE the
   photo. Same selector, and this file is concatenated after kc-redesign.css,
   so the tie breaks here — no !important. Only padding-top is overridden, so
   the sides and bottom stay exactly as that rule set them. */
body.kc-rd ul.nm-products li.product .nm-shop-loop-product-wrap {
	position: relative;
	padding-top: 26px;
}
/* Card templates outside ul.nm-products (the hand-written city sliders) still
   need the positioning context, and their own padding is their business. */
body.kc-rd li.product .nm-shop-loop-product-wrap {
	position: relative;
}
body.kc-rd .kc-flag {
	position: absolute;
	top: 26px;
	right: 10px;
	transform: translateY(calc(-100% + 3px));
	z-index: 5;
	display: block;
	padding: 6px 9px 10px;
	line-height: 1;
	background: var(--text, #1F1A17);
	color: var(--kc-yellow, #FFDA48);
	font-family: var(--kc-font-body);
	font-weight: 800;
	font-size: 11px;
	letter-spacing: 0.04em;
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
	box-shadow: 0 2px 5px rgba(31, 26, 23, 0.22);
	clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 80%, 0 100%);
}
@media (max-width: 767px) {
	body.kc-rd .kc-flag { right: 8px; padding: 5px 8px 9px; font-size: 10px; }
}

/* A page cached before this change still carries WooCommerce's own .onsale;
   without this it would stack with the flag for as long as that page lives.
   NOT applied when the kill switch is on: inc-cards.php then hands the native
   badge back and adds .kc-sale-native to the body, and hiding it there would
   make the switch produce no badge at all — worse than either state it exists
   to choose between. */
body.kc-rd:not(.kc-sale-native) li.product .onsale:not(.kc-flag) {
	display: none;
}

/* --- 24.66 "SAVE $80" instead of "-27%" ---------------------------------
   The corner flag already carries a percentage, and it measures the SALE
   discount. This badge measures how much cheaper the larger size works out
   per gram — a different quantity entirely, and rendering both as "−NN%" on
   one card asked the buyer to tell two unrelated numbers apart by position.
   Measured on /shop/: six cards showed a flag and chips whose figures
   disagreed. A dollar amount cannot be mistaken for the discount above it.

   It is wider than "-27%" was, so the pill gets room and the chip a little
   more padding; the label stays one line at every breakpoint. */
body.kc-rd .kc-tier .kc-tier-vs {
	pointer-events: none;
	box-shadow: none;
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
	letter-spacing: 0.02em;
}

/* --- 24.67 Flavour pickers are a dropdown, not a chip grid ---------------
   inc-pdp.php adds .kc-picker-select to both the chip row and the native
   select wrapper when the axis is NOT weight and there are four or more
   options — "Blueberry Doughnut (Hybrid)" five times over wraps into a
   ragged grid that gets worse with every flavour added, with no ceiling.

   The chips stay in the DOM, visually hidden rather than display:none:
   WooCommerce's variation JS runs through the native <select>, and
   24-buy-value.js reads the per-gram string off the matching chip when
   found_variation fires. Removing them would stop the price line updating
   without a single error anywhere. */
body.kc-rd .kc-tiers.kc-picker-select {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	margin: 0;
	padding: 0;
	border: 0;
}
body.kc-rd .kc-native-variation-select.kc-picker-select {
	position: static;
	width: auto;
	height: auto;
	overflow: visible;
	clip: auto;
	clip-path: none;
	margin: 6px 0 4px;
}
/* Woo Variation Swatches replaces the select with its own <ul> of buttons and
   hides the real control with an INLINE style="display:none". An inline style
   loses to nothing but !important, so this is the one place in the file that
   needs it — and it is aimed at exactly one element, only inside a wrapper the
   PHP marked, only on flavour axes with four or more options. Its own swatch
   list is hidden right below, or the page would show two pickers.
   The plugin keeps the raw select in sync in both directions, so driving the
   select directly is the same input path its buttons use. */
body.kc-rd .kc-native-variation-select.kc-picker-select .variable-items-wrapper {
	display: none !important;
}
body.kc-rd .kc-native-variation-select.kc-picker-select select,
body.kc-rd .kc-native-variation-select.kc-picker-select select.woo-variation-raw-select {
	display: block !important;
	width: 100%;
	min-height: 48px;                 /* the site's tap floor */
	padding: 12px 40px 12px 14px;
	border: 1.5px solid var(--text, #1F1A17);
	border-radius: 10px;
	background-color: var(--kc-white, #fff);
	/* Chevron drawn inline so no extra request and no dependency on the
	   theme's select-arrow gif, which the legacy skin points at a 2x sprite. */
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%231F1A17' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: right 14px center;
	background-size: 12px 8px;
	-webkit-appearance: none;
	appearance: none;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	color: var(--text, #1F1A17);
	line-height: 1.2;
	box-shadow: none;
}
body.kc-rd .kc-native-variation-select.kc-picker-select select:focus-visible {
	outline: 2px solid var(--kc-pine, #1E5B3E);
	outline-offset: 2px;
}
/* WooCommerce prints a "Clear" link next to the select once a value is
   chosen; on a picker that always has a value it is a dead end. */
body.kc-rd .kc-native-variation-select.kc-picker-select .reset_variations {
	display: none;
}

/* ===== 25-card-mobile.css ===== */
/* ==========================================================================
   25 — Phone card layout, NN skeleton (owner: card-mobile agent, 2026-08-26)
   ==========================================================================
   Owner: 「被挤压,产品名两行都写不下」. Recon (docs/redesign/nn-card-recon.md,
   Playwright-measured against nativenic prod at 390px) found the squeeze is
   ARITHMETIC, not styling: KC spends 28px per side on structure (15 li pad +
   1 border + 12 wrap pad) where NN spends 21.8, runs 16px type in a 139px
   column (NN: 14px in 146px), and the home rails cut the slot to 45%
   (159.3px → a 103px text column, 12 chars a line).

   This file is LAYOUT ONLY. The approved buy-box anatomy (big price line +
   gram-only chips + full-width Add, Batch 12 修正 series) is not touched —
   it simply inherits a wider card. Desktop (>520px) is a separately
   approved form and is not touched either, except the font-family bug fix
   in 25.5 which is brand law at every width.

   Scope: max-width 520px = the mobile-card form boundary §26 already uses.
   521–768px tablet keeps today's geometry (wider cards, nothing squeezed).
   ========================================================================== */

/* --- 25.1 One gutter, one charge ----------------------------------------- */
/* The theme's grid column pads every li 15px a side ON TOP of the card
   frame's own 12px + 1px border. NN charges a gutter once (~10px between
   frames). li 15 → 8: content column 139 → 153 (+10%), the sunken gutter
   between card frames 30 → 16 (NN: 19.6), image area +21%. The wrap's 12px
   inner padding and the frame border are the card's own skin — untouched,
   so the visual card language does not change, the card is simply wider.
   !important is NOT a shout-over here: the 15px is savoy-child
   assets/css/main.css:6481 — the LEGACY pre-redesign skin's mobile rule
   `.product { padding-left/right: 15px !important }`, written for old
   WPBakery page sections whose body class was .product and string-matching
   every card li by accident. A live-layer spec fight (31e precedent);
   main.css:6481-6484 is listed for deletion in the report, after which
   this drops to a plain declaration. */
@media (max-width: 520px) {
	body.kc-rd ul.nm-products > li.product {
		padding-left: 8px !important;
		padding-right: 8px !important;
	}
}

/* --- 25.2 Home rails: the slot earns category width ----------------------- */
/* 31b sets the swipe-rail slot to 45% (159.3px at 390 — 2.45 cards crammed
   in view, a 103px text column, "28G" touching its chip border). NN's rail
   runs ~2.05 cards with a peek. 53.7% of the 354px scroll box = 190px at
   390 — the category card's width, on the rail. With 25.1 the rail text
   column goes 103 → ~148 (+43%). Extra <ul> in the selector out-specifies
   31b (loads after this file) without touching it — the sanctioned tie-
   breaker, not a shout-over. flex-basis and width both set because 31b
   sets both. */
@media (max-width: 520px) {
	body.home .kc-hm-rail ul.nm-products > li.product {
		flex: 0 0 53.7%;
		width: 53.7%;
	}
}

/* --- 25.3 Title: two lines that actually hold a product name -------------- */
/* The math the owner's complaint reduces to: 16px Poppins ≈ 8.65px/char in
   a 139px column = 16 chars a line, 32 in two lines — against a catalog
   whose real names run 38–76 chars. 15px (the playbook §3 spec desktop
   already uses — this UNIFIES the two forms) in the 153px column from 25.1
   = ~19 chars/line, ~38 in two. Names ≤38 chars now fit whole; the all-caps
   65–76ch rolling-paper names are a data problem no font size can fix
   (measured coverage in docs/redesign/card-mobile-nn.md).
   !important matches §26's own !important on the same selectors
   (kc-redesign.css:2311-2317) — those lines are superseded and listed for
   orchestrator deletion in the report, after which the !important here can
   drop too.

   2026-08-28 (rootfix mobile-layout, audit M63): the leading used to be the
   literal 1.2 and the reserve the literal 36px. 1.2 is BELOW the structural
   floor for a clipped 2-line box — a clamped third line's ascenders render
   inside the box, which is the "chopped letter-tops" strip on /on-sale-now/.
   The derivation and the floor live in 89-mobile-fix.css §89.0; this rule now
   reads the token so the number exists in exactly one place. The slot height
   itself is set once, for every viewport, in 89-mobile-fix.css §89.3 — it is
   deliberately not restated here. */
@media (max-width: 520px) {
	body.kc-rd ul.nm-products li.product .nm-shop-loop-title,
	body.kc-rd ul.nm-products li.product .nm-shop-loop-title .woocommerce-loop-product__title,
	body.kc-rd ul.nm-products li.product .nm-shop-loop-title a {
		font-size: 15px !important;
		line-height: var(--kc-card-title-lh) !important;
	}
}

/* --- 25.4 Breathing at the ends, rhythm untouched in the middle ----------- */
/* NN's air is not sprinkled: rows stay tight (4–10px) and the pauses sit at
   the card's two ends — 20.9px under the image, 31.5px under the CTA. KC
   runs 6–13 wall to wall (Batch 12 修正三's mid-card rhythm is right; the
   ends are what's missing). Scaled to KC's shorter card: img→title 10→14,
   CTA→card bottom 13→18. Both are ADDITIVE on top of §26's !important
   spacing (details margin-top 10 / wrap padding-bottom 12), so no
   !important fight: padding-top rides on the margin, margin-bottom rides
   on the wrap padding. Every card type gets both (the CTA slot is shared),
   so row baselines move together and stay equal. */
@media (max-width: 520px) {
	body.kc-rd ul.nm-products li.product .nm-shop-loop-details { padding-top: 4px; }
	body.kc-rd .kc-buy-card .kc-cta { margin-bottom: 5px; }
}

/* --- 25.45 Revive the 2-line clamp: the title was a flex item -------------- */
/* §23.1 (kc-redesign.css) puts display:-webkit-box + -webkit-line-clamp:2 on
   the title, but the LEGACY skin (savoy-child assets/css/main.css:359) makes
   .nm-shop-loop-details a flex column — and a direct flex item gets
   blockified: Chrome computes the title back to flow-root and the clamp
   silently dies (the exact gotcha 30-side-cart.css:272 paid for). Measured:
   titleDisplay=flow-root, so every 3-line name HARD-CLIPS at the 36px box —
   mid-descender, no ellipsis. That chopped look is the owner's complaint as
   much as the width is. Details is natively block in Savoy; the flex came
   from the legacy skin (its calc height is already voided by §25.2). Block
   restores it native, the -webkit-box revives, long names get a real "…".
   Phone scope only — desktop's dead clamp is the same bug, reported for
   orchestrator (§23.1's own file), not fixed from here. */
@media (max-width: 520px) {
	body.kc-rd ul.nm-products li.product .nm-shop-loop-details { display: block; }
}

/* --- 25.5 Card titles are Poppins EVERYWHERE (brand-law bug fix) ---------- */
/* The core title rule (kc-redesign.css:133) sets color/size/weight but
   never font-family. Category pages inherit Poppins from a container rule
   by luck; the home rails have no such luck and render the theme's Roboto
   stack → system sans on iOS. Same component, two faces — a brand-law
   violation ("产品名一律 Poppins"), measured in nn-card-recon.md §5. The
   base rule declares no family, so this is additive at equal specificity,
   later file: no !important needed. All viewports on purpose — the desktop
   home rails leak too. Proper home is kc-redesign.css:133 (reported). */
body.kc-rd ul.nm-products li.product h3,
body.kc-rd ul.nm-products li.product h3 a,
body.kc-rd ul.nm-products li.product a.nm-shop-loop-title-link,
body.kc-rd ul.nm-products li.product .nm-shop-loop-title,
body.kc-rd ul.nm-products li.product .woocommerce-loop-product__title {
	font-family: var(--kc-font-body);
}

/* ===== 26-cat-final.css ===== */
/* ==========================================================================
   26 — Category page final (owner: catimpl / category-final implementation)
   Owner-approved 2026-08-25 three-piece set:
     1. Title ledger band — product count inside a double hairline, AA fix
        (faint 2.9:1 → muted 7:1), Read more to a real 44px target.
     2. Shelf skeleton — inc-catalog.php renders .kc-shelf-li separators
        inside ul.nm-products; the grid converts to flex so full-width rows
        are legal (the stock float grid clears on :nth-of-type(4n+1), which
        counts EVERY <li> and would shatter as soon as a separator lands).
     3. B+D chip nav — sticky grade chips (26-cat-final.js builds them from
        the shelf headers; jump only, no URLs), slim collapse past ~70px,
        strain focus + Sort in a bottom ink dock (phone), one instrument
        band with the existing sort pill (desktop).
   Brand law: ink = system state (scroll highlight, default All/Featured);
   pine appears ONLY on a state the customer picked. Amber stays reserved
   for Add to cart. Cards are never touched — .kc-hide is an <li>-level
   runtime class (strain focus hides non-matches; owner call 2026-08-26).
   ========================================================================== */

/* --------------------------------------------------------------------------
   26.1 Title ledger band + AA fix + Read more hit area.
   Count color: 22-catalog-head.css sets --text-faint (2.9:1, fails AA);
   same-specificity override here (later file wins) to --text-muted (7:1).
   -------------------------------------------------------------------------- */
body.kc-rd .kc-cat-count {
	max-width: 420px;
	margin: 0 auto;
	padding: 6px 0;
	border-top: 1px solid var(--text);
	border-bottom: 1px solid var(--border-strong);
	color: var(--text-muted);
}
body.kc-rd .kc-cat-desc-more {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	padding: 0 14px;
	margin-top: 0;
}

/* --------------------------------------------------------------------------
   26.2 Grid: float block-grid → flex (archive catalog only).
   Savoy's [class*="block-grid-"] li float + :nth-of-type clears count every
   <li>; the shelf separators would offset the count and break rows. Flex
   ignores float/clear, keeps the li width % from the same block-grid rules,
   and align-items:flex-start preserves today's natural card heights.
   The stock :before/:after clearfix (display:table) would become phantom
   flex items — removed.
   -------------------------------------------------------------------------- */
body.kc-rd.archive #nm-shop ul.nm-products {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
}
body.kc-rd.archive #nm-shop ul.nm-products::before,
body.kc-rd.archive #nm-shop ul.nm-products::after {
	display: none;
	content: none;
}

/* --------------------------------------------------------------------------
   26.3 Shelf headers (rendered by inc-catalog.php section 10).
   Real term name + ledger line (count · from $X), 2px ink baseline —
   the demo's shelf voice. Full-width row in the flex grid.
   -------------------------------------------------------------------------- */
body.kc-rd.archive #nm-shop ul.nm-products > li.kc-shelf-li {
	flex: 0 0 100%;
	width: 100%;
	float: none;
	padding: 22px 15px 12px;
}
body.kc-rd.archive #nm-shop ul.nm-products > li.kc-shelf-li:first-child {
	padding-top: 4px;
}
body.kc-rd .kc-shelf-head {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 12px;
	border-bottom: 2px solid var(--text);
	padding-bottom: 7px;
}
body.kc-rd .kc-shelf-title {
	margin: 0;
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	line-height: 1.3;
	color: var(--text);
}
body.kc-rd .kc-shelf-sub {
	font-family: var(--kc-font-body);
	font-size: 11px;
	font-weight: 500;
	line-height: 1.4;
	color: var(--text-muted);
	white-space: nowrap;
	font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   26.4 Out-of-stock rule: a labelled hairline before the sunk tail
   (owner's pick, category-final A/B ②-A). No grayscale added — the cards
   below already carry their own out-of-stock treatment.
   -------------------------------------------------------------------------- */
body.kc-rd.archive #nm-shop ul.nm-products > li.kc-oos-li,
body.kc-rd.archive #nm-shop ul.nm-products > li.kc-shelf-break {
	padding: 30px 15px 14px;
}
body.kc-rd .kc-oos-rule,
body.kc-rd .kc-shelf-rule {
	display: flex;
	align-items: center;
	gap: 12px;
}
body.kc-rd .kc-oos-rule::before,
body.kc-rd .kc-oos-rule::after,
body.kc-rd .kc-shelf-rule::before,
body.kc-rd .kc-shelf-rule::after {
	content: "";
	height: 1px;
	background: var(--border-strong);
	flex: 1 1 auto;
}
body.kc-rd .kc-oos-label,
body.kc-rd .kc-shelf-rule-label {
	font-family: var(--kc-font-body);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--text-muted);
	white-space: nowrap;
}

/* --------------------------------------------------------------------------
   26.5 Chip nav (JS-built; buttons only, zero hrefs).
   Sticky below the fixed header stack — 26-cat-final.js measures
   #nm-header's live bottom edge into --kc-chipnav-top (the header both
   floats over a fixed marquee and shrinks on scroll, so a constant lies).
   Ink active chip = where the scroll is (system state, not a choice).
   -------------------------------------------------------------------------- */
/* NOT position:sticky — Savoy wraps every page in .nm-page-overflow
   { overflow:hidden }, which turns that div into the sticky scrollport:
   the nav pinned itself 166px into the wrapper, left a void in the flow
   and floated over the第一 card row (measured). 26-cat-final.js pins it
   instead: .kc-fixed + a spacer holding the flow slot, top/left/width
   re-measured per frame against the fixed header stack. */
body.kc-rd .kc-chipnav {
	position: relative;
	z-index: 60;
	background: var(--surface);
	border-bottom: 1.5px solid var(--text);
	margin: 0 0 16px;
	padding: 2px 0 8px;
}
body.kc-rd .kc-chipnav.kc-fixed {
	position: fixed;
	z-index: 70;
	margin: 0;
}
body.kc-rd .kc-chipnav-label {
	display: block;
	padding: 8px 15px 6px;
	font-family: var(--kc-font-body);
	font-size: 9.5px;
	font-weight: 600;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: var(--text-faint);
}
body.kc-rd .kc-chipnav-row {
	display: flex;
	gap: 7px;
	overflow-x: auto;
	padding: 0 15px;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
	scroll-padding-left: 15px;
}
body.kc-rd .kc-chipnav-row::-webkit-scrollbar { display: none; }
body.kc-rd .kc-chip {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	gap: 5px;
	min-height: 44px;
	padding: 8px 14px;
	border: 1.5px solid var(--text);
	border-radius: 999px;
	background: var(--surface-raised);
	color: var(--text);
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	font-weight: 600;
	white-space: nowrap;
	cursor: pointer;
}
/* Explicit, not inherited: a theme-level span rule paints spans ink-0.7,
   which on the active ink pill rendered the label invisible (measured). */
body.kc-rd .kc-chip span { color: inherit; }
body.kc-rd .kc-chip small {
	font-size: 11px;
	font-weight: 500;
	color: var(--text-faint);
	font-variant-numeric: tabular-nums;
}
body.kc-rd .kc-chip:focus-visible { outline: 2px solid var(--trust); outline-offset: 2px; }
body.kc-rd .kc-chip.kc-on {
	background: var(--text);
	border-color: var(--text);
	color: var(--surface);
}
body.kc-rd .kc-chip.kc-on small { color: var(--surface); opacity: .68; }

/* Slim: past ~70px the label row folds away; the chips stay full 44px
   touch targets, so the collapsed bar is one thumbable row. */
body.kc-rd .kc-chipnav.kc-slim .kc-chipnav-label { display: none; }
body.kc-rd .kc-chipnav.kc-slim { padding-top: 4px; padding-bottom: 5px; }

/* Chip nav replaces the quick-chip links visually while JS drives it; the
   links stay server-rendered in the DOM (crawler set unchanged, no-JS
   fallback keeps them on screen). */
body.kc-chipnav-on .kc-quick-chips { display: none; }

/* Leaf pages raise a band with only strain focus + the sort pill; on the
   phone both live in the dock instead, so the band is desktop-only. The
   strain group itself is desktop-only too (the dock owns strain focus on
   phone) — §26.9 turns it back on above 992px. */
body.kc-rd .kc-desk-types { display: none; }
@media (max-width: 991px) {
	body.kc-rd .kc-chipnav.kc-bandonly { display: none; }
}

/* --------------------------------------------------------------------------
   26.6 Bottom ink dock (phone only): strain focus + Sort in ONE bar.
   6px padding + 44px controls = 56px total. All buttons ≥44px tall.
   All/idle = quiet ink field; a customer-picked strain = pine (.kc-choice).
   The Filters slot from the category-final board is intentionally absent:
   staging's phone view has no filter panel to open (sidebar collapses to
   display:none) — a button with no destination would be a dead control.
   Reported for when the §3.6 inventory work lands one.
   -------------------------------------------------------------------------- */
body.kc-rd .kc-dock {
	display: none;
}
@media (max-width: 991px) {
	body.kc-rd.kc-dock-on .kc-dock {
		position: fixed;
		left: 10px;
		right: 10px;
		bottom: 10px;
		z-index: 1200;
		display: flex;
		gap: 4px;
		background: var(--text);
		border-radius: 999px;
		padding: 6px;
		box-shadow: 0 8px 24px -8px rgba(31, 26, 23, .55);
	}
	body.kc-rd.kc-dock-on { padding-bottom: 76px; }
	/* Lift the WhatsApp bubble (inline-styled fixed bottom:10px) clear of
	   the dock; !important is the only way past an inline style. */
	body.kc-rd.kc-dock-on #ht-ctc-chat { bottom: 78px !important; }
	/* The dock owns Sort on phone; the head pill returns when JS is off. */
	body.kc-rd.kc-dock-on .archive-page-sort-by { display: none; }
}
body.kc-rd .kc-dock button {
	flex: 1 1 0;
	min-height: 44px;
	min-width: 44px;
	border: 0;
	border-radius: 999px;
	background: transparent;
	color: var(--surface);
	font-family: var(--kc-font-body);
	font-size: 12px;
	font-weight: 600;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: 0 4px;
	cursor: pointer;
	white-space: nowrap;
}
body.kc-rd .kc-dock button:focus-visible { outline: 2px solid var(--surface); outline-offset: -2px; }
body.kc-rd .kc-dock .kc-dock-type.kc-on {
	background: var(--surface);
	color: var(--text);
}
body.kc-rd .kc-dock .kc-dock-type.kc-on.kc-choice {
	background: var(--trust);
	color: var(--on-trust);
}
body.kc-rd .kc-dock-divider {
	flex: 0 0 1px;
	width: 1px;
	margin: 6px 2px;
	background: var(--surface);
	opacity: .25;
}
body.kc-rd .kc-dock-sort::after {
	content: "";
	width: 7px;
	height: 7px;
	margin-top: -4px;
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: rotate(45deg);
	flex: 0 0 auto;
}

/* --------------------------------------------------------------------------
   26.7 Sort drawer (phone): proxies the native orderby select — six
   existing values, zero new URL grammar. Current value: quiet ink when it
   is the store default state, pine only when the customer picked it.
   -------------------------------------------------------------------------- */
body.kc-rd .kc-sheet-backdrop {
	position: fixed;
	inset: 0;
	z-index: 1300;
	background: rgba(31, 26, 23, .45);
	opacity: 0;
	pointer-events: none;
	transition: opacity .22s ease;
}
body.kc-rd .kc-sheet-backdrop.kc-open { opacity: 1; pointer-events: auto; }
body.kc-rd .kc-sheet {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 1301;
	background: var(--surface);
	border-top: 1.5px solid var(--text);
	border-top-left-radius: 16px;
	border-top-right-radius: 16px;
	padding: 14px 16px calc(16px + env(safe-area-inset-bottom, 0px));
	transform: translateY(102%);
	transition: transform .26s ease;
}
body.kc-rd .kc-sheet.kc-open { transform: none; }
body.kc-rd .kc-sheet-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	margin-bottom: 10px;
}
body.kc-rd .kc-sheet-title {
	font-family: var(--kc-font-body);
	font-size: 12px;
	font-weight: 600;
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--text-muted);
}
body.kc-rd .kc-sheet-close {
	width: 44px;
	height: 44px;
	margin: -8px -8px -8px 0;
	border: 0;
	background: none;
	color: var(--text);
	font-size: 22px;
	line-height: 1;
	cursor: pointer;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
body.kc-rd .kc-sheet-close:focus-visible,
body.kc-rd .kc-sheet-opt:focus-visible { outline: 2px solid var(--trust); outline-offset: 2px; }
body.kc-rd .kc-sheet-opt {
	display: flex;
	align-items: center;
	justify-content: space-between;
	width: 100%;
	min-height: 48px;
	margin-bottom: 8px;
	padding: 0 14px;
	background: var(--surface-raised);
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 500;
	color: var(--text);
	cursor: pointer;
	text-align: left;
}
body.kc-rd .kc-sheet-opt.kc-cur { border-width: 2px; border-color: var(--text); font-weight: 600; }
body.kc-rd .kc-sheet-opt.kc-cur.kc-choice { border-color: var(--trust); }
body.kc-rd .kc-sheet-opt.kc-cur::after {
	content: "";
	width: 10px;
	height: 6px;
	margin-left: 12px;
	border-left: 2px solid var(--text);
	border-bottom: 2px solid var(--text);
	transform: rotate(-45deg) translateY(-2px);
	flex: 0 0 auto;
}
body.kc-rd .kc-sheet-opt.kc-cur.kc-choice::after {
	border-color: var(--trust);
}

/* --------------------------------------------------------------------------
   26.8 Strain focus: non-matching cards are HIDDEN (owner call 2026-08-26 —
   the earlier 28% dim "wastes the phone user's scroll"). Runtime class on
   the <li> only; card markup untouched. A shelf with no match collapses to
   its header (title + count line stay) plus one note ("No Indica here",
   owner's own phrasing); .kc-focus-empty is the same note as a full-width
   row for headerless (leaf) grids. All restores everything.
   -------------------------------------------------------------------------- */
body.kc-rd.archive #nm-shop ul.nm-products > li.product.kc-hide,
body.kc-rd.archive #nm-shop ul.nm-products > li.kc-shelf-li.kc-hide {
	display: none;
}
body.kc-rd .kc-shelf-note {
	margin: 10px 0 0;
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	font-weight: 500;
	line-height: 1.4;
	color: var(--text-muted);
}
body.kc-rd.archive #nm-shop ul.nm-products > li.kc-shelf-li.kc-shelf-empty {
	padding-bottom: 18px;
}
body.kc-rd.archive #nm-shop ul.nm-products > li.kc-focus-empty {
	padding-top: 8px;
}
@media (prefers-reduced-motion: reduce) {
	body.kc-rd .kc-sheet,
	body.kc-rd .kc-sheet-backdrop { transition: none; }
}

/* --------------------------------------------------------------------------
   26.9 Desktop (secondary end): the instrument band — double hairline,
   chips left, strain focus centre-right, the EXISTING sort pill docked at
   the right edge (26-cat-final.js moves the .archive-page-sort-by node in;
   no-JS keeps today's layout). No bottom dock on desktop.
   -------------------------------------------------------------------------- */
@media (min-width: 992px) {
	body.kc-rd .kc-chipnav {
		display: flex;
		align-items: center;
		gap: 10px;
		border-top: 1px solid var(--border-strong);
		padding: 7px 0;
	}
	body.kc-rd .kc-chipnav-label { padding: 0 0 0 15px; flex: 0 0 auto; }
	body.kc-rd .kc-chipnav.kc-slim .kc-chipnav-label { display: block; }
	body.kc-rd .kc-chipnav-row { flex: 1 1 auto; }
	body.kc-rd .kc-chipnav .kc-desk-types {
		display: flex;
		gap: 6px;
		flex: 0 0 auto;
		padding-right: 4px;
	}
	body.kc-rd .kc-desk-types .kc-dock-type {
		min-height: 44px;
		padding: 8px 12px;
		border: 1.5px solid var(--border-strong);
		border-radius: 999px;
		background: var(--surface-raised);
		color: var(--text);
		font-family: var(--kc-font-body);
		font-size: 12px;
		font-weight: 600;
		cursor: pointer;
	}
	body.kc-rd .kc-desk-types .kc-dock-type:focus-visible { outline: 2px solid var(--trust); outline-offset: 2px; }
	body.kc-rd .kc-desk-types .kc-dock-type.kc-on { background: var(--text); border-color: var(--text); color: var(--surface); }
	body.kc-rd .kc-desk-types .kc-dock-type.kc-on.kc-choice { background: var(--trust); border-color: var(--trust); color: var(--on-trust); }
	body.kc-rd .kc-chipnav .archive-page-sort-by {
		margin: 0;
		padding: 0 15px 0 0;
		flex: 0 0 auto;
	}
}

/* --------------------------------------------------------------------------
   26.10 Sticky category rail (F34).

   The rule that was supposed to do this lives at kc-redesign.css:566-571 as
   `body.kc-rd #nm-shop-sidebar > .nm-shop-sidebar { position: sticky; top: 96px }`
   and it has never applied to anything: the live element IS
   `div#nm-shop-sidebar.nm-shop-sidebar` — one node, not a parent and a child —
   so that selector matches zero nodes on every catalog page. Measured
   2026-08-28: computed position `static`. It should be deleted; it is left in
   place here only because this file does not own it.

   Two more things had to be true, and neither was:

   1. A SCROLLPORT THAT SCROLLS. Savoy wraps every page in
      `.nm-page-wrap { overflow: hidden }`, which makes that div the sticky
      element's scrollport. It never scrolls, so a sticky descendant never
      offsets — the same trap 26.5 documents for the chip nav, which is why
      the chip nav is JS-pinned `position: fixed` instead. `overflow: clip`
      clips identically but does NOT create a scroll container, so one word
      brings native sticky back with no JS and no pinning arithmetic. A
      browser too old for `clip` drops the declaration, keeps `hidden`, and
      gets exactly today's behaviour.

   2. A RUNWAY. Sticky travels inside its containing block, and
      `#nm-shop-sidebar` sits in `.nm-shop-sidebar-col` — 689px on
      /product-category/weed/, 509px on /product-category/edible/ (the height
      the audit measured), against a 4,745px row. Making the COLUMN the sticky
      element instead moves the containing block up to `.nm-row`, which is as
      tall as the grid, and costs no layout change at all: the column keeps
      its float, its width and its natural height. Measured after: rail pinned
      at 118px from y=400 through y=4,000 and released with the grid; grid x
      and width, column height, document width and page height all identical
      to before.

   118px = the live bottom of the fixed #nm-header (110px, constant at every
   scroll offset — it does not shrink) plus an 8px breath. The chip nav pins
   at the same 110px but inside the GRID column's box, so the two never meet.

   The rail's own overflow cap comes from `.kc-catnav` (kc-redesign.css:1035,
   `max-height: calc(100vh - 120px)`), which was written for a rail that
   started at the top of the page. Pinned at 118px with 46px of column chrome
   that runs 44px past the fold on a 700px-tall viewport, so it is re-based
   here on the same offsets. Phone is untouched: the rail is display:none
   below 992px. */
@media (min-width: 992px) {
	body.kc-catalog .nm-page-wrap { overflow: clip; }
	body.kc-catalog #nm-shop-products > .nm-row > .nm-shop-sidebar-col {
		position: sticky;
		top: 118px;
	}
	body.kc-catalog #nm-shop-sidebar .kc-catnav { max-height: calc(100vh - 180px); }
}

/* --------------------------------------------------------------------------
   26.11 Quantity tiers the stock cannot honour (F31).
   inc-catalog.php section 6c publishes WooCommerce's own
   get_max_purchase_quantity() for the cards on the page; 26-cat-final.js
   disables the tiers above it. Disabled is a real `disabled` attribute, so
   the delegated handler in kc-redesign.js cannot fire on it — this is only
   what that state LOOKS like, never what enforces it.
   -------------------------------------------------------------------------- */
body.kc-rd .kc-buy .kc-tier.kc-tier-cap {
	opacity: .38;
	cursor: not-allowed;
}
body.kc-rd .kc-buy .kc-tier.kc-tier-cap .kc-tier-p,
body.kc-rd .kc-buy .kc-tier.kc-tier-cap .kc-tier-w {
	text-decoration: line-through;
}
body.kc-rd .kc-buy .kc-save.kc-cap-note {
	color: var(--text-muted);
	font-weight: 600;
}

/* ===== 28-pdp-void.css ===== */
/* ==========================================================================
   28 — pdp-void (owner #3): non-flower buy-column tail fill
   Rendering is decided server-side in inc-pdp.php (pages with no Feels-like
   pills only); this file styles the two new blocks. Same 460px column
   discipline as .kc-acc / .kc-facts.
   ========================================================================== */

/* --- Free-gift ladder teaser: informational panel, fact-strip family ----- */
body.kc-rd.single-product .kc-pv-gift {
	max-width: 460px;
	margin: 18px 0 0;
	padding: 13px 16px 10px;
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
	background: var(--surface-raised);
}
body.kc-rd.single-product .kc-pv-lab {
	display: flex;
	align-items: center;
	gap: 7px;
	margin: 0 0 5px;
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--trust);
}
body.kc-rd.single-product .kc-pv-lab svg { flex: 0 0 auto; }
body.kc-rd.single-product .kc-pv-rows {
	list-style: none;
	margin: 0;
	padding: 0;
}
body.kc-rd.single-product .kc-pv-row {
	display: flex;
	align-items: baseline;
	gap: 10px;
	margin: 0;
	padding: 4px 0;
	font-size: 13px;
	line-height: 1.45;
}
body.kc-rd.single-product .kc-pv-min {
	flex: 0 0 48px;
	font-weight: 600;
	color: var(--text);
	font-variant-numeric: tabular-nums;
}
body.kc-rd.single-product .kc-pv-name {
	min-width: 0;
	color: var(--text-muted);
}

/* --- Brand archive link: a link, not a control --------------------------- */
body.kc-rd.single-product .kc-pv-brand {
	max-width: 460px;
	margin: 4px 0 0;
}
body.kc-rd.single-product .kc-pv-brand a {
	display: inline-block;
	padding: 13px 0; /* ~45px tap height on a one-line link */
	font-size: 13.5px;
	font-weight: 600;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-color: var(--border-strong);
}
body.kc-rd.single-product .kc-pv-brand a:hover {
	color: var(--trust);
	text-decoration-color: currentColor;
}

/* ==========================================================================
   28.2 — PDP STATE MACHINE (rootfix-pdp-state, 2026-08-28)

   Server side decides the state once (kc_rd_pdp_state in inc-pdp.php) and
   renders three slots for every one of them. This file only dresses the
   states that had no styling at all because they used to render nothing.

   Nothing here uses !important and nothing here overrides a rule in
   kc-redesign.css: every selector below is either a brand-new class or a
   class the old selectors deliberately do not match.
   ========================================================================== */

/* --- The dead-end buy zone: price / status / way out --------------------- */
body.kc-rd.single-product .kc-void {
	max-width: 460px;
	margin: 0 0 4px;
}

/* form.cart:has(.kc-sizelab) .kc-pline sets 440px, and this price line lives
   OUTSIDE the form (that is the whole point — the form is the thing that does
   not exist here), so the width has to be stated rather than inherited. */
body.kc-rd.single-product .kc-pline-void {
	max-width: 440px;
	margin: 14px 0 0;
}
/* A price you cannot act on is reference, not an offer: same size and
   position as the real one so the page reads the same, one step back in ink. */
body.kc-rd.single-product .kc-pline-void .kc-pline-price,
body.kc-rd.single-product .kc-pline-void .kc-pline-price .amount,
body.kc-rd.single-product .kc-pline-void .kc-pline-price bdi {
	color: var(--text-muted);
}

/* Status slot. WooCommerce's own wording; ours is the only visible copy of
   it (the theme's p.stock stays hidden by kc-redesign.css:2560, so there is
   exactly one out-of-stock statement on the page). */
body.kc-rd.single-product .kc-void-stat {
	display: block;
	margin: 6px 0 0;
	font-size: 15px;
	font-weight: 600;
	line-height: 1.35;
	color: var(--urgent);
}

/* Action slot. A link is allowed to look like a link; it must not look like
   a buy control — no border, no fill, no shadow, no hover lift. */
body.kc-rd.single-product .kc-void-exit {
	margin: 2px 0 0;
	max-width: 440px;
}
body.kc-rd.single-product .kc-void-exit a {
	display: inline-block;
	padding: 13px 0;            /* ~45px tap height on a one-line link */
	font-size: 13.5px;
	font-weight: 600;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-color: var(--border-strong);
}
body.kc-rd.single-product .kc-void-exit a:hover {
	color: var(--trust);
	text-decoration-color: currentColor;
}

/* --- Single non-weight option: the identity, stated (audit M53) ---------- */
/* The chip row still ships for WooCommerce's variation JS, but a lone chip
   that cannot be changed is a control that does nothing. Only Add to cart
   may look tappable, so the identity moves to the label. .kc-tiers is
   display:flex at (0,2,1); this is (0,4,1) and wins with no !important. */
body.kc-rd.single-product .kc-tiers.kc-tiers-id { display: none; }

/* kc-redesign.css leaves .kc-sizelab at 11px / .08em / muted — right for a
   slot LABEL ("Choose a size"), far too quiet for the only place the page
   says what you are buying. */
body.kc-rd.single-product .summary .kc-sizelab.kc-sizelab-id {
	font-size: 12px;
	letter-spacing: .10em;
	color: var(--text-faint);
	margin: 14px 0 0;
}
body.kc-rd.single-product .summary .kc-sizelab.kc-sizelab-id .kc-sizelab-v {
	display: block;
	margin-top: 2px;
	font-size: 17px;
	font-weight: 600;
	letter-spacing: 0;
	text-transform: none;
	color: var(--text);
}

/* --- Sticky bar amount (renamed from .kc-sticky-price, see inc-pdp.php) --- */
/* Migrated from kc-redesign.css:353-360 + :2695, which now match nothing.
   The !important there existed to beat Savoy's colouring of .amount / bdi /
   del / ins inside a price blob; re-tested at 375px on 35280 before keeping
   or dropping each one — see docs/redesign/rootfix-pdp-state.md. */
body.kc-rd .kc-sticky-atc .kc-sticky-amt,
body.kc-rd .kc-sticky-atc .kc-sticky-amt .price,
body.kc-rd .kc-sticky-atc .kc-sticky-amt .amount,
body.kc-rd .kc-sticky-atc .kc-sticky-amt bdi {
	font-family: var(--kc-font-body);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
	font-size: 15px;
	color: var(--text);
}
body.kc-rd .kc-sticky-atc .kc-sticky-amt del,
body.kc-rd .kc-sticky-atc .kc-sticky-amt del .amount,
body.kc-rd .kc-sticky-atc .kc-sticky-amt del bdi {
	font-weight: 400;
	color: var(--text-muted);
}
body.kc-rd .kc-sticky-atc .kc-sticky-amt ins,
body.kc-rd .kc-sticky-atc .kc-sticky-amt ins .amount,
body.kc-rd .kc-sticky-atc .kc-sticky-amt ins bdi {
	text-decoration: none;
	color: var(--urgent);
}
/* "From $55.00" / "$20.00 - $25.00" wrappers the pricing plugin emits */
body.kc-rd .kc-sticky-atc .kc-sticky-amt .screen-reader-text {
	position: absolute;
	width: 1px; height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}

/* --- Cross-sell moved out of the hidden tab pane (audit M51) ------------- */
/* Same envelope as .kc-bento (kc-redesign.css:2865) so the rail lines up
   with the cards above it instead of starting at a third left edge. */
body.kc-rd.single-product .kc-xsell {
	max-width: 1080px;
	margin: 8px auto 40px;
	padding: 0 20px;
}
body.kc-rd.single-product .kc-xsell[hidden] { display: none; }
/* The theme's block-grid keeps its -15px side margins (they carry an
   !important we are not going to stack on). Cancel them on the wrapper
   instead, so the first card's edge lands on the heading's edge. */
body.kc-rd.single-product .kc-xsell-slot { padding: 0 15px; }
body.kc-rd.single-product .kc-xsell-h {
	margin: 0 0 18px;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--text-muted);
}
/* The theme's Foundation block-grid assumes a container with its own 15px
   gutters and, measured at 375px, sizes itself to 375px inside a 285px
   content box — the second column then hangs 21px past the viewport and is
   clipped by an ancestor's overflow (the page itself still reports 0
   horizontal overflow, which is exactly why this was invisible to the
   overflow check). Own the grid here instead of inheriting an assumption:
   explicit tracks cannot disagree with the container they are in. */
body.kc-rd.single-product .kc-xsell-slot ul.products {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 26px 22px;
	width: auto;
	max-width: 100%;
	margin: 0;          /* the theme block-grid ships -15px side margins,
	                       which put the cards 15px left of their own heading */
	padding: 0;
	list-style: none;
}
/* The theme's clearfix ::before/::after become real grid items and ate the
   first cell — the rail started in column 2 with a hole beside it. */
body.kc-rd.single-product .kc-xsell-slot ul.products::before,
body.kc-rd.single-product .kc-xsell-slot ul.products::after {
	content: none;
}
/* Descendant, not child. The <ul> reaches this rail as either a plain list
   or a Slick carousel (see the ownership note in inc-pdp.php): a child
   combinator quietly matches nothing in the carousel shape, and "quietly
   matches nothing" is how the 59px cards shipped. Same specificity, one
   less assumption. */
body.kc-rd.single-product .kc-xsell-slot ul.products li.product {
	width: auto;
	max-width: 100%;
	margin: 0;
	padding: 0;
	float: none;
}
/* Belt to the JS braces: if any Slick wrapper ever survives the hand-back,
   dissolving it keeps the cards as direct grid items instead of stacking
   them all into cell one. A clone would be a duplicate product, so it goes
   rather than lays out. */
body.kc-rd.single-product .kc-xsell-slot ul.products .slick-list,
body.kc-rd.single-product .kc-xsell-slot ul.products .slick-track {
	display: contents;
}
body.kc-rd.single-product .kc-xsell-slot ul.products .slick-cloned,
body.kc-rd.single-product .kc-xsell-slot ul.products .slick-dots,
body.kc-rd.single-product .kc-xsell-slot ul.products .slick-arrow {
	display: none;
}
@media (max-width: 900px) {
	body.kc-rd.single-product .kc-xsell-slot ul.products {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 20px 14px;
	}
}
/* Below 520px kc-redesign.css already gives every .nm-products card an
   !important 8px side gutter and a 14px bottom margin (the category-grid
   rhythm). Adding a grid gap on top would double it and make the cross-sell
   rail look different from the category page it links into, so the gap goes
   to zero and the existing rhythm is the one that shows. */
@media (max-width: 520px) {
	body.kc-rd.single-product .kc-xsell-slot ul.products { gap: 0; }
}

/* --- One-size cards on this rail must still name their price (fix2) ------
   Two rules that are each correct where they were written meet on this page
   and cancel a price to nothing:

     kc-redesign.css:2818  body.kc-rd.single-product .kc-tiers:has(.kc-tier-solo)
                           { display: none !important }
     kc-redesign.css:2265  @media (min-width:521px)
                           .kc-buy-card:has(.kc-tier:not(.kc-tier-link)) .kc-pline
                           { display: none }

   :2818 is about the BUY BOX — one size, so the fused "$120.00 /28G" on the
   price line already says it and a lone chip would be a control that controls
   nothing. But it is scoped to the PAGE, not to the component, so the moment
   the M51 rail put product CARDS on a single-product page they inherited a
   rule written about a different element. :2265 then hides the card's own
   price line because the card "has chips" — and it does, one, invisible.
   Result, desktop only: Death Diesel / Chemdog / Candy Floss show "$14.29/g"
   and a button, and nowhere the $100.00. Phone is fine (:2265 is desktop-only)
   and the SAME card on /product-category/weed/1-oz-deals/ is fine (:2818 needs
   body.single-product), which is what makes it look like a card bug when it is
   a scope bug.

   :2818 carries !important and is not this package's file, so the chip cannot
   come back from here without stacking one (banned). The card keeps its price
   the other way instead: give the price line back, since :2265 has NO
   !important and this selector out-specifies it. Same facts as the category
   card, same order — absolute price, then $/g under the CTA — and the price
   is still said exactly once, because the chip that would have repeated it is
   the very thing that is hidden.
   Orchestrator: the root fix is re-scoping :2818 to the summary column
   (`.summary .kc-tiers:has(.kc-tier-solo)`); it was never meant to reach a
   product card. Reported, not stacked. */
@media (min-width: 521px) {
	body.kc-rd.single-product .kc-xsell-slot .kc-buy-card:has(.kc-tiers .kc-tier-solo) .kc-pline {
		display: flex;
	}
	/* $/g already has a slot on the card (.kc-save, under the CTA row) and it
	   is visible at this width — the price line's own copy would be the same
	   number twice on one card. Say it once, in the slot every other card in
	   the rail uses, so the rail keeps one baseline. */
	body.kc-rd.single-product .kc-xsell-slot .kc-buy-card:has(.kc-tiers .kc-tier-solo) .kc-pline .kc-pline-pergram {
		display: none;
	}
}

/* --- The description pane, on the same grid as everything else (M54) ----- */
/* Root cause of "aligned to nothing": kc-redesign.css:1183 gives every
   [class*="custom-product-tab-"] `max-width:72ch; margin:auto`, which centres
   the pane in the full 1280px body — a container the redesign uses nowhere
   else. The bento above it lives in the .kc-bento envelope (:2865), so the
   two blocks had different left edges and different widths by construction.
   Put the pane in the SAME envelope and keep the reading measure inside it:
   the text now starts on the bento's left edge at every viewport.
   Specificity (0,3,1) vs (0,2,1) — no !important needed. */
body.kc-rd.single-product .custom-product-tab-info,
body.kc-rd.single-product .custom-product-tab-reviews {
	box-sizing: border-box;
	max-width: 1080px;
	margin-left: auto;
	margin-right: auto;
	padding-left: 20px;
	padding-right: 20px;
}
body.kc-rd.single-product .custom-product-tab-info > * {
	max-width: 72ch;
}
/* tables and media may use the full column */
body.kc-rd.single-product .custom-product-tab-info > table,
body.kc-rd.single-product .custom-product-tab-info > img,
body.kc-rd.single-product .custom-product-tab-info > figure {
	max-width: 100%;
}

/* A pane with headings and NO paragraphs is not a document outline — it is
   body copy that was typed into heading tags years ago (7 products, e.g.
   /?p=15160: 13 <h2>, 0 <p>, including whole sentences and literal
   "--- SATIVA ---" separators). Detect that shape rather than listing IDs,
   so a future product with the same authoring lands in the same branch and
   a genuine article never does.

   Only the properties kc-redesign.css does NOT mark !important are touched:
   the 24px at kc-redesign.css:1201 carries !important and out-shouting it
   would be exactly the layering this project has already paid for three
   times. Flagged for the orchestrator instead — see the report. */
body.kc-rd.single-product .custom-product-tab-info:has(h2):not(:has(p)) h2 {
	font-weight: 600;
	line-height: 1.35;
	margin: 16px 0 4px;
	letter-spacing: 0;
}

/* When there is no price to lead with, the status takes the lead position so
   the buy column still starts with a statement instead of a gap. */
body.kc-rd.single-product .kc-void-stat.kc-void-stat-lead {
	margin: 14px 0 0;
	font-size: 19px;
	font-weight: 700;
}

/* ===== 29-home-hero.css ===== */
/* ===========================================================================
   29-home-hero — front-page hero headline lockup + USP strip
   Owner: hero-usp agent.  Loads after kc-redesign.css §29, so equal-specificity
   selectors here win without !important (parts are enqueued last, see
   kc-redesign.php).

   Why this file exists
   -------------------
   The front page carried a brand-voice H1 ("The corner store, grown up.")
   while production's keyword H1 ("Fast Weed Delivery") — the one Google has
   been ranking for six years — was gone. This store is 100% organic traffic,
   so that swap was load-bearing. inc-home.php now emits the production H1
   verbatim; this file gives it the display treatment and demotes the brand
   line to a sub-tier that still reads as the voice of the page.
   =========================================================================== */

/* --- 29a. headline lockup ------------------------------------------------ */

/* "Fast Weed Delivery" is 18 characters against the old headline's 26, so it
   can carry more size in the same box. Fluid so the 1-vs-2 line break is a
   measurement, not a guess: at 1280 it lands on two lines ("Fast Weed /
   Delivery"), which is the lockup we want, and it never overflows the column. */
body.home .kc-hero-light .kc-hero-h {
	font-size: clamp(38px, 4.3vw, 56px);
	letter-spacing: -.01em;
	max-width: 11ch;          /* holds the two-line break without a <br>,
	                             which would break a literal grep for the H1 */
}

/* The brand line, one tier down. Explicit font-weight is not optional: the
   theme's Meyer-style reset lists `b` in its `font: inherit` shorthand, which
   silently flattens <b> to weight 400 — measured on the old USP strip, where
   both lines rendered at 400 and the hierarchy was literally zero. */
body.home .kc-hero-tag {
	display: block;
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 17px;
	line-height: 1.35;
	color: var(--text);
	margin: 0 0 6px;
}

/* the paragraph now opens with the tag block, so it needs less top air */
body.home .kc-hero-light .kc-hero-p { margin-top: 14px; }

@media (max-width: 767px) {
	body.home .kc-hero-light .kc-hero-h { font-size: 34px; max-width: none; }
	body.home .kc-hero-tag { font-size: 15.5px; }
}

/* --- 29b. carousel dots: stop covering a CTA, and be tappable ------------- */

/* 2026-08-21 regression, found by measuring rather than by eye: kc-redesign.css
   §29.1 now carries the note ".kc-hero-dots is fully owned by
   assets/parts/29-home-hero.css §29b … Deleted, not left as a dead layer" —
   but what this partial actually declared was bottom/padding/button styling
   only. The deletion therefore also took position:absolute, the flex row and
   the centring, none of which were ever restated here. Measured on staging
   before this batch: the dots row went `position: static`, full-bleed, 45px
   tall; the first button landed at x=-24 (half off-canvas) and the row
   overlapped the USP strip by 24px on desktop.
   Root cause worth carrying forward: a partial that "takes over" a component
   must declare EVERY property that component relies on, not only the ones it
   wanted to change. Half-ownership survives exactly until the other half is
   deleted. Everything the dots need is now in this block — and it sits BEFORE
   the phone media query, because a later same-specificity rule would silently
   win over it (that is how bottom:40px leaked onto 375 the first time). */
body.home .kc-hero-dots {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 36px;
	display: flex;
	justify-content: center;
	align-items: center;
	gap: 4px;
	z-index: 3;
	padding: 6px 4px;
	/* full-bleed so it can centre — only the buttons may take a tap, or an
	   invisible 1280px-wide row would swallow clicks meant for the hero */
	pointer-events: none;
}
body.home .kc-hero-dots button { pointer-events: auto; }

/* Phones: the dots need a band of their own between the CTA row and the USP
   strip. The hero art — which used to hold that space open — is display:none
   under 768, so the space has to be bought with padding.
   Solved as arithmetic rather than by eye, because the thing that collides is
   the invisible 44px hit box, not the 9px dot. With H = hero bottom, P = the
   slide's bottom padding, B = the dots' `bottom`, and the strip pulled 16px up
   into the hero:
       button top    = H - B - 37        (30px row, box overhangs 7px each end)
       button bottom = H - B + 7
   2026-08-27 (hero-usp-fix) restates it against the two boxes that actually
   collide, because the old form measured to the icon row's TEXT and the
   verifier caught a 1px hit-box overlap at every viewport. The icon row's link
   hangs `od` px of transparent padding below its own line (od = 8 everywhere,
   the padding that buys the 44px touch box), and the strip is pulled U px up
   over the hero (16 on a phone, 24 on desktop):
       clear above the dots = P - B - 37 - od   >= 3
       clear below the dots = B - 23 + (U - 16) >= 5
   Phone (U=16): B >= 28, and P >= B + 48 -> 28/76, which is exactly what this
   block already spent. Desktop (U=24): B >= 36, P >= 84 -> 36/84, so §29c's
   84 stays and only the dots move 4px down. Both pairs are now at their floor:
   any smaller B puts the hit box inside the USP plate, any smaller P puts it
   on the "Coverage area" link. Measured after: 3px clear above, 5px below.
   At the old 58/12 the hit box ate 14.5px of both CTAs — a decorative widget
   must never cover a purchase control, visibly or otherwise. */
@media (max-width: 767px) {
	body.home .kc-hero-in { padding-bottom: 76px; }
	body.home .kc-hero-dots { bottom: 28px; }
}

/* Hit area: the visible dot stays 9px (owner signed off on that look in Batch
   19), but the button itself becomes 44px per the touch-target rule. Negative
   block margins keep the pill from ballooning — in flex, a child's negative
   cross-axis margins shrink its contribution to the line, so the pill grows
   from 21px to 30px while the tappable box is 44x44 and simply overhangs it
   into empty hero background. */
body.home .kc-hero-dots button {
	width: 44px;
	height: 44px;
	margin: -13px -8px;
	border: 0;
	border-radius: 0;
	background: transparent;
	position: relative;
}
body.home .kc-hero-dots button::after {
	content: '';
	position: absolute;
	inset: 0;
	margin: auto;
	width: 9px;
	height: 9px;
	border-radius: 50%;
	border: 1.5px solid var(--text);
	background: transparent;
}
body.home .kc-hero-dots button.kc-on { background: transparent; }
body.home .kc-hero-dots button.kc-on::after { background: var(--action); }

/* --- 29c. V2 ground: production's own illustration, letters removed ------- */

/* Owner pick, 2026-08-27: slide 1 gets production's hero picture back as its
   ground — hand, phone, Toronto street map — and the two 1 OZ product cards
   that used to fill this half are retired. What is NOT coming back is the
   lettering: production baked "2 HOUR DELIVERY", "11 PM Last Call", "FREE on
   orders over $200" and "VIEW Coverage Area" into the pixels, and two of those
   said $200 for months after the threshold became $150. Every one of them is
   HTML now (see the icon row, §29d) — except "FREE on orders over $150", which
   is not in the hero at all: the USP strip 68px below states it at 52px, and
   the same claim twice in one screen is what this hero refused "2 HOUR
   DELIVERY" for. Deleted 2026-08-27, see hero-usp-fix.md.

   One image, not three. The mockup composited a map tile plus an alpha cutout
   of the hand so product cards could sit between the two layers; with the cards
   gone there is nothing to interleave, and the master already contains both the
   hand and the map. The rectangle is placed so that only ONE of its four edges
   falls inside the hero — the left one — and the cream veil below covers that.
   Top, right and bottom all run off-canvas. That is the whole trick, and it is
   why no cutout, no mask and no second request are needed. */

body.home .kc-hero-light {
	position: relative;
	overflow: hidden;         /* the picture bleeds past three edges on purpose */
}
body.home .kc-hero-light > .kc-hero-in { position: relative; z-index: 2; }

/* Slide 1 got taller (the icon row is three new lines on a phone), and the
   slides are flex items that stretch to the tallest one — so slide 2's copy,
   top-aligned, was left sitting above 100px of empty pine on 375 and 109px on
   desktop. Measured, not guessed: slide 2's content is 367px tall against a
   467px track. Centring the inner grid is a no-op on slide 1 (it IS the tallest
   slide, so there is nothing to centre) and costs slide 2 nothing but the dead
   band. Not one word of slide 2 is touched. */
body.home .kc-hero-slide { display: flex; flex-direction: column; justify-content: center; }
/* width:100% is not belt-and-braces, it is the whole fix. .kc-hero-in carries
   `margin: 0 auto`, and auto cross-axis margins DISABLE align-self:stretch in
   flexbox — so the moment the slide became a flex container the inner grid
   collapsed to fit-content, the copy column narrowed, the icon row wrapped to
   two lines and the desktop hero jumped 527 -> 563. Measured, then fixed. */
body.home .kc-hero-slide > .kc-hero-in { width: 100%; }

/* Geometry (desktop). The master is 2500x973 with the hand+phone occupying
   x 376-1080, y 178-972 — i.e. the figure sits in the LEFT third of the file
   while our copy column sits on the left of the hero, so the picture has to be
   pushed right until the figure lands in the empty art column:
       scale        = 560 / 973                = 0.5756
       figure width = 704 * 0.5756             = 405px
       figure left  = image left + 376 * 0.5756 = image left + 216px
   anchored off the centre line (not the viewport edge) so it tracks the
   1160px content column at every desktop width instead of drifting right on a
   wide monitor. -121px puts the palm at x=735 on a 1280 viewport, which is
   23px into the .9fr column.

   mix-blend-mode: multiply is doing real work, not decoration. The map's paper
   is neutral #F2F2F2 and its streets are pure white; multiplied against the
   cream page the streets become exactly the page colour and the paper becomes a
   warm cream-grey, so the picture reads as part of the surface instead of a
   cool-grey rectangle pasted onto it. Nothing here may set opacity, filter,
   transform or mask on this element — each of them creates a stacking context
   and would isolate the blend, which is exactly how the "grey box" regression
   comes back. */
body.home .kc-hero-ground {
	position: absolute;
	bottom: 0;
	left: 50%;
	margin-left: -121px;
	height: 560px;
	width: auto;
	max-width: none;
	mix-blend-mode: multiply;
	pointer-events: none;
	user-select: none;
	z-index: 0;
}

/* The veil is a token colour with a gradient alpha, never a hand-mixed rgba of
   the cream — build_kc_css.py --check bans the raw hex and the intent is the
   same either way.

   Stops are calc() off the centre line, not percentages, and that is the whole
   correctness argument: the picture is anchored to the centre too, so a
   percentage stop would drift away from the edge it is hiding as soon as the
   viewport is not 1280 and the seam would reappear on a wide monitor. Measured
   first attempt: 34%/66% put the edge at 77% cream and the join was plainly
   visible in the screenshot — the map's multiplied paper is ~6% darker than the
   page, not the 1.5% the arithmetic predicted, because the streets read as
   texture and the eye locks onto a straight vertical. So the rule is stronger
   than "mostly covered": the edge must sit in the FULLY opaque band and the
   fade must happen over the map, where there is nothing to line up on.
   -116px is 5px left of the picture's own -121px. */
body.home .kc-hero-veil {
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient(90deg,
		var(--surface) 0,
		var(--surface) calc(50% - 116px),
		transparent    calc(50% + 120px));
}

/* The dots row lives at the hero's bottom edge (§29b) and the icon row is now
   the last thing in the copy column, so without this they end up on the same
   line and the dots read as a fourth fact. Measured: at the inherited 64px the
   dots' visual centre was 13px ABOVE the icon row's baseline. 84px buys 22px of
   clear air under the icons. Cost: slide 2 (shorter, top-aligned in the flex
   track) gains 20px of empty pine at its foot — accepted rather than
   vertically centring it, because slide 2 is out of scope this round. */
body.home .kc-hero-light .kc-hero-in { padding-bottom: 84px; }

/* The ghost CTA's background is rgba(...,.08) — invisible on a flat cream page,
   but on phones the picture now runs underneath the button row and 8% cream is
   not a background, it is a window. Opaque surface: identical on desktop
   (surface IS the page colour there), legible on top of the map. */
body.home .kc-hero-light .kc-hbtn-ghost { background: var(--surface); }

/* --- 29d. the facts production drew as pixels ---------------------------- */

body.home .kc-hero-icons {
	list-style: none;
	display: flex;
	flex-wrap: wrap;
	gap: 8px 20px;
	margin: 24px 0 0;
	padding: 0;
}
body.home .kc-hero-icons li { margin: 0; }
body.home .kc-hero-icons li,
body.home .kc-hero-icons a {
	display: flex;
	align-items: center;
	gap: 9px;
	font-family: var(--kc-font-body);
	font-size: 13px;
	line-height: 1.25;
	color: var(--text-muted);
	text-decoration: none;
}
body.home .kc-hero-icons b { color: var(--text); font-weight: 600; }
body.home .kc-hero-icons svg { width: 28px; height: 28px; flex: none; color: var(--text); }
/* the truck's wheels punch out of its body line — filled with the page colour,
   not white, so they stay invisible when the map is behind them */
body.home .kc-hero-icons .kc-ico-wheel { fill: var(--surface); }
/* 28px of icon is a 28px tap target; the padding buys 44 without moving the row
   (negative margins give the space back to the flex line) */
body.home .kc-hero-icons a { padding: 8px 6px 8px 0; margin: -8px -6px -8px 0; }
body.home .kc-hero-icons a:hover b { text-decoration: underline; }

/* --- 29d2. narrow desktops: shrink the picture, do not let it reach the copy -

   Between 768 and 1150 the .9fr art column shrinks but the icon row does not,
   so the 1280 geometry walks the wrist into the copy: measured at 1024, the
   "Coverage area ->" arrow ended 4px from bare skin (sampled background there
   came back [226,196,173] — that is the wrist, not the map). Contrast still
   passed at 4.95:1, but text about to sit on a photographed forearm is a
   composition failure before it is a contrast one. Smaller picture, pushed
   right, veil moved with it — the same three numbers that are tied together
   everywhere in this file. */
@media (min-width: 769px) and (max-width: 1150px) {
	body.home .kc-hero-ground { height: 460px; margin-left: -66px; }
	body.home .kc-hero-veil {
		background: linear-gradient(90deg,
			var(--surface) 0,
			var(--surface) calc(50% - 60px),
			transparent    calc(50% + 160px));
	}
}

/* --- 29e. phones: the picture becomes a corner, not a background --------- */

/* Under 768 the hero has a 430-470px budget and the copy already spends most of
   it, so the figure has to live in the strip beside the icon list and bleed off
   the bottom-right. That puts TWO of the rectangle's edges back inside the hero
   (left and top), so the veil grows a second gradient: one fades the picture
   out towards the top (behind the paragraph and the buttons), one fades it out
   towards the left (behind the icon list and the carousel dots). Where the two
   overlap the alpha compounds — 1-(1-a1)(1-a2) — so the top-left is solid cream
   and only the bottom-right corner shows through. Numbers are measured, see
   docs/redesign/hero-v2-land.md.

   Note .kc-hero-art { display:none } in kc-redesign.css §mobile stays exactly
   where it is: slide 1 no longer has one, and on slide 2 hiding the parcel
   outline on a phone is still the right call. This layer uses its own class
   names precisely so that rule never has to be touched. */
/* 768, not 767. kc-redesign.css collapses .kc-hero-in to ONE column at
   `max-width: 768px` while §29a/§29b here stop at 767, so 768 — iPad portrait,
   a real device, not a rounding artefact — was a mixed state: single-column
   copy with the desktop picture geometry. Measured there: the icon row ran to
   x=508 and the wrist started at 496, so "Coverage area ->" was literally on
   skin (sampled [241,209,183], 5.31:1). This block now owns 768 too, and
   restates the one §29b value that would otherwise stay at its desktop setting
   there. Do not narrow it back without also moving the grid rule. */
@media (max-width: 768px) {
	/* §29b solved this as arithmetic for <=767; 768 needs the same answer and
	   the rule that carries it stops one pixel short. */
	body.home .kc-hero-dots { bottom: 28px; }
	/* bottom:-40 not -70: at -70 the phone's green pill — the one piece of the
	   illustration that says "a courier is moving" — fell into the 16px the USP
	   strip pulls up over the hero and was never seen. */
	body.home .kc-hero-ground {
		height: 270px;
		left: 81px;
		margin-left: 0;
		bottom: -40px;
	}
	/* Vertical stops measured from the BOTTOM (calc(100% - N)) because that is
	   what the picture is anchored to; a % stop would slide off the top edge
	   every time the copy re-wraps. Picture top sits 230px above the hero's
	   bottom, so 234 keeps it inside the opaque band with 4px to spare. */
	body.home .kc-hero-veil {
		background:
			linear-gradient(180deg, var(--surface) 0, var(--surface) calc(100% - 234px), transparent calc(100% - 142px)),
			linear-gradient(90deg,  var(--surface) 0, var(--surface) 165px, transparent 340px);
	}
	/* §29b already bought the dots their band on phones with padding-bottom:76,
	   solved as arithmetic there. The desktop 84 above is MORE specific than
	   that rule (three classes vs two) so it leaked through the media query and
	   added 8px to a hero that has a 430-470 budget — restated here rather than
	   weakened there, because 84 is load-bearing on desktop. */
	body.home .kc-hero-light .kc-hero-in { padding-bottom: 76px; }

	/* 2026-08-27 (hero-usp-fix) — the first-fold budget, solved as arithmetic.
	   With the strip pulled 16px up over the hero, the strip's bottom edge sits
	   at (hero top 150) + heroH - 16 + (strip height 249.7) = 383.7 + heroH. A
	   375x812 phone therefore needs heroH <= 416 for the whole strip to be
	   readable without scrolling; before this batch heroH was 467.1 and the
	   last 38.8px of the strip were below the fold. Deleting the middle icon
	   row bought 33 of the 51 needed (24px row + 9px gap). The other 18 come
	   from four evenly-spread trims rather than one big cut, so no single gap
	   in the lockup reads as collapsed — 6 off the top pad, 3 off the
	   paragraph, 5 off the button row, 4 off the icon row. Desktop keeps every
	   original value: these are all scoped to the phone block, and .kc-hero-p /
	   .kc-hero-ctas are additionally scoped to .kc-hero-light so slide 2 (which
	   is centred in the same track and is shorter) is not touched. */
	body.home .kc-hero-light .kc-hero-in { padding-top: 26px; }
	body.home .kc-hero-light .kc-hero-p { margin-top: 11px; }
	body.home .kc-hero-light .kc-hero-ctas { margin-top: 19px; }

	body.home .kc-hero-icons {
		flex-direction: column;
		gap: 9px;
		margin-top: 10px;
		/* keeps the labels clear of the figure even at the longest string */
		max-width: 210px;
	}
	body.home .kc-hero-icons svg { width: 24px; height: 24px; }
	body.home .kc-hero-icons li,
	body.home .kc-hero-icons a { font-size: 12.5px; gap: 8px; }
	/* The 44px touch box, restated for the 24px phone icon: §29d's 8+8 was
	   sized for the 28px desktop svg (28+16=44) and silently gave 40px here —
	   40 at exactly the two viewports where a finger is the only pointer.
	   Asymmetric on purpose: the extra 4px goes UPWARD, into the 9px gap above,
	   because the space BELOW is what the carousel dots need (see §29b's
	   arithmetic — od is the 8 in `P - B - 37 - od`). The 3px by which 12
	   exceeds the 9px gap overlaps the "11 PM last call" line, which carries no
	   hit box of its own, so nothing competes for the tap. */
	body.home .kc-hero-icons a { padding: 12px 6px 8px 0; margin: -12px -6px -8px 0; }
}

/* Nothing in 29c-29e animates, so prefers-reduced-motion has nothing to turn
   off — stated here so the next person does not go looking for the hook. */

/* ===== 29-home-usp.css ===== */
/* ===========================================================================
   29-home-usp — front-page USP strip, direction A "the delivery clock"
   Owner: hero-usp agent.  Sibling of 29-home-hero.css, deliberately a separate
   file: the hero lockup and the USP strip have different lifecycles (the
   headline is settled SEO, the strip changes whenever the delivery rules do).

   Why it looks like this
   ----------------------
   The old strip was three centred label/value pairs at 12.5/11.5px, both lines
   computing to weight 400 — measured hierarchy of zero, so the eye skipped it.
   Direction A answered that with a size jump rather than with decoration.
   2026-08-27 (owner pick "V3, main statement + two wings") finishes the job:
   three equal 42px figures were three shouts with no focal point, so the
   money statement is now centred at 52px with the other two facts either
   side at 26px, and the vertical rules are gone — nothing separates a claim
   from its own footnotes. Nothing in here may read as a purchase control:
   no borders, no shadows, no hover lift on any cell or label.

   Markup contract (inc-home.php §2 + §6):
     .kc-usp-wrap      > .kc-usp > .kc-usp-clock (.kc-usp-cell-wing,
                                 .kc-usp-cell-money, .kc-usp-cell-wing —
                                 DOM order is the desktop order)
                                 > .kc-usp-sub   (fact + .kc-usp-sub-dot +
                                                  link, centred as one line)
     .kc-usp-more-wrap.kc-usp-fine > .kc-usp-more (3 x .kc-usp-item, each
                                 span.kc-usp-h + p.kc-usp-p + a.kc-usp-link,
                                 rendered run-in — see §29e2)
   Every cell carries -money or -wing: the figure/label/fact rules below are
   written per cell type, with no base rule underneath to fall through to.
   2026-08-23: the owner bounced tier 2 (three title+paragraph pairs) as a
   phone-screen wall of prose between hero and first product. The pairs moved
   to §6 near the page's long-copy register (.kc-usp-more); the strip keeps
   the clock plus one slim .kc-usp-sub line distilled from those paragraphs.
   2026-08-27 (home-copy-0827): §6 moved again — to the very bottom of the
   page, on its own priority-14 filter — and was demoted from three bordered
   columns to a run-in fine-print band. It carries the SAME three claims the
   strip above makes, so it must never look like a second set of cards making
   them; see §29e2. Nothing in the strip (29c/29d/29d2/29e) changed.
   The old .kc-hm-usp / .kc-hm-usp-wrap classes are no longer emitted, so the
   base rules in kc-redesign.css §29.2 stop matching on their own — no
   !important and no edit to a file this agent does not own. Those rules are
   dead now and are reported for deletion.
   =========================================================================== */

/* --- 29c. the plate ------------------------------------------------------ */

/* Same box geometry the strip has always had: 1160 track, 40px gutter, pulled
   24px up so it overlaps the hero and reads as attached to it rather than as
   the first row of the next section. */
body.home .kc-usp-wrap {
	max-width: 1160px;
	margin: -24px auto 0;
	padding: 0 40px;
	position: relative;
	z-index: 2;
}
body.home .kc-usp {
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
	background: var(--surface-raised);
	overflow: hidden;
	box-shadow: 0 8px 22px rgba(31, 26, 23, .08);
	font-family: var(--kc-font-body);
}

/* --- 29d. tier 1: one statement, two wings -------------------------------- */

/* `1fr auto 1fr` instead of three equal columns: the centre column sizes to
   its own text, so $150 sits on the plate's optical centre no matter how long
   the two facts beside it run. Padding lives on the grid rather than on the
   cells — a padding box on an `auto` column would widen the centre and drag
   the statement off centre. align-items:center puts the three groups on one
   axis; the wings are plain text groups, not chips: no box, no border, no
   hover, so nothing here can be mistaken for a control. */
body.home .kc-usp-clock {
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	align-items: center;
	text-align: center;
	padding: 20px 22px 19px;
	gap: 0 26px;
}

/* Poppins, not Gazella: these are numbers, and the brand rule puts every
   price and figure in the body face — 52px is over the 28px display
   threshold, but "price stays Poppins" wins. tabular-nums so "$150" and
   "2 HRS" sit on the same rhythm as the rest of the store's numerals. Fluid
   because it is the widest thing in an `auto` column: at 900-1024 a fixed
   52px squeezes the wings' facts into an extra line each (measured). */
body.home .kc-usp-cell-money .kc-usp-fig {
	display: block;
	font-size: clamp(40px, 4vw, 52px);
	line-height: 1;
	font-weight: 700;
	letter-spacing: -.03em;
	font-variant-numeric: tabular-nums;
	color: var(--text);
}
/* Ink, not pine. Green on this store means "a choice the customer made";
   a standing claim about the delivery rules is a system statement, so it is
   the same ink as everything else it sits with. (Until 2026-08-27 this was
   var(--trust) and the comment here defended it as a category label — the
   owner's rule replaces that reading.) */
body.home .kc-usp-cell-money .kc-usp-lab {
	display: block;
	margin: 9px 0 6px;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: .19em;
	text-transform: uppercase;
	color: var(--text);
}
/* 40ch keeps the rule sentence from spanning the whole centre column on a
   wide plate; margin:0 auto re-centres it inside that cap. */
body.home .kc-usp-cell-money .kc-usp-fact {
	margin: 0 auto;
	max-width: 40ch;
	font-size: 12.5px;
	line-height: 1.55;
	font-weight: 400;
	color: var(--text-muted);
}

/* The wings are the same three parts at half the weight of attention: 26px
   against 52px is the 2:1 that says "footnote", and it is carried by size
   alone — same face, same ink, same absence of decoration. */
body.home .kc-usp-cell-wing .kc-usp-fig {
	display: block;
	font-size: 26px;
	line-height: 1;
	font-weight: 700;
	letter-spacing: -.02em;
	font-variant-numeric: tabular-nums;
	color: var(--text);
}
body.home .kc-usp-cell-wing .kc-usp-lab {
	display: block;
	margin: 7px 0 5px;
	font-size: 10px;
	font-weight: 600;
	letter-spacing: .19em;
	text-transform: uppercase;
	color: var(--text);
}
body.home .kc-usp-cell-wing .kc-usp-fact {
	margin: 0 auto;
	max-width: 40ch;
	font-size: 12px;
	line-height: 1.5;
	font-weight: 400;
	color: var(--text-muted);
}

/* --- 29d2. the wings' one wrap-out-of-step band -------------------------- */

/* Measured, not defensive. The two wing facts are different lengths (279px
   and 232px at 12px), so between roughly 878px and 972px of viewport the
   long one wraps and the short one does not: the cells come out 84px and
   66px, `align-items:center` centres each in the row, and "2 HRS" ends up
   9px above "EVERY" with the two labels on different lines. Screenshot at
   900 showed it plainly.

   A two-line floor on the wing facts equalises them. Bounded to the tablet
   band on purpose: above 1023px both facts are single-line and a floor here
   would push the wings 9px off the centre the owner signed off at 1280. It
   costs nothing in height anywhere it applies — the money cell is the tall
   one in this band either way, so the row height is unchanged. `lh` is the
   honest unit ("two lines"); the px above it is the fallback for engines
   that do not have it. */
@media (min-width: 769px) and (max-width: 1023px) {
	body.home .kc-usp-cell-wing .kc-usp-fact {
		min-height: 36px;      /* 2 x (12px x 1.5) */
		min-height: 2lh;
	}
}

/* --- 29e. the sub line --------------------------------------------------- */

/* One slim line under the clock: the two phrases the paragraphs earned their
   keep with. Sunken half a step (surface, not surface-raised) so it reads as
   the plate's footer, not a fourth cell. The link is the only interactive
   thing in the strip — underlined text, no box, no shadow, no hover lift. */
body.home .kc-usp-sub {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: 0 14px;
	padding: 1px 22px;
	border-top: 1px solid var(--border);
	background: var(--surface);
}
body.home .kc-usp-sub-fact {
	font-size: 12px;
	line-height: 1.5;
	color: var(--text-muted);
}
/* the seam between the two centred phrases — punctuation, not content, and
   the faintest ink on the plate so it never reads as a bullet */
body.home .kc-usp-sub-dot {
	font-size: 12px;
	color: var(--text-faint);
}
body.home .kc-usp-sub-link {
	display: inline-flex;
	align-items: center;
	min-height: 44px;          /* tap box without a 44px visual slab */
	font-size: 12px;
	font-weight: 600;
	color: var(--text);        /* pine is the customer's choice, not a claim */
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
}
body.home .kc-usp-sub-link:hover,
body.home .kc-usp-sub-link:focus-visible { text-decoration-thickness: 2px; }

/* --- 29e2. §6: the fine-print band (home-copy-0827, 2026-08-27) --------- */

/* Production's .bannerheader-right column, restored verbatim, now rendering
   at the very bottom of the page (inc-home.php's kc_rd_home_fineprint_html(),
   its own the_content filter at priority 14).

   Why it stopped being three columns. The three pairs say 2-hours delivery,
   a gift on every order, and 10% off a first order — which is exactly what
   .kc-usp-clock says 2,600px higher up in 26-52px type. Two title/paragraph/
   link columns of equal width with their links pushed to a shared baseline
   (`margin-top:auto`) is the shape of a card row, and a card row asks to be
   read. Owner 2026-08-27: keep every word, stop asking twice.

   So: one column, one row per pair, and the heading, the sentence and the
   link all `display:inline` so each row reads as a single run of small print
   rather than as a titled block. Rows are separated by the same hairline the
   rest of the page uses, and nothing in here has a box, a fill, a radius or a
   hover move — the band has to be legible and skippable at the same time.

   🔴 The DOM must NOT be flattened to match. home_seo_diff.py starts a new
   text block at every inline tag it meets, so the heading has to stay its own
   <span>, the sentence its own <p>, and the link its own <a> OUTSIDE the
   sentence, or the production keys it matches on break and the only-add
   ratchet reports copy as lost. `display:inline` is a rendering property; the
   parser reads source. That is the whole trick here.

   The full stop after each heading is CSS-generated (::after), not DOM text:
   run-in heads need the sentence break to read, and adding a character to
   production copy is not this file's call. */
body.home .kc-usp-more-wrap.kc-usp-fine {
	max-width: 1160px;
	margin: 0 auto;
	padding: 0 40px;
}
/* The compound selector is deliberate. 31-home-rails.css:174 still sets
   `body.home .kc-usp-more-wrap { margin-top: 34px }` above 769px — it was the
   seam between the last product rail and this band, and that seam no longer
   exists (the band left the rails behind). Two classes outrank one at equal
   element depth, so the move is made here without editing another surface's
   file and without a single !important. That rule is now inert and is
   reported for deletion. */

body.home .kc-usp-more {
	display: block;
	border-top: 1px solid var(--border);
	border-bottom: 1px solid var(--border);
	font-family: var(--kc-font-body);
}
body.home .kc-usp-item {
	display: block;
	padding: 11px 0 12px;
}
body.home .kc-usp-item + .kc-usp-item {
	border-top: 1px solid var(--border);
}

/* Explicit weight, always: the theme's reset lists b/strong in a `font:
   inherit` shorthand, so anything relying on a default bold renders at 400
   here. That is the exact bug that flattened the old strip. */
body.home .kc-usp-h {
	display: inline;
	font-size: 11.5px;
	font-weight: 600;
	line-height: 1.62;
	letter-spacing: -.004em;
	color: var(--text);
	margin: 0;
}
body.home .kc-usp-h::after { content: "."; }
body.home .kc-usp-p {
	display: inline;
	margin: 0;
	font-size: 11.5px;
	line-height: 1.62;
	font-weight: 400;
	color: var(--text-muted);
}
/* Ink, not pine — same call the strip's links took on 2026-08-27: green on
   this store marks a choice the customer made, and these are standing claims.
   It also stops three underlined green words from being the brightest thing
   in a band whose whole job is to be quiet.

   No min-height here, unlike .kc-usp-sub-link. The link is now a word inside
   a sentence, which is WCAG 2.5.8's own "inline" exception — and a 44px box
   on an inline run either inflates every row or overlaps the row below it
   (measured: rows are 24px apart on a phone, two 22px overhangs collide).
   Symmetric vertical padding buys the pointer some slack without touching the
   line box; it is sized in §29f so it can never reach the next row. */
body.home .kc-usp-link {
	display: inline;
	font-size: 11.5px;
	font-weight: 600;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
	white-space: nowrap;   /* measured: "Learn more" broke across two lines on
	                          a 375 phone, which splits the underline and the
	                          pointer box in half. The longest label is
	                          "Sign up now" at 68px against a 373px row. */
	padding: 8px 0;
	margin: 0;
}
body.home .kc-usp-link:hover,
body.home .kc-usp-link:focus-visible { text-decoration-thickness: 2px; }

/* --- 29f. phones (owner pick "B1", 2026-08-27) --------------------------- */

/* Same three groups, same order of importance, every number turned down.
   `order:-1` lifts the money statement out of the two-column grid to a
   full-width row ABOVE the wings — the reverse of the old fold, which parked
   it at the bottom as an afterthought. The first thing thumbed past is $150.

   🔴 Specificity: every rule in here is written at the same depth as its
   desktop twin in 29d (`body.home .kc-usp-cell-money .kc-usp-fig`, three
   classes). A two-class shortcut like `body.home .kc-usp-fig` LOSES to 29d
   and fails silently — the page renders, nothing errors, the strip just
   measures taller than before you started. Ties are broken by source order,
   and 29f is below 29d in this file, so equal depth is enough.

   Round 2 (usp-0827b) measured three folds; the owner took B1, knowing it
   holds the phone label at 11px rather than the 12px floor the other two
   reach. Production's own facts already run 11.5px here, so this is a
   parameter choice, not a new precedent. */
@media (max-width: 768px) {
	body.home .kc-usp-wrap { padding: 0 16px; margin-top: -16px; }

	body.home .kc-usp-clock {
		grid-template-columns: 1fr 1fr;
		gap: 0;
		padding: 0;              /* the cells carry their own padding down here */
	}
	body.home .kc-usp-cell-money {
		order: -1;
		grid-column: 1 / -1;
		border-bottom: 1px solid var(--border);
		padding: 9px 12px 8px;
	}
	body.home .kc-usp-cell-money .kc-usp-fig { font-size: 26px; }
	body.home .kc-usp-cell-money .kc-usp-lab {
		margin: 4px 0 3px;
		font-size: 11px;
		letter-spacing: .14em;
	}
	body.home .kc-usp-cell-money .kc-usp-fact {
		font-size: 12px;
		line-height: 1.38;
		max-width: none;         /* 40ch is narrower than the phone plate */
	}
	body.home .kc-usp-cell-wing { padding: 8px 10px 9px; }
	body.home .kc-usp-cell-wing .kc-usp-fig { font-size: 17px; }
	body.home .kc-usp-cell-wing .kc-usp-lab {
		margin: 4px 0 3px;
		font-size: 11px;
		letter-spacing: .11em;
	}
	body.home .kc-usp-cell-wing .kc-usp-fact {
		font-size: 12px;
		line-height: 1.38;
		max-width: none;
	}

	/* the footer stacks to two short rows on a phone: fact, then link. The
	   centring seam has nothing to sit between once they stack, so it goes.
	   2026-08-27 (hero-usp-fix): text-align on the fact. Flex `justify-content:
	   center` centres the BOX, and a 100%-wide box is already centred — so the
	   5PM line rendered left-aligned over two lines while the 10% link under it
	   (a shrink-to-fit box) was centred, and the plate's footer read as half
	   finished. Everything else on the phone plate is centred; this is the only
	   line that was not. Costs 0px of height. */
	body.home .kc-usp-sub { padding: 6px 10px 0; gap: 0; }
	body.home .kc-usp-sub-fact { flex: 1 0 100%; font-size: 12px; line-height: 1.38; text-align: center; }
	body.home .kc-usp-sub-dot { display: none; }
	body.home .kc-usp-sub-link { font-size: 12px; } /* keeps the 44px tap box */

	/* §6 band on a phone: same run-in shape, one step smaller. The compound
	   selector matches §29e2's, because two classes there would otherwise beat
	   a one-class rule in here — media queries add no specificity. */
	body.home .kc-usp-more-wrap.kc-usp-fine { padding: 0 16px; }
	body.home .kc-usp-item { padding: 10px 0 11px; }
	body.home .kc-usp-h,
	body.home .kc-usp-p,
	body.home .kc-usp-link { font-size: 11px; }
	/* 7px each way = a 14px overhang against a 22px row gap (10 + 1 + 11):
	   the link's pointer box can never reach the row below it. */
	body.home .kc-usp-link { padding: 7px 0; }
}

/* The carousel dots are next to this strip but belong to the hero component —
   their positioning lives in 29-home-hero.css §29b, which now owns every
   property they depend on (see the note there: the base layer was deleted and
   took position:absolute with it). Nothing about the dots is declared here. */

/* ===== 30-side-cart.css ===== */
/* =============================================================================
   30-side-cart.css — Slide-out side cart (Group A, shopping surface)
   Owner: side-cart surface.  Partner file: includes/kc-redesign/inc-cart-panel.php

   DOM VERIFIED against staging, not assumed. Sources:
     · savoy/woocommerce/cart/mini-cart.php  and  savoy/woocommerce/global/quantity-input.php
       read off the staging server (Savoy 2.4.6).
     · A populated panel pulled from a real session:
         ?add-to-cart=… then ?wc-ajax=get_refreshed_fragments
       → fragment key "div.widget_shopping_cart_content".
     · Computed styles measured in headless Chrome at a true 375x812 mobile
       viewport (CDP Emulation.setDeviceMetricsOverride + touch emulation),
       against the live staging CSS chain.

   Structure (only what this file styles):
     #nm-widget-panel.nm-widget-panel
       .nm-widget-panel-inner
         .nm-widget-panel-header > .nm-widget-panel-header-inner
           a#nm-widget-panel-close > span.nm-cart-panel-title + span.nm-widget-panel-close-title
         .widget_shopping_cart_content            <- THE FRAGMENT ROOT
           #nm-cart-panel[.nm-cart-panel-empty]
             form#nm-cart-panel-form              (hidden nonce only)
             #nm-cart-panel-loader > .nm-loader-oldh5
             .kc-ladder[.kc-ladder-done] | .kc-cart-empty              <- ours (PHP)
             .nm-cart-panel-list-wrap > ul.woocommerce-mini-cart.cart_list.product_list_widget
               li.woocommerce-mini-cart-item
                 .nm-cart-panel-item-thumbnail > .nm-cart-panel-thumbnail-wrap > [a >] img
                 .nm-cart-panel-item-details
                   .nm-cart-item-loader
                   a.remove.remove_from_cart_button > i.nm-font.nm-font-close2
                   a|span.nm-cart-panel-product-title
                   ul.variation > li > div.variation-*        (NOT the dl/dt/dd Woo default)
                   .nm-cart-panel-quantity-pricing
                     .product-quantity > .nm-quantity-wrap > .quantity
                        .nm-qty-minus &nbsp; input.qty &nbsp; .nm-qty-plus
                     .nm-cart-panel-item-price > span.amount
               li.empty                            (rendered ALWAYS, hidden by class)
             .nm-cart-panel-summary > .nm-cart-panel-summary-inner
               p.woocommerce-mini-cart__total.total
               p.kc-cart-note                                        <- ours (PHP)
               p.woocommerce-mini-cart__buttons.buttons > a.button.checkout + a.button.border
               p.buttons.nm-cart-empty-button > a#nm-cart-panel-continue

   FRAGMENT SAFETY. The panel is re-rendered wholesale on every cart change, so
   nothing below depends on a JS hook having run. The only two pieces of state
   are class names the server emits every time: #nm-cart-panel.nm-cart-panel-empty
   (Savoy) and .kc-ladder-done / per-tick done classes (inc-cart-panel.php).
   Everything else is
   structural CSS on markup that always comes back identical.

   THEME SELECTORS BEATEN (each verified from the actual competing rule):
     savoy-child/style.css
       (0,1,6,3) body div#nm-widget-panel .woocommerce-mini-cart__buttons.buttons
                 > a.button.checkout.wc-forward
                 { display:none; font-weight:600; transition:none }
                 → the side cart ships with NO checkout button today. Beaten at
                   (0,1,7,2) by adding .nm-cart-panel-summary to the chain.
                   All three are plain declarations, so the heavier selector is
                   enough — but §30.10 has to restate weight and transition, or
                   they beat kc-redesign.css §2 (0,0,4,1) and the CTA ships at
                   600 with no press motion.
       (0,1,4,3)+!important  …a.button.border.wc-forward{color;border:none}
                 → in that same block, background:#EFEFEF and border-radius:10px
                   are PLAIN, as is its :hover{background:#dddddd}. Specificity
                   alone beats them; see §30.10 for why !important there was
                   actively harmful.
       (0,1,4,3)+!important  …div.nm-qty-minus/plus, input.qty { color:black }
       (0,0,4,0)+!important  .nm-qty-*.nm-font.nm-font-media-play{width:50px;height:54px;border:none}
       (0,2,0,2)+!important  body div#nm-widget-panel #nm-cart-panel-continue{color;border:none}
       (0,1,3,2)             li.empty:before{content:url(https://thompson.justwebagency.com/…svg)}
                             → a hot-linked asset on the PREVIOUS DEVELOPER's host,
                               in the customer-facing cart. Killed twice below.
     savoy-child/assets/css/main.css
       (0,0,1,1)+!important  .woocommerce-mini-cart__buttons a { color:#2A2621 }
       (0,0,1,0)+!important  .woocommerce-mini-cart__total { margin-bottom:15px }
       (0,1,2,1)             .widget-panel-dark #nm-widget-panel .product_list_widget li a
       (0,0,2,0)             .widget-panel-dark .nm-quantity-wrap .quantity{justify-content:end}
     savoy/style.css
       (0,0,2,0)  .widget-panel-dark .nm-widget-panel { color:#a5a5a5 }
                  → measured: the whole panel inherits grey, and the one product
                    name that is a <span> (free-gift line) renders grey today.
       (0,1,2,0)/(0,1,2,1)  .product_list_widget > li{display:table} / > li > div{display:table-cell}
       (0,1,2,0)  .product_list_widget .remove { position:absolute;width:28px;height:28px;opacity:.5 }
       (0,1,2,0)  .quantity .qty { width:20px }        ← measured 20x22px on a phone
       (0,1,1,0)  .product-quantity{float:left} / .nm-cart-panel-item-price{float:right}
       (0,0,2,0)  .no-touch .nm-widget-panel-header / .nm-cart-panel-summary { position:absolute }
       (0,1,1,0)  #nm-widget-panel .nm-cart-panel-summary-inner { overflow:hidden }
                  → would clip the primary button's 3px hard shadow.
     @media ≤320px  #nm-widget-panel .quantity .qty { font-size:inherit !important }

   Preference throughout is a heavier scoped selector over !important. Every
   !important below is beating an existing !important and says so inline.
   Every rule is scoped `body.kc-rd`, so the kill switch removes all of it.
   ========================================================================== */


/* --------------------------------------------------------------------------
   30.1  Panel shell
   Savoy forks the whole layout on .no-touch (header and summary absolutely
   positioned) vs .touch (normal flow). Two layouts means two sets of bugs, and
   in the flow version the summary — and therefore the checkout CTA — floats
   wherever the list ends. One flex column replaces both: header pinned, list
   scrolls, summary always on the bottom edge whatever the cart contains.
   -------------------------------------------------------------------------- */

body.kc-rd div#nm-widget-panel.nm-widget-panel {
	font-family: var(--kc-font-body);
	color: var(--text);
	background: var(--panel-bg);
	box-shadow: var(--panel-shadow);
}

body.kc-rd #nm-widget-panel .nm-widget-panel-inner {
	display: flex;
	flex-direction: column;
	height: 100%;
	overflow: hidden; /* the list scrolls, not the panel */
}

body.kc-rd #nm-widget-panel .nm-widget-panel-header {
	position: static; /* beats .no-touch … { position:absolute } */
	flex: 0 0 auto;
}

body.kc-rd #nm-widget-panel .widget_shopping_cart_content,
body.kc-rd #nm-widget-panel #nm-cart-panel {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	min-height: 0; /* without this the flex item refuses to shrink and the list never scrolls */
}

body.kc-rd #nm-widget-panel #nm-cart-panel-form {
	flex: 0 0 auto; /* hidden nonce field; must not stretch */
}

body.kc-rd #nm-widget-panel .nm-cart-panel-list-wrap {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain; /* a flick that bottoms out must not scroll the page behind the drawer */
	-webkit-overflow-scrolling: touch;
	padding-top: var(--kc-space-1);
	padding-bottom: var(--kc-space-4);
}

body.kc-rd #nm-widget-panel .nm-cart-panel-summary {
	position: static; /* beats .no-touch .nm-cart-panel-summary { position:absolute; bottom:0 } */
	flex: 0 0 auto;
	color: var(--text); /* main.css paints this block #2A2621 */
	background: var(--surface-raised);
	border-top: 1px solid var(--border);
}


/* --------------------------------------------------------------------------
   30.2  Header
   The whole header row is one anchor that closes the panel — Savoy's markup,
   not ours to restructure. "Close" is given a real chip so the control is
   findable, and the row is pinned to the 48px minimum.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .nm-widget-panel-header-inner {
	display: flex;
	align-items: center;
	height: auto;
	min-height: 64px;
	overflow: visible; /* Savoy clips here; a focus ring must be able to escape */
	color: var(--text); /* Savoy paints the header #282828 */
	background: var(--panel-bg);
	border-bottom: 1px solid var(--border);
}

body.kc-rd #nm-widget-panel #nm-widget-panel-close {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--kc-space-3);
	width: 100%;
	min-height: var(--btn-min-height);
	padding: var(--kc-space-2) 0; /* replaces Savoy's padding-top:32px */
	color: var(--text);
	overflow: visible;
	touch-action: manipulation;
}

/* Shopping surface: the cart title is Poppins, never the display face. */
body.kc-rd #nm-widget-panel #nm-widget-panel-close .nm-cart-panel-title {
	display: block;
	opacity: 1;
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 18px;
	line-height: 1.2;
	color: var(--text);
}

body.kc-rd #nm-widget-panel #nm-widget-panel-close .nm-widget-panel-close-title {
	float: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--btn-min-height);
	padding: 0 var(--kc-space-4);
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 14px;
	line-height: 1;
	color: var(--text);
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
}


/* --------------------------------------------------------------------------
   30.3  Gift-tier milestone ladder  (.kc-ladder — replaced the single
         free-delivery bar 2026-08-23)
   Markup, thresholds, tick positions and achieved-states all come from
   inc-cart-panel.php, which reads the live wdr gift rules and both
   free_shipping engines — never a hardcoded number. Styles here are
   self-contained: the old unscoped .kc-threshold base in kc-redesign.css §6
   is now dead markup-side and is on the orchestrator delete list.
   One nudge line + one track + one label row. Pine = earned/confirmation
   (same semantics as the FREE label on gift rows); unearned is quiet ink.
   -------------------------------------------------------------------------- */

/* Container box only. Every inner element (head, track, rungs, animation) is
   styled ONCE in 66-track.css §66.5 — the shared component layer — so the
   panel, cart page and checkout rail cannot drift. Do not restate track/dot
   geometry here: an ID-scoped copy would silently outrank the component. */
body.kc-rd #nm-widget-panel .kc-ladder {
	flex: 0 0 auto;
	margin: 0;
	padding: var(--kc-space-3) 37px var(--kc-space-2);
	background: var(--surface-sunken);
	border-bottom: 1px solid var(--border);
}


/* --------------------------------------------------------------------------
   30.4  Line items — from display:table to a compact flex row
   2026-08-27 budget (375×812, panel open, 3 paid items + gift + ghost rows):
     before  header 65 · ladder 145 · list 145 (ONE 166px row, cut) ·
             pinned footer 457 (tally 45 · upsell 110 · subtotal 23 ·
             estimator 64 · note 39 · buttons 104 · gaps/padding)
     target  three paid rows fully on screen with the ladder, a gift row and
             the CTA — the NN compact row (48px shot, one-line name, qty and
             price on one line) at ~78px, tally + upsell moved out of the
             pinned footer into the scrolling list (inc-cart-panel.php).
   The list is a flex column so gift/ghost/tally/upsell rows can be ORDERED
   after the paid rows regardless of the order the engine appended them
   (the engine's gift line lands wherever the tier was crossed — measured
   between paid rows 2 and 3).
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .product_list_widget {
	display: flex;
	flex-direction: column;
	padding: 0;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .product_list_widget > li.woocommerce-mini-cart-item {
	position: relative; /* containing block for .remove and .nm-cart-item-loader */
	display: flex;
	align-items: flex-start;
	gap: 10px;
	width: 100%;
	min-height: 0;
	margin: 0;
	/* Round 3 (owner 2026-08-27: "加减号和价钱下面还是有很多 spacing … 留一点点
	   就可以了"). Measured breakdown of the gutter under the stepper/price line
	   BEFORE: row padding-bottom 8 + hairline 1 + next row padding-top 8 = 17px
	   of air between one row's pill and the next row's name. Nothing else was
	   contributing — the text column is 48.19 (name 18.19 + 4 + 26px pill) and
	   the thumbnail frame is 48.00, so the 0.19 of alignment slack is noise,
	   and the pill's own 2px of hairline is the control, not padding.
	   4px is the floor, not a taste call: the invisible 36x36 ::after that gives
	   each +/- half a real tap target hangs 5px below the pill on a coarse
	   pointer (4px on fine). At a flat 4px the ::after ends ON the hairline and
	   a 1px-grid elementFromPoint scan came back 36x35 — one pixel under the
	   floor — so the bottom gutter carries the hairline's own pixel as well:
	   4 + 1. Measured back at 36x36, and the ::after still stops at the row's
	   border-box edge, so it never reaches the next row's name.
	   Gutter 17 -> 10, row 65.19 -> 58.19. */
	padding: var(--kc-space-1) 0 calc(var(--kc-space-1) + 1px);
	border-bottom: 1px solid var(--border);
}

/* Visual order: paid rows, gift rows, ghost rows, tally, upsell.
   Gift rows take slots 1–4 from their reading-order class (kc-gift-oN, set
   in inc-cart-panel.php from kc_rd_gift_sorted) so gummy sits above joints
   at both the $150 and the $200 rung — the engine appends them in different
   orders per rung (measured 2026-08-27: joints→gummy at $150, gummy→joints at
   $200). A gift row without a slot (product outside every tier) stays at 1. */
body.kc-rd #nm-widget-panel .product_list_widget > li.kc-gift-row            { order: 1; }
body.kc-rd #nm-widget-panel .product_list_widget > li.kc-gift-row.kc-gift-o2 { order: 2; }
body.kc-rd #nm-widget-panel .product_list_widget > li.kc-gift-row.kc-gift-o3 { order: 3; }
body.kc-rd #nm-widget-panel .product_list_widget > li.kc-gift-row.kc-gift-o4 { order: 4; }
body.kc-rd #nm-widget-panel .product_list_widget > li.kc-ghost-row           { order: 5; }
body.kc-rd #nm-widget-panel .product_list_widget > li.kc-tally-li            { order: 6; }
body.kc-rd #nm-widget-panel .product_list_widget > li.kc-upsell-li           { order: 7; }
body.kc-rd #nm-widget-panel .product_list_widget > li.empty                  { order: 9; }

body.kc-rd #nm-widget-panel .product_list_widget > li > div {
	display: block; /* beats display:table-cell */
	padding-top: 0;
	padding-bottom: 0;
	border-bottom: 0;
}

/* Deliberately block, not flex-column. -webkit-line-clamp needs a
   display:-webkit-box element, and a direct flex item gets blockified — Chrome
   computes it back to flow-root and the clamp silently stops working. Measured:
   with this column as a flex container the product title reported
   display:flow-root and did not clamp. Margins do the spacing instead. */
body.kc-rd #nm-widget-panel .product_list_widget > li > .nm-cart-panel-item-details {
	display: block;
	flex: 1 1 auto;
	min-width: 0;
	padding-left: 0;
}

/* The first two children (.nm-cart-item-loader, a.remove) are absolutely
   positioned, so the title is the first in-flow box and sits flush. */
body.kc-rd #nm-widget-panel .nm-cart-panel-item-details > * {
	padding-bottom: 0;
	margin-top: 0;
}

body.kc-rd #nm-widget-panel .nm-cart-panel-item-details > .variation {
	margin-top: 1px;
}


/* --------------------------------------------------------------------------
   30.5  Thumbnail — one ground for every shot
   Measured before: linked products got 72x72 on white, unlinked ones (the
   auto-added free gift) fell through to 60x60. Two sizes in one list. Both are
   normalised onto the stone bed the brand kit specifies for product shots.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .product_list_widget > li > .nm-cart-panel-item-thumbnail {
	flex: 0 0 48px;
	width: 48px;
}

body.kc-rd #nm-widget-panel .nm-cart-panel-item-thumbnail .nm-cart-panel-thumbnail-wrap {
	width: 48px;
	height: 48px;
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
	background: var(--card-shot-bg);
	overflow: hidden;
}

body.kc-rd #nm-widget-panel .nm-cart-panel-thumbnail-wrap > a {
	display: block;
	width: 100%;
	height: 100%;
	border: 0;
}

body.kc-rd div#nm-widget-panel .product_list_widget li .nm-cart-panel-thumbnail-wrap img {
	/* !important on background only: savoy-child pins background:#ffffff!important
	   on the linked variant, which would put a white square on the stone bed. */
	display: block;
	width: 100%;
	max-width: none;
	height: 100%;
	margin: 0;
	background: transparent !important;
	border-radius: 0;
	object-fit: contain;
	filter: none; /* savoy-child dims these with brightness(.95) */
}


/* --------------------------------------------------------------------------
   30.6  Product name and variation line
   Body face, never the display face — this is the shopping surface, and a
   strain name is data, not tone of voice. ONE line with an ellipsis (the NN
   compact row, owner reference 2026-08-27): the weight of a variable product
   lives in the .variation line under it, so a truncated name never loses the
   gram figure; the full name is one tap away on the PDP link.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .product_list_widget li .nm-cart-panel-product-title {
	display: block;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	margin: 0;
	padding: 0 40px 0 0; /* clears the remove control pinned top-right */
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 14px;
	line-height: 1.3;
	color: var(--text); /* the <span> variant renders grey without this */
}

body.kc-rd #nm-widget-panel .variation {
	display: block;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	margin: 0;
	padding: 0 40px 0 0;
	font-family: var(--label-font);
	font-size: 11.5px;
	line-height: 1.3;
	letter-spacing: .04em;
	color: var(--text-muted);
}

body.kc-rd #nm-widget-panel .variation li,
body.kc-rd #nm-widget-panel .variation dt,
body.kc-rd #nm-widget-panel .variation dd,
body.kc-rd #nm-widget-panel .variation li > div,
body.kc-rd #nm-widget-panel .variation p {
	display: inline;
	margin: 0;
	padding: 0;
	font-size: inherit;
	line-height: inherit;
}

body.kc-rd #nm-widget-panel .variation dt,
body.kc-rd #nm-widget-panel .variation li > div:first-child {
	font-weight: 600;
}

/* The discount plugin's free-gift tag, seen on the auto-added pre-roll line.
   woo-discount-rules ships it as a filled chip on #3d9cd2 — a sixth hue, and a
   fill the kit reserves for strain type. Stripped back to plain pine text: pine
   is the trust role and "Free" is a confirmation, not decoration. Caught in a
   screenshot, not in the computed-style pass — nothing I measured was wrong. */
body.kc-rd #nm-widget-panel .variation .awdr_free_product_text {
	display: inline;
	padding: 0;
	font-weight: 700;
	color: var(--trust);
	background: none;
	border-radius: 0;
}


/* --------------------------------------------------------------------------
   30.7  Per-line price + quantity — ONE line (stepper left, price right)
   Round 2 (owner 2026-08-27 pm): the whole TEXT COLUMN now equals the 48px
   thumbnail — name line 18 + 2/4 gap + a 28px (desktop) / 26px (touch) pill.
   The pill is ~92px wide and the details column ~243px at 375px, so the
   price keeps ~140px of its own on the same line. The remove control is
   pinned top-right beside the NAME, a full line above the price, so the two
   never share a tap zone.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .nm-cart-panel-quantity-pricing {
	display: flex;
	flex-direction: row;
	align-items: center;
	justify-content: space-between;
	gap: var(--kc-space-2);
	width: 100%;
	margin-top: 2px; /* 48px text column: 18.2 name + 2 + 28px pill (coarse: 4 + 26) */
	overflow: visible;
	line-height: 1.2;
}

body.kc-rd #nm-widget-panel .nm-cart-panel-quantity-pricing .nm-cart-panel-item-price {
	order: 0;
	flex: 0 1 auto;
	min-width: 0;
	float: none;
	margin: 0 0 0 auto;
	text-align: right;
}

body.kc-rd #nm-widget-panel .nm-cart-panel-item-price .amount {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
	font-size: 15px;
	line-height: 1.2;
	color: var(--text); /* money is never grey */
}

/* woo-discount-rules appends "<br>You saved $2.00" (inline green) under the
   line price. In a 78px row that is a second money line per row; the cart
   page keeps it (60-cart-page.css), the panel states the discounted line
   price once. */
body.kc-rd #nm-widget-panel .nm-cart-panel-item-price br,
body.kc-rd #nm-widget-panel .nm-cart-panel-item-price .awdr-you-saved-text {
	display: none;
}

body.kc-rd #nm-widget-panel .nm-cart-panel-quantity-pricing .product-quantity,
body.kc-rd #nm-widget-panel .nm-cart-panel-quantity-pricing > span.quantity {
	float: none;
	margin: 0;
	font-family: var(--price-font);
	font-size: 14px;
	color: var(--text);
}


/* --------------------------------------------------------------------------
   30.8  Quantity stepper
   Measured before: 50x54 chevrons around a 20px-wide number field. The field
   is the only part of this control that a keyboard or screen-reader user can
   operate (the +/- are bare <div>s), so it gets real width and a real label —
   see inc-cart-panel.php.
   Owner 2026-08-25 evening: the 48px boxed version read as oversized, abrupt
   and too white — recut as a compact hairline pill. Owner 2026-08-27 pm went
   further ("加减和中间的数字上下占了太多空间"): the pill is now text-height —
   28px outer on fine pointers, 26px on coarse — so name + stepper line equal
   the 48px thumbnail. Sub-44 visuals are the owner's documented trade; the
   REAL tap target is restored to 36x36 by an absolutely positioned ::after
   on each half (visually nothing, hit-area everything).
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .nm-quantity-wrap {
	display: inline-flex;
	width: auto;
	min-height: 28px; /* 26px halves + the hairline top/bottom */
	margin: 0;
	padding: 0;
	border: 1px solid var(--border);
	border-radius: 999px;
	background: transparent;
	overflow: visible; /* focus ring on the number field must not be clipped */
	box-sizing: border-box;
}

body.kc-rd #nm-widget-panel .nm-quantity-wrap .quantity {
	display: flex;
	align-items: center;
	justify-content: center;
	margin: 0;
	/* The template separates the three controls with literal &nbsp;. A no-break
	   space is not collapsible white space, so in a flex container each one
	   becomes a real anonymous item with real width. Zeroing the font size here
	   removes them; every child restores its own. */
	font-size: 0;
}

/* The .nm-font on the end of these two selectors is load-bearing, not decoration:
   savoy-child's `body div#nm-widget-panel div.nm-qty-minus.nm-font.nm-font-media-play
   { color: black !important }` is (0,1,4,3). Without the extra class this rule
   ties and loses, which is exactly what the first render measured — the glyphs
   came back rgb(0,0,0) instead of the ink token. */
body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-minus.nm-font,
body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-plus.nm-font {
	/* !important throughout: savoy-child/style.css pins these at
	   width:50px!important; height:54px!important; border:none!important and
	   paints them color:black!important. Only !important beats !important. */
	position: relative; /* anchors the ::after tap extender */
	display: flex !important;
	align-items: center;
	justify-content: center;
	width: 28px !important;
	height: 26px !important;
	min-height: 0 !important;
	margin: 0 !important;
	padding: 0 !important;
	border: 0 !important;
	font-size: 18px !important;
	line-height: 1 !important;
	color: var(--text) !important;
	background: transparent;
	border-radius: 999px;
	cursor: pointer;
	user-select: none;
	-webkit-tap-highlight-color: transparent;
	/* Quantity is inherently a repeat-tap control; this stops Safari treating
	   the second tap as double-tap-to-zoom. Plain declaration — nothing in
	   savoy-child contests touch-action. */
	touch-action: manipulation;
}

body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-minus.nm-font:hover,
body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-plus.nm-font:hover {
	/* !important: savoy-child forces color:rgba(0,0,0,.5)!important on hover,
	   which reads as "disabled" on the control people use most. */
	color: var(--text) !important;
	background: var(--surface-sunken);
}

/* The pill is 26/28px tall by the owner's explicit call, but a thumb is not.
   Each half carries an invisible 36x36 tap zone centred on itself (minus
   ends x<32, plus starts x>58 at 28px halves — the two zones never meet, and
   the number field keeps its middle). Positioned, so it paints above the
   in-flow input edge it overlaps by ~4px — a click there repeats the tap the
   customer aimed at. */
body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-minus.nm-font::after,
body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-plus.nm-font::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	width: 36px;
	height: 36px;
	transform: translate(-50%, -50%);
}

/* Savoy puts a media-play triangle in these (◀ / ▶ via .flip). A triangle pair
   reads as previous/next, not less/more. The marks are the PDP's finalized
   stepper set — masked SVG rects, not text glyphs: a glyph's ink sits on font
   metrics and never optically centres against the digit; a masked rect is
   centred by flex geometry (ledger Batch 18d — the same control was reworked
   four times on the PDP before landing here, so the cart reuses it verbatim
   instead of re-deriving it). content:"" also beats the .flip transform rule
   by replacing the glyph entirely. */
body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-minus.nm-font:before,
body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-plus.nm-font:before {
	content: "" !important;
	display: block;
	width: 11px;
	height: 11px;
	background: currentColor;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: contain;
	mask-size: contain;
	transform: none;
}

body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-minus.nm-font:before {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='7.15' width='12' height='1.7' rx='.85'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='7.15' width='12' height='1.7' rx='.85'/%3E%3C/svg%3E");
}
body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-plus.nm-font:before {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='7.15' width='12' height='1.7' rx='.85'/%3E%3Crect x='7.15' y='2' width='1.7' height='12' rx='.85'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='7.15' width='12' height='1.7' rx='.85'/%3E%3Crect x='7.15' y='2' width='1.7' height='12' rx='.85'/%3E%3C/svg%3E");
}

body.kc-rd #nm-widget-panel .nm-quantity-wrap .quantity .qty {
	/* !important on color beats savoy-child's `color:black!important`; on
	   font-size it beats the ≤320px `font-size:inherit!important`, which would
	   drop the field to 14px and re-introduce iOS zoom-on-focus. */
	width: 34px;
	min-width: 0;
	height: 26px;
	min-height: 0;
	margin: 0;
	padding: 0;
	border: 0;
	border-left: 1px solid var(--border);
	border-right: 1px solid var(--border);
	border-radius: 0;
	background: transparent;
	font-family: var(--price-font);
	font-weight: 700;
	font-size: 16px !important; /* < 16px makes iOS zoom the page on focus */
	line-height: 1;
	color: var(--text) !important;
	text-align: center;
	box-shadow: none;
	-moz-appearance: textfield;
	appearance: textfield;
}

body.kc-rd #nm-widget-panel .nm-quantity-wrap .quantity .qty::-webkit-outer-spin-button,
body.kc-rd #nm-widget-panel .nm-quantity-wrap .quantity .qty::-webkit-inner-spin-button {
	-webkit-appearance: none;
	margin: 0;
}

/* Coarse pointers: two points SMALLER than the desktop pill, not bigger —
   owner 2026-08-27 pm asked for the phone row specifically, and accepted a
   sub-44 stepper on record; the 36x36 ::after above is what a thumb actually
   gets. The 2px the pill gives up goes to the name gap (margin-top 4), so
   the text column is 48px on both pointers. */
@media (pointer: coarse) {
	body.kc-rd #nm-widget-panel .nm-quantity-wrap { min-height: 26px; }
	body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-minus.nm-font,
	body.kc-rd div#nm-widget-panel .nm-quantity-wrap .quantity div.nm-qty-plus.nm-font {
		height: 24px !important;
	}
	body.kc-rd #nm-widget-panel .nm-quantity-wrap .quantity .qty { height: 24px; }
	body.kc-rd #nm-widget-panel .nm-cart-panel-quantity-pricing { margin-top: 4px; }
}

/* Savoy renders two labels here and main.css hides both with !important; they
   carry no `for` attribute, so nothing is lost. The accessible name is put on
   the input itself, server-side, in inc-cart-panel.php. */


/* --------------------------------------------------------------------------
   30.9  Remove control
   Deliberately NOT adjacent to the stepper. Pinned to the row's top-right
   corner while the stepper sits at the bottom-left: roughly 70px apart
   horizontally and a full row apart vertically, against the kit's 8px floor.
   A mis-tap here costs the line, so distance is the whole design.
   Note some rows legitimately have no remove link (auto-added free gifts) —
   nothing below assumes it exists.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .product_list_widget .remove {
	position: absolute;
	/* 44px target centred on the 18.19px name line. Follows the row padding:
	   at padding-top 4 the name's optical centre is row+13.1, so -7 puts the
	   44px box at row-7..row+37 (centre row+15, 1.9px low — under the 2px the
	   rest of this row is built to). -9 would centre it exactly but would then
	   overlap the line price of the row ABOVE by ~1px, and a mis-tap there
	   deletes the wrong line. */
	top: -7px;
	right: -6px;
	z-index: 100;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	margin: 0;
	padding: 0;
	font-size: 16px; /* the theme-icon close glyph is hairline-thin at 14 */
	line-height: 1;
	text-align: center;
	color: var(--text-muted);
	background: transparent;
	border-radius: var(--kc-radius-md);
	opacity: 1; /* Savoy ships it at .5 */
	touch-action: manipulation;
}

body.kc-rd #nm-widget-panel .product_list_widget .remove:hover,
body.kc-rd #nm-widget-panel .product_list_widget .remove:focus-visible {
	color: var(--urgent);
	background: var(--surface-sunken);
}


/* --------------------------------------------------------------------------
   30.10  Summary, subtotal, and the buttons
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .nm-cart-panel-summary-inner {
	display: flex;
	flex-direction: column;
	gap: 10px;
	/* 0px fallback inside env() on purpose: a bare env() an older UA does not
	   know invalidates the whole declaration (same caveat 40-nav-menu.css
	   documents). max() keeps the token padding on notchless screens and yields
	   to the home-indicator strip on notched ones — the CTA measured at y748 on
	   an 812pt viewport sits inside the 34pt strip without this. */
	padding: var(--kc-space-3) 0 max(var(--kc-space-4), env(safe-area-inset-bottom, 0px));
	background: transparent;
	overflow: visible; /* Savoy clips here, which would cut the CTA's hard shadow */
}

body.kc-rd #nm-widget-panel .nm-cart-panel-summary .total {
	/* margin !important: main.css pins margin-bottom:15px!important. */
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--kc-space-3);
	width: 100%;
	margin: 0 !important;
	padding: 0;
	border: 0;
	background: transparent;
	font-size: 14px;
	line-height: 1.2;
}

body.kc-rd #nm-widget-panel .nm-cart-panel-summary .total strong {
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 14px;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .nm-cart-panel-summary .nm-cart-panel-summary-subtotal,
body.kc-rd #nm-widget-panel .nm-cart-panel-summary .nm-cart-panel-summary-subtotal .amount {
	float: none;
	font-family: var(--price-font);
	font-weight: 700;
	font-size: 20px;
	line-height: 1;
	color: var(--text);
}

/* One line of expectation-setting, printed by inc-cart-panel.php. Full-contrast
   ink: this is a fact the customer needs in order to decide, not fine print. */
body.kc-rd #nm-widget-panel .kc-cart-note {
	margin: calc(var(--kc-space-2) * -1) 0 0;
	font-family: var(--kc-font-body);
	font-weight: 500;
	/* 14px, not 13: the kit's scale has no 13px role — 12 is Metric/Label (THC,
	   $/g, weights), 15 is Body. A sentence set at 13 reads as the disclaimer
	   this line is specifically not. Matched to .kc-cart-empty-note, the
	   panel's other full sentence. */
	font-size: 14px;
	line-height: 1.4;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .nm-cart-panel-summary .woocommerce-mini-cart__buttons.buttons {
	display: flex;
	flex-direction: column;
	gap: var(--kc-space-2);
	width: 100%;
	margin: 0;
	padding: 0;
}

/* Savoy renders the "Continue shopping" paragraph on every cart state and hides
   it unless #nm-cart-panel carries .nm-cart-panel-empty. That guard has to stay
   on this rule: an earlier revision grouped it with the selector above and the
   measurement came back with a third full-width amber button sitting under
   Checkout in a populated cart — three primary actions in one view. */
body.kc-rd #nm-widget-panel #nm-cart-panel.nm-cart-panel-empty .buttons.nm-cart-empty-button {
	display: flex;
	flex-direction: column;
	gap: var(--kc-space-2);
	width: 100%;
	margin: 0;
	padding: 0;
}

/* ONE primary action in this view. Restoring it is the single biggest change in
   this file: savoy-child hides the checkout button outright, so today every
   order is routed through the cart page for no reason.
   Colour and shadow come from kc-redesign.css §2. Weight and transition do NOT:
   savoy-child's own block on this selector sets font-weight:600 and
   transition:none at (0,1,5,3), which outranks §2's (0,0,4,1) — measured, the
   CTA came back at 600 with a 0s transition, i.e. typographically identical to
   the quiet secondary below it and with no press motion. Six properties the
   child theme was winning are re-asserted here, at (0,1,7,2). */
body.kc-rd #nm-widget-panel .nm-cart-panel-summary .woocommerce-mini-cart__buttons.buttons > a.button.checkout.wc-forward {
	display: flex; /* beats display:none at (0,1,6,3) — this is (0,1,7,2) */
	align-items: center;
	justify-content: center;
	order: -1;
	width: 100%;
	min-height: var(--btn-min-height);
	margin: 0;
	padding: var(--kc-space-3) var(--kc-space-4);
	font-family: var(--kc-font-body);
	font-weight: 700;  /* no !important needed: savoy-child's 600 is a plain declaration */
	font-size: 16px;
	line-height: 1.2;
	touch-action: manipulation;
	transition: transform .12s ease, box-shadow .12s ease; /* beats transition:none, same way */
	/* the three !importants below each beat an !important in savoy-child/style.css
	   that was outranking the primary-button spec in kc-redesign.css §2 */
	color: var(--btn-primary-text) !important;
	border: 2px solid var(--btn-primary-border) !important;
	border-radius: var(--kc-radius-md) !important;
}

/* Secondary: quieter, not a smaller primary. No fill, no shadow, lighter weight. */
body.kc-rd #nm-widget-panel .nm-cart-panel-summary .woocommerce-mini-cart__buttons.buttons > a.button.border.wc-forward {
	display: flex;
	align-items: center;
	justify-content: center;
	order: 0;
	width: 100%;
	min-height: var(--btn-min-height);
	margin: 0;
	padding: var(--kc-space-3) var(--kc-space-4);
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 15px;
	line-height: 1.2;
	touch-action: manipulation;
	/* Only color and border are !important here, because only color and border
	   carry !important in the competing savoy-child block. Its background:#EFEFEF
	   and border-radius:10px are plain declarations at (0,1,5,3) and lose to this
	   selector's (0,1,7,2) on specificity alone — !important on them would have
	   been decoration, and would also have outranked the kit's own secondary
	   hover in parts/20-buttons.css, leaving this button with no hover at all. */
	color: var(--text) !important;          /* beats color:#2B2520!important */
	background: transparent;
	border: 2px solid var(--border-strong) !important; /* beats border:none!important */
	border-radius: var(--kc-radius-md);
	box-shadow: none;
}

/* The secondary's own hover, at (0,1,8,2). parts/20-buttons.css carries the
   sitewide `.button.border:hover { background: var(--surface-sunken) }` at
   (0,0,4,1), which this surface's base rule outranks — so the state has to be
   restated at this weight or the button is inert to the pointer. */
body.kc-rd #nm-widget-panel .nm-cart-panel-summary .woocommerce-mini-cart__buttons.buttons > a.button.border.wc-forward:hover {
	background: var(--surface-sunken);
}

/* State table: press seats the button into its own shadow. */
body.kc-rd #nm-widget-panel .nm-cart-panel-summary .button.checkout:active,
body.kc-rd #nm-widget-panel #nm-cart-panel-continue:active {
	transform: translate(3px, 3px);
	box-shadow: 0 0 0 var(--text);
}


/* --------------------------------------------------------------------------
   30.11  Empty state
   Savoy always renders li.empty ("No products in the cart.") and only shows it
   under .nm-cart-panel-empty. Its ::before pulls an image from
   thompson.justwebagency.com — the previous developer's host — on a
   customer-facing screen. It is switched off here and the state is rebuilt from
   markup we render ourselves, with the live free-delivery figure in it.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel #nm-cart-panel .product_list_widget .empty,
body.kc-rd #nm-widget-panel #nm-cart-panel.nm-cart-panel-empty .product_list_widget .empty {
	display: none;
}

/* Mirrors savoy-child's own selector one class heavier — measured: at
   (0,1,3,1) this rule lost to its (0,1,4,3) and the ::before still resolved to
   the thompson.justwebagency.com URL. */
body.kc-rd div#nm-widget-panel ul.woocommerce-mini-cart.cart_list.product_list_widget > li.empty:before {
	content: none; /* kills the hot-linked third-party SVG outright */
}

/* Takes the space the (now empty) list would otherwise hold, so the message
   sits in the middle of the panel rather than stranded at the top with 540px
   of nothing between it and the button. Measured before this: y65-190 with the
   CTA at y748. */
body.kc-rd #nm-widget-panel .kc-cart-empty {
	display: flex;
	flex-direction: column;
	justify-content: center;
	flex: 1 1 auto;
	min-height: 0;
	padding: var(--kc-space-6) 37px;
	text-align: center;
}

body.kc-rd #nm-widget-panel #nm-cart-panel.nm-cart-panel-empty .nm-cart-panel-list-wrap {
	flex: 0 0 auto;
}

body.kc-rd #nm-widget-panel .kc-cart-empty-title {
	margin: 0 0 var(--kc-space-2);
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 18px;
	line-height: 1.3;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .kc-cart-empty-note {
	margin: 0;
	font-family: var(--kc-font-body);
	font-size: 14px;
	line-height: 1.5;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .kc-cart-empty-note .amount {
	font-family: var(--price-font);
	font-weight: 700;
	color: var(--text);
}

/* With nothing in the cart, "Continue shopping" is the only action here, so it
   takes the primary spec rather than sitting there as a grey pill. */
body.kc-rd #nm-widget-panel #nm-cart-panel-continue {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	min-height: var(--btn-min-height);
	margin: 0;
	padding: var(--kc-space-3) var(--kc-space-4);
	font-family: var(--kc-font-body);
	font-weight: 700;
	font-size: 16px;
	line-height: 1.2;
	touch-action: manipulation;
	background: var(--btn-primary-bg);
	/* !important: savoy-child sets color and border:none with !important here */
	color: var(--btn-primary-text) !important;
	border: 2px solid var(--btn-primary-border) !important;
	border-radius: var(--kc-radius-md);
	box-shadow: var(--btn-primary-shadow);
	transition: transform .12s ease, box-shadow .12s ease;
}

body.kc-rd #nm-widget-panel #nm-cart-panel-continue:hover {
	background: var(--btn-primary-bg);
	transform: translate(1px, 1px);
	box-shadow: 2px 2px 0 var(--text);
}


/* --------------------------------------------------------------------------
   30.12  AJAX loader overlay
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel #nm-cart-panel-loader {
	background: var(--panel-bg);
}

body.kc-rd #nm-widget-panel #nm-cart-panel-loader .nm-loader-oldh5,
body.kc-rd #nm-widget-panel #nm-cart-panel-loader h5 {
	font-family: var(--kc-font-body);
	font-weight: 600;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .nm-cart-item-loader,
body.kc-rd #nm-widget-panel .nm-cart-panel-thumbnail-loader {
	background: var(--panel-bg);
}


/* --------------------------------------------------------------------------
   30.13  Focus
   Every interactive element in the panel gets the kit's focus ring. Outlines
   are never removed — hover gives a keyboard user nothing.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel a:focus-visible,
body.kc-rd #nm-widget-panel button:focus-visible,
body.kc-rd #nm-widget-panel input:focus-visible,
body.kc-rd #nm-widget-panel [tabindex]:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}

body.kc-rd #nm-widget-panel .nm-quantity-wrap .quantity .qty:focus-visible {
	outline-offset: -2px; /* the field is inset between two buttons */
}


/* --------------------------------------------------------------------------
   30.14  Breakpoints
   Mirrors Savoy's own 550px gutter change so our blocks stay aligned with the
   header, list and summary. 86.5% of this store's search traffic is mobile, so
   375px is the design target and this is the branch that actually runs.
   -------------------------------------------------------------------------- */

@media all and (max-width: 550px) {

	body.kc-rd #nm-widget-panel .kc-ladder,
	body.kc-rd #nm-widget-panel .kc-cart-empty {
		padding-left: 21px;
		padding-right: 21px;
	}

	/* Compact ladder: container padding only — the v3 component (66-track.css
	   §66.5) is already sized for a 375px panel (the mockup's own frame), so
	   the phone branch must not shrink track/dot geometry out from under it. */
	body.kc-rd #nm-widget-panel .kc-ladder {
		padding-top: 8px;
		padding-bottom: 6px;
	}

	body.kc-rd #nm-widget-panel .nm-widget-panel-header-inner {
		min-height: 60px;
	}

	body.kc-rd #nm-widget-panel .nm-cart-panel-summary-inner {
		padding-bottom: max(var(--kc-space-4), env(safe-area-inset-bottom, 0px));
	}
}

/* Very narrow phones. The +/- buttons deliberately do NOT narrow here: 44px is
   the kit's hard floor (§06 "minimum target 44pt on iOS, 48dp on Android", and
   §10 lists a sub-44pt target on the Never list), and the width it would buy
   was never contested. Measured at 320px: Savoy gives the panel
   calc(100% - 15px) = 305px, the list gutter above is 21px a side, and the
   thumbnail + gap take 76px — so the details column is 187px and the full-size
   136px stepper fits with 51px to spare. Only the number field, which is not a
   tap-to-increment target, gives up width; if a narrower branch is ever really
   needed, take it from there and not from the buttons. */
@media all and (max-width: 359px) {

	body.kc-rd #nm-widget-panel .nm-quantity-wrap .quantity .qty {
		width: 40px;
	}
}


/* --------------------------------------------------------------------------
   30.15  Reduced motion
   The panel itself slides in on a transform, and the threshold fill animates
   its width. Both are suppressed rather than merely shortened.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {

	body.kc-rd #nm-widget-panel,
	body.kc-rd #nm-widget-panel * {
		transition-duration: .01ms !important;
		animation-duration: .01ms !important;
		animation-iteration-count: 1 !important;
	}
}


/* --------------------------------------------------------------------------
   30.16  Panel upsell — one, pinned to the top of the footer
   Rendered server-side inside the fragment (inc-cart-panel.php), so it
   recomputes on every cart change and never needs a client clear/refill.
   The hook lands between subtotal and buttons; order:-1 on the flex footer
   moves it above the subtotal — top of the pinned area, where it does not
   scroll away with the line items (PLAYBOOK §3.3 #7). "Add" is a quiet
   bordered text control: amber in this panel belongs to Checkout alone, and
   the price appears exactly once.
   -------------------------------------------------------------------------- */

/* 2026-08-27: the upsell is the LAST row of the scrolling list
   (li.kc-upsell-li, inc-cart-panel.php) — pinned it cost the list 110px on a
   phone. Shell + inner box below. */
body.kc-rd #nm-widget-panel .product_list_widget > li.kc-upsell-li {
	display: block;
	margin: 0;
	padding: var(--kc-space-3) 0 0;
	border: 0;
}

body.kc-rd #nm-widget-panel .kc-cart-upsell {
	margin: 0;
	padding: 0;
	border: 0;
}

body.kc-rd #nm-widget-panel .kc-cart-upsell-label {
	margin: 0 0 var(--kc-space-2);
	font-family: var(--label-font);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--text-muted);
}

body.kc-rd #nm-widget-panel .kc-cart-upsell-row {
	display: flex;
	align-items: center;
	gap: var(--kc-space-3);
}

body.kc-rd #nm-widget-panel .kc-cart-upsell-thumb {
	flex: 0 0 44px;
}
body.kc-rd #nm-widget-panel .kc-cart-upsell-thumb img {
	display: block;
	width: 44px;
	height: 44px;
	object-fit: contain;
	border-radius: var(--kc-radius-md);
	background: var(--surface-sunken);
}

body.kc-rd #nm-widget-panel .kc-cart-upsell-body {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

body.kc-rd #nm-widget-panel .kc-cart-upsell-name {
	display: -webkit-box;
	-webkit-line-clamp: 2;   /* same clamp rule as the line items: nowrap is how "28g" falls off a name */
	-webkit-box-orient: vertical;
	overflow: hidden;
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 13px;
	line-height: 1.3;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .kc-cart-upsell-price,
body.kc-rd #nm-widget-panel .kc-cart-upsell-price .amount {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
	font-size: 13px;
	line-height: 1.2;
	color: var(--text);
}

/* The theme's a.button chain (padding, red hovers, 100% widths) must not reach
   this control; declare the whole box. */
body.kc-rd #nm-widget-panel a.kc-cart-upsell-add {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	margin: 0;
	padding: 0 var(--kc-space-4);
	background: transparent;
	color: var(--text);
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	line-height: 1;
	text-transform: none;
	text-decoration: none;
	cursor: pointer;
	touch-action: manipulation;
	-webkit-tap-highlight-color: transparent;
	transition: background-color .15s ease;
}
body.kc-rd #nm-widget-panel a.kc-cart-upsell-add:hover {
	background: var(--surface-sunken);
	color: var(--text);
}
/* WC core add-to-cart.js appends "View cart ->" after a successful add and
   flips classes; neither belongs in a 44px pill. */
body.kc-rd #nm-widget-panel a.kc-cart-upsell-add.loading { opacity: .6; pointer-events: none; }
body.kc-rd #nm-widget-panel .kc-cart-upsell .added_to_cart { display: none; }


/* --------------------------------------------------------------------------
   30.17  Add-to-cart failure bar (60-cart.js)
   Urgent is the correct hue: this is the one message on the surface about
   money the customer believes they just spent. role=alert carries it to AT.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-cart-panel .kc-cart-fail {
	margin: 0;
	padding: var(--kc-space-3) 37px;
	background: var(--urgent);
	color: #fff;
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 14px;
	line-height: 1.35;
}


/* --------------------------------------------------------------------------
   30.18  Stepper digit ink — measured, and deliberately NOT corrected
   Batch 18d's PDP control needed translateY(2.5px); this one was measured the
   same way (dark-pixel band scan of the rendered control at DPR2) and does
   not: marks' optical centres 56.5/56.5, digit 56.75 → residual -0.25 css px,
   inside the ±1px acceptance. The panel input centres by line-height:1 +
   flex, not by the PDP's line-height=box-height recipe, so the PDP's burn-in
   value must not be copied here. A first draft shipped translateY(2px) copied
   by analogy and the scan caught the digit sitting 1.75px LOW — the
   correction was the misalignment.
   -------------------------------------------------------------------------- */


/* --------------------------------------------------------------------------
   30.19  Gift row — engine-owned line, read-only by design
   The tier engine fixes the quantity of auto-added gift lines; the server
   gate in inc-cart-panel.php refuses qty edits for them. This section is the
   honest face of that fact: ×N as plain text where paid rows get a stepper,
   FREE (pine = confirmation, same semantics as the ladder's earned state)
   where paid rows get a price. The wdr "Discount: Free" chip hides here —
   the badge says it once. NN precedent: nn-native-cart.php gift rows
   ("stepper NO — quantity is fixed by the tier").
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel li.kc-gift-row .variation {
	display: none; /* its only content on a gift line is "Discount: Free" */
}

body.kc-rd #nm-widget-panel li.kc-gift-row .kc-gift-qty {
	flex: 0 0 auto;
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-size: 14px;
	font-weight: 600;
	line-height: 26px; /* the paid rows' pill height — gift rows keep the grid */
	color: var(--text-muted);
}

/* Round 2: the "yours — added automatically" note rides BESIDE ×N on the
   stepper/price line (emitted there by inc-cart-panel.php since l6), so the
   gift text column is name + one 26px line = the 48px thumbnail, same as a
   paid row. The old under-the-name block rule below stays for a fragment
   cached before the salt bump. */
body.kc-rd #nm-widget-panel li.kc-gift-row .nm-cart-panel-quantity-pricing .product-quantity {
	display: flex;
	align-items: center;
	gap: var(--kc-space-2);
	flex: 1 1 auto;
	min-width: 0;
}

body.kc-rd #nm-widget-panel li.kc-gift-row .product-quantity .kc-gift-note {
	flex: 0 1 auto;
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}

body.kc-rd #nm-widget-panel li.kc-gift-row .kc-gift-free {
	display: inline-block;
	padding: 2px 8px;
	font-family: var(--kc-font-body);
	font-weight: 700;
	font-size: 12.5px;
	letter-spacing: .05em;
	line-height: 1.4;
	color: var(--trust);
	background: var(--surface-sunken);
	border: 1px solid var(--trust);
	border-radius: 999px;
}

/* The "yours — added automatically" subline lives inside the (one-line)
   title span as a block; it takes its own line under the truncated name. */
body.kc-rd #nm-widget-panel li.kc-gift-row .nm-cart-panel-product-title .kc-gift-note {
	display: block;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}


/* --------------------------------------------------------------------------
   30.20  Ghost row + value tally — panel shells for the shared component
   Inner markup (.kc-ghost-inner / .kc-gift-tally) is built in
   inc-cart-panel.php and styled once in 66-track.css §66.5; this section owns
   only how the shells sit in THIS surface: the ghost <li> in the scrolling
   list (after the last real row — mini_cart_contents fires there), the tally
   pinned to the top of the footer. The inner wrapper is a <span> on purpose:
   §30.4's `> li > div { display:block }` blanket would blockify a div.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .product_list_widget > li.kc-ghost-row {
	display: block;
	margin: 0;
	/* Same gutter as the paid/gift rows (§30.4): the ghost row sits in the same
	   column and an 8px gutter here would read as a gap in the list. */
	padding: var(--kc-space-1) 0 calc(var(--kc-space-1) + 1px);
	border-bottom: 1px solid var(--border);
}

/* Tally row (li.kc-tally-li): after the ghost rows, before the upsell —
   mockup order rows → tally → money footer, with the tally now in the
   scrolling half (2026-08-27). The extra class on the chain beats §30.4's
   `> li > div { display:block }` blanket, which would stack the icon over
   the sentence. */
body.kc-rd #nm-widget-panel .product_list_widget > li.kc-tally-li {
	display: block;
	margin: 0;
	padding: var(--kc-space-3) 0 0;
	border: 0;
}

body.kc-rd #nm-widget-panel .product_list_widget > li.kc-tally-li > .kc-gift-tally {
	display: flex;
	margin: 0;
	padding: 8px 12px;
}

/* ===== 31-home-rails.css ===== */
/* ===========================================================================
   31-home-rails — the three front-page product rails.  Owner: rails agent
   (2026-08-23 wave); 31g/31h added by the rails-auto agent (2026-08-27,
   surface rails-auto-0827).

   Markup contract (inc-home.php):
     section.kc-hm-picks.kc-hm-rail   — New Arrivals / King Deals / Best
       Sellers. `.kc-hm-picks` buys the settled §29.5 geometry (1160 track,
       40px gutter, sechead flex row, Gazella H2 at 28px) from kc-redesign.css
       — nothing there is restated here. `.kc-hm-rail` adds only what three
       stacked rails need that one rail never did.
   2026-08-27 (surface usp-land): the USP money cell's second threshold line
   (`.kc-usp-cell-money > .kc-usp-fact-ship`) was styled here because
   29-home-usp.css belonged to another agent. The owner merged both $150
   thresholds into one label, inc-home.php stopped emitting that line, and
   both rule blocks were deleted rather than left as a dead layer. What
   survives is §31c below: the strain band's top margin, which is now load-
   bearing for a different reason than the one it was written for.
   =========================================================================== */

/* --- 31a. desktop: three rails need a bottom, one rail didn't ------------- */

/* §29.5 gives .kc-hm-picks zero bottom padding because a single rail used to
   hand off to .kc-hm-how's own top padding. With three rails stacked, each
   rail's 34px top padding is the whole rhythm — even, no special cases. The
   last rail hands off to .kc-usp-more-wrap's margin-top:30px as before. */

/* The shortcode's <ul> carries its own bottom whitespace per card; nothing to
   trim without reaching into inc-cards' component, so we don't. */

/* "See all" was a 13px-tall tap target (brief: ≥44px). inline-flex+min-height
   grows the box, not the type; the sechead keeps baseline alignment because
   an inline-flex box hands up its first-line baseline. Scoped to .kc-hm-rail:
   the one-rail layout owner signed off is not restyled anywhere it persists. */
body.home .kc-hm-rail .kc-hm-sechead a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

/* --- 31b. phones: one row you swipe, not a 2x2 wall ----------------------- */

/* Three rails as 2x2 grids = twelve full-size cards stacked a screen tall
   each; the page already runs 9,300px on a phone. Dispatch form: 4 cards,
   one swipeable row per rail. Cards keep the ~45% width they had in the 2-col
   grid, so the buy UI inside them reflows not at all; the half-visible third
   card is the scroll affordance. The <ul> is the scroll container (page
   overflow stays 0) and bleeds to the viewport edge through the section's
   18px gutter, matching how the hero carousel bleeds. */
@media (max-width: 768px) {
	body.home .kc-hm-rail .nm-products {
		display: flex;
		flex-wrap: nowrap;
		overflow-x: auto;
		scroll-snap-type: x proximity;
		scroll-padding-left: 18px;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
		margin: 0 -18px;
		padding: 0 18px 6px;
	}
	body.home .kc-hm-rail .nm-products::-webkit-scrollbar { display: none; }
	/* floats/widths from Savoy's block-grid are inert inside flex; the basis
	   is the whole card geometry. 45% ≈ the 166px the cards measured in the
	   old 2-col grid — same card, new axis. */
	body.home .kc-hm-rail .nm-products > li.product {
		flex: 0 0 45%;
		width: 45%;
		scroll-snap-align: start;
	}
}

/* --- 31c. the gap between the plate and the strain band ------------------- */

/* Written for the ship line (it made the strip a text-row taller and the
   hero→first-product gate is a measured number: phone 287px is the floor,
   desktop 193px), so the strain band's 40px top margin from kc-redesign.css
   §29 was dropped to 8px to buy that row back.

   2026-08-27 (usp-land): the ship line is gone and the whole strip is 37.8px
   shorter on a phone (287.5 -> 249.7), so the premise this patch was written
   for has expired. Measured after landing: phone gate 241.7, desktop 168.6.
   Restoring the 40px would cost +32 and land the phone at ~273.7, still
   13.3px inside the 287 floor — so a restore is now affordable, which it was
   not when this was written. Left at 8px on purpose: it is one number that
   six parallel surfaces are measuring against today, and moving it is the
   orchestrator's call, not this surface's. Kept as an override, not a dead
   layer: the base rule still owns the band's colour and geometry elsewhere. */
body.home .kc-sotw { margin-top: 8px; }

/* --- 31d. slot parity: the pline escape (owner 2026-08-24 round) ---------- */

/* Measured root cause of "King Deals 和 New Arrival 大小都不太一样": §26
   (kc-redesign.css:2249) hides the card pline above 520px on chip cards —
   chips carry the prices there — but the >4-option link cards (vapes) keep
   theirs, because "from $40.00" is that card's ONLY price. That one visible
   13px row is the single slot the Batch-11 baseline system did not reserve:
   on desktop the link cards ran 462.6px against everyone else's 449.6, and
   inside King Deals the two card types' CTAs sat 13px apart (366.6 vs 379.6
   from card top — Savoy's equalizer stretched the row but nothing pins the
   button, so the difference surfaced at the CTA, ledger Batch 12's exact
   failure mode).
   Fix per the slot system's own rule ("equal height comes from STRUCTURE,
   not a guessed number", §26): restore the hidden pline as RESERVED space.
   visibility:hidden, not a margin — the slot then tracks the real pline
   height forever instead of a snapshotted 13px. No price duplication: hidden
   ink is not a second price. Same 521px breakpoint as the hide rule it
   pairs with; scoped to home rails — category grids are another surface. */
@media (min-width: 521px) {
	body.home .kc-hm-rail .kc-buy-card:has(.kc-tier:not(.kc-tier-link)) .kc-pline,
	body.home .kc-hm-rail .kc-buy-card:has(.kc-btn-secondary) .kc-pline {
		display: flex;
		visibility: hidden;
	}
}

/* --- 31e. phone: "Choose options" must fit its own button ----------------- */

/* Measured: on the 45% rail card the CTA box is 93px inside the border, and
   "Choose options" is ~97px of INK — the label bled ~11px past EACH edge of
   the yellow box (screenshot: text visibly outside the button, worst look on
   the page). Two diagnosis traps paid for here: (1) scrollWidth under-reports
   centered overflow — the left-side bleed is invisible to it, so the deficit
   "measured" 6px when it was 22; (2) the ink is Poppins 700 while the sibling
   <button> CTAs compute to ROBOTO (theme .button family, a base-layer brand
   divergence reported upward, not fixed from this file) — width estimates
   from the Add buttons were estimates of the wrong typeface. In Poppins the
   label can NEVER fit this box on one line at any respectable size, so the
   §26.x nowrap trades into a wrap: two lines, centered in the SAME 3.4em box
   the base rule pins (min-height is font-relative and the font is untouched,
   so the box stays 44.1px = the Add buttons' box; Batch 11 already ruled a
   consistently wrapped CTA acceptable on narrow cards). !important because
   the base nowrap/padding are themselves !important (their fight with the
   WIG tap-target rule) — a spec fight with a live layer, not a shout-over. */
@media (max-width: 768px) {
	body.home .kc-hm-rail a.kc-cta.kc-cta-link.button {
		white-space: normal !important;
		line-height: 1.15;
		padding-left: 2px !important;
		padding-right: 2px !important;
	}
}

/* --- 31f. seam rhythm: one number per viewport (owner: 整个看着不流畅) ------ */

/* Measured seams (last content pixel -> next section's first) before this
   round — desktop: 34 / 64 / 40 / 58 / 54; phone: 34 / 68 / 34 / 78 / 50.
   Five different numbers each = the "not smooth" the owner is naming.
   Target: every seam below the strain band reads 58px on desktop, 44px on
   phones. Card rows already hand 24px (desk li padding) / 20px (phone li
   margin 14 + ul padding 6) of their own trailing air to a seam, so each
   entry pays only the remainder:

     seam                     desk: was -> pays      phone: was -> pays
     strain band -> New Arr.   34  -> pt 58          34  -> pt 44
     NA cards -> bento tiles   64  -> cats pt 34     68  -> woo mb 0 (cats 24)
     bento -> King Deals H2    40  -> cats pb 24     34  -> pt 44
     KD cards -> Best Sellers  58  -> (already 58)   78  -> woo mb 0, pt 24
     BS cards -> fine print    54  -> mt 34          50  -> woo mb 0 (mt 24)

   The phone column's recurring "woo mb 0": savoy-child style.css:4681 gives
   every non-archive .woocommerce block a 24px bottom margin on phones — a
   legacy rule for the OLD homepage's widget stack, now double-charging every
   rail seam. Killed inside home rails only (same 0,3,1 specificity, later
   file wins the tie).
   .kc-hm-cats geometry and .kc-usp-more-wrap's margin live in other owners'
   files (kc-redesign.css §29.4 / 29-home-usp.css); overridden from here, not
   edited there — same one-property pattern as 31c's .kc-sotw override, and
   the base rules keep every other property. Nothing above the strain band
   changes: hero->first-product stays 279.5 / 193.8 (the gated numbers). */
@media (min-width: 769px) {
	body.home .kc-sotw + .kc-hm-rail { padding-top: 58px; }
	body.home .kc-hm-cats { padding-top: 34px; padding-bottom: 24px; }
	body.home .kc-usp-more-wrap { margin-top: 34px; }
}
@media (max-width: 768px) {
	body.home .kc-hm-rail .woocommerce { margin-bottom: 0; }
	body.home .kc-sotw + .kc-hm-rail { padding-top: 44px; }
	body.home .kc-hm-cats + .kc-hm-rail { padding-top: 44px; }
	body.home .kc-hm-rail + .kc-hm-rail { padding-top: 24px; }
}

/* --- 31g. desktop: one row that overflows, not a 4-up wall (rails-auto) --- */

/* Owner 2026-08-27: the three rails glide slowly to the right on their own
   (assets/parts-js/31-rails-auto.js). Before this round a rail held exactly
   four cards and the 4-up block-grid filled the 1110px <ul> to the pixel:
   scrollWidth == clientWidth, nothing to glide. inc-home.php now hands each
   rail up to eight candidates; this block turns the wrapping grid into ONE
   row that overflows inside the <ul>, so cards 5–8 sit off to the right and
   the page itself never grows sideways (the <ul> is the scroll container,
   exactly as 31b already does on phones).
   The card must not change size, so nothing about its box is restated:
   Savoy's .large-block-grid-4 > li width:25% (33.33% on the medium grid)
   stays in charge and only flex-shrink goes to 0, so eight cards cannot be
   squeezed into four slots. The <ul> keeps the theme's own box — Savoy's
   grid-gutter option prints `[class*="block-grid-"] { margin: 0 -15px
   !important }` inline, so the box is 1110 wide whatever a stylesheet says
   (a first cut widened it to the 1160 track with margin/padding and the
   padding silently shrank every card to 265: 25% of 1060). No horizontal
   padding here, then: 25% of 1110 = 277.5, the number rails-uniform pinned,
   and card 1 renders where it always did. A card mid-glide is cut 15px out
   from where a resting card ends, the same clip the phone row has at the
   viewport edge. overflow-y is set explicitly because overflow-x:auto forces
   it to auto anyway; 3px of top padding keeps the card hover lift
   (translateY(-2px), kc-redesign.css) from being clipped, and the sechead
   gives those 3px back so H2→cards stays the 16px §29.5 set (margin-top on
   the <ul> is inside the same !important shorthand, so the pad is repaid at
   the sechead, not with a negative margin). Scrollbar hidden as 31b hides
   it: the glide is the affordance, hover stops it (report §5 raises the
   no-arrows trade-off for the owner). */
@media (min-width: 769px) {
	body.home .kc-hm-rail ul.nm-products {
		flex-wrap: nowrap;
		overflow-x: auto;
		overflow-y: hidden;
		padding-top: 3px;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
	}
	body.home .kc-hm-rail ul.nm-products::-webkit-scrollbar { display: none; }
	body.home .kc-hm-rail ul.nm-products > li.product { flex: 0 0 auto; }
	body.home .kc-hm-rail .kc-hm-sechead { margin-bottom: 13px; }
}

/* --- 31h. motion consent + snap hand-off (rails-auto) --------------------- */

/* Belt and braces with 31-rails-auto.js: the script checks the media query
   itself AND reads this custom property off the <ul>, so a stylesheet-level
   "no" is honoured even if a matchMedia listener is missed. Someone who asked
   for less motion gets the resting grid/row, still. */
body.home .kc-hm-rail ul.nm-products { --kc-rail-auto: 1; }
@media (prefers-reduced-motion: reduce) {
	body.home .kc-hm-rail ul.nm-products { --kc-rail-auto: 0; }
}

/* 31b's `scroll-snap-type: x proximity` and a script writing scrollLeft every
   frame fight: Chromium re-snaps programmatic scrolls, so the glide would
   jump card to card. While the script is driving (class set by it, never by
   PHP — server markup is unchanged) snapping is off; the moment a finger
   or wheel takes over the class comes off and the row snaps as 31b meant. */
body.home .kc-hm-rail ul.nm-products.kc-rail-auto-on { scroll-snap-type: none; }

/* ===== 32-mixmatch.css ===== */
/* 32 · Mix & Match entry points — PDP hint on member strains + home band.
   Owner: mix-match line. Tokens only; scoped body.kc-rd so the module kill switch removes
   it with the rest. The box page itself (picker) is styled by mu-plugins/kc-mix-match.php. */

/* ── PDP hint (under the size chips, before the stepper + Add to cart) ──── */
body.kc-rd .kc-mm-hint{flex:0 0 100%;width:100%;margin:10px 0 0;font:400 13.5px/1.45 var(--kc-font-body);color:var(--kc-ink);font-variant-numeric:tabular-nums}
body.kc-rd .kc-mm-hint-link{color:var(--kc-pine-700);font-weight:600;text-decoration:underline;text-underline-offset:3px;white-space:nowrap}

/* ── home band (inside .kc-home, same 1160px / 40px register as the rails) ── */
body.kc-rd .kc-mm-eyebrow{margin:0 0 6px;font:600 12px/1.2 var(--kc-font-body);letter-spacing:.14em;text-transform:uppercase;color:var(--kc-pine-300)}
body.kc-rd .kc-mm-h1{margin:0;font:400 clamp(30px,6vw,44px)/1.02 var(--kc-font-display);letter-spacing:-.01em;color:var(--kc-cream-50)}
body.kc-rd .kc-mm-lede{margin:12px 0 0;font:400 16px/1.55 var(--kc-font-body);color:var(--kc-cream-100);max-width:40ch}
body.kc-rd .kc-mm-lede strong{font-weight:600;color:var(--kc-cream-50);font-variant-numeric:tabular-nums}
body.kc-rd .kc-mm-band{max-width:1160px;margin:28px auto 0;padding:0 20px}
body.kc-rd .kc-mm-band-in{background:var(--kc-pine-700);border-radius:var(--kc-radius-lg);padding:26px 22px;display:grid;gap:18px;position:relative;overflow:hidden}
body.kc-rd .kc-mm-band-cta{margin-top:16px}
body.kc-rd .kc-mm-band-slots{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(4,64px);gap:10px}
body.kc-rd .kc-mm-band-slots li{width:64px;height:64px;margin:0;border-radius:16px;background:var(--kc-cream-50);display:grid;place-items:center;overflow:hidden;transform:rotate(calc(var(--r,0) * 1deg))}
body.kc-rd .kc-mm-band-slots li:nth-child(1){--r:-4}
body.kc-rd .kc-mm-band-slots li:nth-child(2){--r:3}
body.kc-rd .kc-mm-band-slots li:nth-child(3){--r:-2}
body.kc-rd .kc-mm-band-slots li:nth-child(4){--r:4}
body.kc-rd .kc-mm-band-slots li img{width:100%;height:100%;object-fit:contain;padding:4px}
body.kc-rd .kc-mm-band-slots li.is-empty{background:transparent;border:1.5px dashed rgba(255,255,255,.4)}
body.kc-rd .kc-mm-band-slots li.is-empty span{font:600 13px/1 var(--kc-font-data);color:var(--kc-cream-100)}
@media (min-width:768px){
	body.kc-rd .kc-mm-band{padding:0 40px;margin-top:34px}
	body.kc-rd .kc-mm-band-in{grid-template-columns:1fr auto;align-items:center;padding:36px 40px}
	body.kc-rd .kc-mm-band-slots{grid-template-columns:repeat(2,96px);gap:14px}
	body.kc-rd .kc-mm-band-slots li{width:96px;height:96px;border-radius:22px}
}

/* ===== 40-nav-menu.css ===== */
/* ==========================================================================
   40 · MENUS — desktop dropdowns, Shop index panel, mobile drawer, login shell
   Group A (no SEO surface of its own, but every href in it is an internal-link
   asset, so nothing here may remove one). Owner: nav-menu agent. Tokens only.
   The login popup's INTERIOR is owned by 50-account.css — see §40.5.
   --------------------------------------------------------------------------
   DOM as of 2026-08-21 ROUND 2. The Shop panel and the mobile drawer are now
   SERVER-RENDERED by includes/kc-redesign/inc-nav.php. Read that file's header
   before editing this one: it explains which hooks build which half and why,
   and it is the thing that made most of the old fight in this file disappear.

   (a) DESKTOP MAIN NAV
       header#nm-header > .nm-header-inner > .nm-header-row > .nm-header-col
         > nav.nm-main-menu > ul#nm-main-menu-ul.nm-menu
           > li.menu-item[.menu-item-has-children] > a
             > div.sub-menu             (ordinary dropdowns: Brands, Locations)
                 > ul.nm-sub-menu-ul > li.menu-item > a
       NOTE: .sub-menu is a DIV here, not a UL. No .megamenu / .megamenu.full
       class is present on any li, so every Savoy `.megamenu*` rule is dead.
       Active-state markers confirmed on /product-category/weed/:
         li#menu-item-15033 gets  current-menu-ancestor current-menu-parent

   (b) THE SHOP INDEX PANEL — PHP, no longer an injected script.
       li.menu-item.kc-nav-shop > a
         > div.sub-menu.kc-nav
             > .kc-nav__inner
                 > .kc-nav__cols > section.kc-nav__col[.kc-nav__col--wide]
                     > a.kc-nav__head > .kc-nav__thumb + .kc-nav__headtext
                     > ul.kc-nav__list[.kc-nav__list--split] > li.kc-nav__row
                         > a.kc-nav__link > .kc-nav__label + .kc-nav__n
                     > details.kc-nav__more > summary + ul
                 > aside.kc-nav__rail > a.kc-nav__deal + a.kc-nav__railrow …
             > .kc-nav__retired          (mushroom anchors, display:none)
       It keeps the class `sub-menu` on purpose — that is what Savoy's
       `.nm-menu li:not(.no-hover):hover > .sub-menu` hover rule and §40.2's
       focus-within opener both key on. There is no JS behind the desktop panel.

       GONE, and this is the important part for anyone reading old rules:
       `.header-open-shop` no longer exists in the document, so every rule in
       savoy-child/style.css keyed on it now matches nothing. That includes the
       (0,5,8) link chain, the (0,8,1) `filter: contrast(.3)` hover, the
       32px-Gazella heading rule, and :1676-1700 — seven !important
       background-image rules pointing at the PRODUCTION origin, which is how
       staging came to be hot-linking live-store photography. Retired by
       removing their subject rather than by overriding them.

   (c) MOBILE DRAWER — NOT Savoy's #nm-mobile-menu.
       #nm-mobile-menu does not exist in the document at all (savoy-child's
       footer.php has the get_template_part commented out). nm-core.min.js still
       binds to it, so its jQuery set is empty and its open/close is a no-op.
       The real drawer is child-theme markup that lives AFTER </header>:
         div.mobile-container-nav-menu > ul#main-nav.nav.mobile-menu
           > li.kc-nav-m__li
               > .kc-nav-m__row > a.kc-nav-m__link + button.kc-nav-m__toggle
               > ul.kc-nav-m__sub[hidden] > li.kc-nav-m__subli > a
       ⚠️ The hamburger's open/close handler used to live inside the injected
       script that inc-nav.php now suppresses. assets/parts-js/40-nav.js owns
       it, together with the accordion. If the drawer stops opening, look there.

   (d) LOGIN POPUP
       div#nm-login-popup-wrap.nm-login-popup-wrap.mfp-hide
         > div#customer_login.nm-myaccount-login > .nm-myaccount-login-inner
           > div#nm-login-wrap    > .login-form-old-h2 + form.login
           > div#nm-register-wrap > .login-form-old-h2 + form.register
       Magnific Popup ("inline" type) keeps the node under <body>, so the
       body.kc-rd scope still matches once the popup is open.
       NOTE the nesting: #customer_login is a CHILD of #nm-login-popup-wrap, so
       50-account.css's `body.kc-rd #customer_login …` rules already style this
       popup's interior. Only the outer container is styled here.

   --------------------------------------------------------------------------
   COMPETING SELECTORS I HAVE TO BEAT (source order matters as much as weight —
   savoy/style.css, savoy-child/style.css and savoy-child/assets/css/main.css
   all load BEFORE kc-parts.css, but the Customizer's inline
   <style class="nm-custom-styles"> block is emitted ~6KB AFTER it, so on that
   one I must out-specify, not merely match):

     LATER IN SOURCE than this file — specificity win is mandatory
       .nm-menu li a                       (0,1,2)  color #707070
       .nm-menu .sub-menu                  (0,2,0)  background #282828
       .nm-menu .sub-menu li a             (0,2,2)  color #a0a0a0
       .nm-menu-icon span                  (0,1,1)  background #707070

     savoy/style.css
       .nm-menu .sub-menu                          display:none; left:-12px;
                                                   min-width:248px; padding:18px 0 22px
       .nm-menu li:not(.no-hover):hover > .sub-menu (0,4,1)  the ONLY open trigger — hover only
       .nm-menu .sub-menu li a             (0,2,2)  14px / #a0a0a0 / padding 8px 27px
       .nm-menu .sub-menu li a:hover       (0,3,2)  color #eee
       .nm-login-popup-wrap                (0,1,0)  background #fff; padding 47px 37px 50px

     savoy-child/style.css
       #nm-header li.menu-item a           (1,1,2)  color:black, Poppins 600 18px, uppercase
                                           ^ style.css:982, top level, no media query. It is a
                                             DESCENDANT selector, so it sets colour and size for
                                             every <a> under the header — the top row, the
                                             ordinary dropdowns AND every link in the index
                                             panel. The ID means no class-only rule of mine can
                                             touch any of them. §40.1, §40.2 and §40.3 all carry
                                             #nm-header for exactly this reason.
       #nm-header .sub-menu                (1,1,0)  background: white
                                           ^ style.css:1005. THIS is the panel colour today:
                                             Savoy's and the Customizer's #282828 (0,2,0) both
                                             already lose to it on the ID column. §40.2 has to
                                             carry #nm-header to repaint it at all.
       ul#nm-main-menu-ul > li:nth-child(1) .sub-menu  (1,1,1) padding-right:30px
                                           ^ style.css:1557 — aimed at the Shop panel by
                                             POSITION. §40.3.1 beats it at (1,3,2).
       #nm-main-menu-ul > li.menu-item > a:hover   (1,2,2)  filter: contrast(0.3)
                                           ^ style.css:665. `filter` repaints the whole element,
                                             so it washes the label AND any underline drawn on
                                             it. Reset in §40.1.
       #nm-header li.menu-item-has-children > a:after (1,1,3) border: solid black
       .mobile-menu > li.menu-item > a     (0,2,2)  18px centred uppercase #000
       .nav.mobile-menu .menu-item a       (0,3,1)  text-transform:uppercase
                                           ^ BOTH now match nothing: the drawer's rows are
                                             li.kc-nav-m__li, not li.menu-item. That is why
                                             §40.4 needs no counter-rule for centring or caps.
       .mobile-container-nav-menu.active   (0,2,0)  top:116px  (138px @<=650)
       .mobile-menu                        (0,1,0)  height:calc(100% - 116px); overflow-y:scroll
       .sub-menu > ul > li.menu-item a:hover        (0,3,3) filter:contrast(.3)
                                           ^ still live for the Brands / Our Locations
                                             dropdowns; reversed in §40.2.
       every `.header-open-shop …` rule    DEAD — see (b) above.

     savoy-child/assets/css/main.css
       .nm-menu li a                       (0,1,2)  font-size:14px !important; padding:16px 9px
                                           ^ main.css:6029, INSIDE @media (max-width:1199px).
                                             Savoy hides .nm-main-menu at <=991, so this governs
                                             992–1199px only. At >=1200px the top row is 18px
                                             from #nm-header li.menu-item a above. Left alone —
                                             see §40.1. NOTE it also reaches the index panel's
                                             links in that band; §40.3.4 declares font-size at
                                             (1,4,3), which loses to an !important, so panel
                                             rows are 14px there either way — the same size
                                             §40.3.4 asks for, so nothing renders wrong.
       (main.css's .nm-login-popup-wrap button / field / label / lost-password
        rules are 50-account.css's fight to pick, not this file's — see §40.5)

   Everything is scoped `body.kc-rd` and dies with the kill switch.
   ========================================================================== */


/* --------------------------------------------------------------------------
   40.1  Desktop main nav — the trigger row
   -------------------------------------------------------------------------- */

/* (1,3,3) beats #nm-header li.menu-item a (1,1,2) which paints pure #000 and
   the Customizer's .nm-menu li a (0,1,2) #707070.
   Font-size is left to the theme on purpose, but it is NOT one size: main.css's
   14px !important is scoped to @media (max-width:1199px), and the desktop nav
   is hidden below 992, so the row is 14px only across 992–1199px. From 1200px
   up it is 18px, from #nm-header li.menu-item a. Both are legitimate Poppins
   600 caps UI labels under the kit, so neither fight is worth picking. */
body.kc-rd #nm-header .nm-main-menu .nm-menu > li > a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--btn-min-height);
	color: var(--text);
	transition: color .12s ease;
}

/* Hover: keep the same ink and move the signal to the underline so hover and
   current read as the same vocabulary.
   `filter: none` is the load-bearing line here, not the colour. The child theme
   hovers this exact row with
     #nm-main-menu-ul > li.menu-item > a:hover { filter: contrast(0.3) }
   (style.css:665, (1,2,2), no !important). This rule out-specifies it, but
   specificity only settles properties BOTH rules declare — leaving `filter`
   undeclared lets it apply anyway, and a filter repaints the element after the
   fact: the ink label washes out to roughly #636160 and it drags the underline
   below (and the pine current-state underline) out with it. Same reversal as
   §40.2 and §40.3 already do for the sub-menu and the mega panel; this row was
   the one instance still uncovered. */
body.kc-rd #nm-header .nm-main-menu .nm-menu > li > a:hover {
	color: var(--text);
	filter: none;
	box-shadow: inset 0 -2px 0 var(--border-strong);
}

/* Current / ancestor. WP emits current-menu-item on the exact page and
   current-menu-ancestor + current-menu-parent on the section (verified on
   /product-category/weed/). All three get the same pine rule so "you are in
   Shop" survives even on a subcategory the top row does not link to.
   inset box-shadow rather than a pseudo-element: ::after is already taken by
   the child theme's chevron on menu-item-has-children. */
body.kc-rd #nm-header .nm-main-menu .nm-menu > li.current-menu-item > a,
body.kc-rd #nm-header .nm-main-menu .nm-menu > li.current-menu-ancestor > a,
body.kc-rd #nm-header .nm-main-menu .nm-menu > li.current-menu-parent > a,
body.kc-rd #nm-header .nm-main-menu .nm-menu > li.current_page_item > a,
body.kc-rd #nm-header .nm-main-menu .nm-menu > li.current_page_ancestor > a {
	color: var(--text);
	box-shadow: inset 0 -2px 0 var(--trust);
}

/* The chevron is hard-coded `border: solid black`. currentColor makes it
   follow the link, including the pine current state.
   (1,2,4) beats #nm-header li.menu-item-has-children > a:after (1,1,3). */
body.kc-rd #nm-header li.menu-item-has-children > a::after {
	border-color: currentColor;
	opacity: .55;
	transition: transform .14s ease, opacity .14s ease;
}
body.kc-rd #nm-header li.menu-item-has-children:hover > a::after,
body.kc-rd #nm-header li.menu-item-has-children:focus-within > a::after {
	opacity: 1;
	/* theme sets rotate(45deg) = pointing down; 225deg = pointing up = open */
	transform: rotate(225deg);
}


/* --------------------------------------------------------------------------
   40.2  Desktop dropdown panels
   -------------------------------------------------------------------------- */

/* Panel surface. (1,3,1), and the ID is required, not decorative.
   Three rules paint this panel, and the one that wins today is neither of the
   ones you would guess:
     savoy/style.css + Customizer inline   .nm-menu .sub-menu   (0,2,0)  #282828
     savoy-child/style.css:1005            #nm-header .sub-menu (1,1,0)  white
   The child theme's ID rule beats both #282828 declarations outright, so the
   live dropdown is WHITE — the charcoal this file used to claim it was
   repainting has not rendered in a long time. A class-only (0,3,1) here would
   have lost to that same ID rule and changed nothing at all, including on the
   Shop mega panel (§40.3.1 out-specifies at (1,3,2) but declares no
   background, so it inherits whatever wins here).
   `.nm-menu` is only ever ul#nm-main-menu-ul and ul#nm-right-menu-ul, both
   inside #nm-header, so the prefix costs no coverage. The mobile copy of this
   panel lives after </header> on ul#main-nav.nav.mobile-menu and is styled
   separately in §40.4.5.
   Real-world effect of the fix: white -> cream surface, a small shift, not the
   large one a charcoal starting point would have implied. */
body.kc-rd #nm-header .nm-menu .sub-menu {
	background: var(--panel-bg);
	color: var(--text);
	border: 1px solid var(--border);
	border-top: 0;
	border-radius: 0 0 var(--kc-radius-md) var(--kc-radius-md);
	box-shadow: var(--panel-shadow);
	padding: var(--kc-space-2) 0;
	min-width: 260px;
}

/* KEYBOARD. Savoy opens the panel on `:hover` only, so a keyboard user could
   tab into the trigger and never reach the children. focus-within opens it,
   and tabbing past the last child closes it again — dismissible without a
   pointer. (0,5,2) beats .nm-menu li:not(.no-hover):hover > .sub-menu (0,4,1).
   Esc-to-close still needs a JS listener — reported in needs_lead. */
body.kc-rd .nm-menu li.menu-item-has-children:focus-within > .sub-menu {
	display: block;
}

/* Rows. (1,3,3), and again the ID is load-bearing. Beating the two
   `.nm-menu .sub-menu li a` (0,2,2) rules in savoy/style.css and the Customizer
   block is the easy half; the rule that actually decides colour and size here
   is #nm-header li.menu-item a (1,1,2) — catalogued at the top of this file,
   beaten for the top row in §40.1, and then missed for these rows, which sit
   under the same #nm-header and match the same descendant selector. Without
   the prefix `color` and `font-size` below both lose on the ID column and the
   rows render #000 at 18px instead of ink at 15px. The kit is explicit that
   ink is a warm near-black, not #000.
   44px, not the 48px floor, and that is deliberate rather than an oversight:
   `.nm-header.menu-centered .nm-main-menu { display: none }` fires at <=991
   (savoy/style.css; #nm-header does carry .menu-centered), so this panel has
   no touch path at all — below 992 the offscreen panel in §40.4 is the only
   menu that renders, and that one is on the 48px floor throughout.
   Colour is --text, never --text-muted: nav a customer is using to find a
   product is not decoration. */
body.kc-rd #nm-header .nm-menu .sub-menu li a {
	display: flex;
	align-items: center;
	min-height: 44px;
	font-size: 15px;
	line-height: 1.3;
	color: var(--text);
	padding: var(--kc-space-2) var(--kc-space-5);
	transition: background-color .12s ease;
}
body.kc-rd #nm-header .nm-menu .sub-menu li a:hover {
	color: var(--text);
	background: var(--surface-sunken);
	/* the child theme washes hovered sub-menu links out with
	   filter: contrast(0.3) at (0,3,3); the class-only rule below reverses it */
}
body.kc-rd .nm-menu .sub-menu > ul > li.menu-item a:hover {
	filter: none;
}

/* SPECULATIVE — MATCHES NOTHING ON THE SITE AS IT STANDS TODAY. Verified, not
   assumed: `menu-item-has-children` occurs exactly 3 times inside
   ul#nm-main-menu-ul on /, /shop/ and /product-category/weed/, and all 3 are
   top-level li. There is no second-level item with children anywhere in the
   menu, so no `.sub-menu li.menu-item-has-children` exists to style.
   Kept rather than deleted because it costs two unmatched selectors and is the
   treatment a third menu level would need the day someone adds one in the WP
   menu editor — but it has never rendered, so nothing below is evidence of how
   the surface looks now. Carries the #nm-header prefix so that if it does start
   matching it beats #nm-header li.menu-item a (1,1,2) rather than silently
   losing its colour, which is the exact trap the live rows above fell into. */
body.kc-rd #nm-header .nm-menu .sub-menu li.menu-item-has-children > a {
	font-family: var(--label-font);
	font-size: 12px;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--text-muted);
}
body.kc-rd #nm-header .nm-menu .sub-menu li.menu-item-has-children > a::after {
	color: var(--text-faint);
}

/* Current item inside a panel. Only the panels the inline script does NOT
   rebuild keep their current-menu-item marker (Brands, Our Locations); the
   Shop panel's markup is thrown away and replaced, so it has none.
   #nm-header for the same reason as the rows above: without it `color` loses to
   #nm-header li.menu-item a (1,1,2) and the pine current state would have been
   carried by the inset box-shadow alone. */
body.kc-rd #nm-header .nm-menu .sub-menu li.current-menu-item > a,
body.kc-rd #nm-header .nm-menu .sub-menu li.current_page_item > a {
	color: var(--trust);
	font-weight: 600;
	box-shadow: inset 3px 0 0 var(--trust);
}

/* A nested-flyout rule (.sub-menu .sub-menu) used to sit here. Removed: it is
   the same non-existent third menu level as the block above, so it never
   matched a single element, and unlike that block it had no forward-looking
   value — savoy already positions a nested flyout, and it would inherit this
   panel's surface from the rule at the top of §40.2 regardless. */


/* --------------------------------------------------------------------------
   40.3  The Shop index panel  (.kc-nav)   — direction A, owner pick 2026-08-21
   --------------------------------------------------------------------------
   WHAT CHANGED UNDER THIS SECTION
   Everything §40.3 used to fight is gone rather than overridden. inc-nav.php
   removes savoy-child/functions.php's two wp_nav_menu_*_items filters, so the
   inline <script> that rebuilt the Shop dropdown from templatera 14976 is
   never emitted and the panel is server-rendered PHP with its own class
   namespace. Consequences worth knowing before editing this file:

     - `.header-open-shop` no longer exists in the document. Every rule keyed
       on it in savoy-child/style.css is dead code — including :1676-1700, the
       seven !important background-image rules that pointed at the PRODUCTION
       origin (staging was hot-linking live-store photography through them).
       7 of the 25 hard-coded production URLs reported in round 1 are retired
       by deletion of their subject, not by an override.
     - The two !important declarations §40.3.1 needed are gone with it. They
       existed only to beat an inline style attribute written by that script.
       There is no !important anywhere in this section now.
     - The old (0,5,8) / (0,8,1) selector chains in savoy-child/style.css that
       this file used to out-class also no longer match anything, so the rules
       below are ordinary component CSS at ordinary weights.

   WHAT STILL HAS TO BE BEATEN (all of it descendant-of-#nm-header):
     #nm-header li.menu-item a            (1,1,2)  #000, Poppins 600 18px, caps
     #nm-header .sub-menu                 (1,1,0)  background white
     ul#nm-main-menu-ul > li:nth-child(1) .sub-menu (1,1,1) padding-right:30px
     .nm-menu .sub-menu                   (0,2,0)  display:none, left:-12px,
                                                   min-width:248px, padding
     .nm-menu .sub-menu li a              (0,2,2)  14px #a0a0a0, padding 8px 27px
     .nm-menu .sub-menu li a:hover        (0,3,2)  colour #eee
   Every selector in this section carries #nm-header for the first of those:
   it is a DESCENDANT selector, so it paints every <a> inside the panel, and a
   class-only rule loses `color` and `font-size` to it on the ID column. That
   is the exact trap §40.2 documents falling into once already.

   The panel opens on `.nm-menu li:not(.no-hover):hover > .sub-menu` — Savoy's
   own hover rule, unchanged — which is why the server-rendered container still
   carries the class `sub-menu` alongside `kc-nav`. §40.2's focus-within rule
   opens it from the keyboard for the same reason.
   -------------------------------------------------------------------------- */

/* 40.3.1  Full bleed.
   The li has no .megamenu class, so the panel is absolutely positioned inside
   a position:relative li partway across the header; left:-12px and
   min-width:248px then make it a narrow dropdown hanging off the Shop label.
   Making the li static hands the containing block to .nm-header-inner
   (position:relative, full width, no max-width), which is what Savoy's own
   full-width megamenu resolves against. (1,3,2) and (1,3,1) beat all three
   competitors listed above without !important — nothing writes an inline
   style on this element any more. */
body.kc-rd #nm-main-menu-ul > li.kc-nav-shop {
	position: static;
}
body.kc-rd #nm-main-menu-ul > li.kc-nav-shop > .kc-nav {
	left: 0;
	right: 0;
	width: 100%;
	max-width: 100vw;
	min-width: 0;
	padding: 0;
	border-left: 0;
	border-right: 0;
	border-radius: 0 0 var(--kc-radius-md) var(--kc-radius-md);
}

/* 40.3.2  The grid.
   Five category columns and a rail. Flower gets ~1.8 of a share because it
   holds 290 of the 595 products and 49% of revenue, and its list runs in two
   text columns — that is what removes the 336px of dead cream the old panel
   left under columns 2-5 (measured 665px tall, of which half was empty).
   minmax(0,1fr) rather than 1fr: grid items default to min-width:auto and a
   long label ("Full-Spectrum Oil") would otherwise push the track wider than
   its share and overflow the panel horizontally. */
body.kc-rd #nm-header .kc-nav .kc-nav__inner {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 240px;
	gap: var(--kc-space-5);
	padding: var(--kc-space-5) var(--gutter);
	align-items: start;
	text-align: left;
}
body.kc-rd #nm-header .kc-nav .kc-nav__cols {
	display: grid;
	grid-template-columns: 2.15fr repeat(4, minmax(0, 1fr));
	gap: var(--kc-space-5);
}
/* 992-1199px: the rail moves UNDER the columns instead of beside them.
   Measured before choosing: keeping it as a side column at 1024 leaves the
   five category tracks 108px wide and six labels wrap to two lines. Dropping
   it below gives them 140px and nothing wraps — the panel pays ~80px of height
   for it and is still ~460px against the 665px it replaced. This band is real
   hardware (small laptops, landscape tablets); Savoy hides the desktop nav
   entirely below 992, so this is the narrowest width the panel ever renders
   at. */
@media (max-width: 1199px) {
	body.kc-rd #nm-header .kc-nav .kc-nav__inner {
		grid-template-columns: minmax(0, 1fr);
		gap: var(--kc-space-4);
	}
	/* The rail's own switch to a horizontal strip is NOT here — it is at the end
	   of §40.3.7, after the rules it overrides. A media query adds no
	   specificity, so a block written here would have been beaten by the base
	   `flex-direction: column` further down the file purely on source order.
	   Cost me one deploy: the rail rendered as a centred vertical stack at
	   1024 and the panel came out 674px instead of ~470. */
}

/* 40.3.3  Column heading.
   The old heading was Gazella 32px with a full-bleed category photograph
   behind the whole column — the photo is what made the panel read as 2014.
   The art survives as a 34px thumbnail beside the word, so the column is still
   recognisable to the 88% of revenue that is repeat customers, but it is no
   longer printed under the links.

   Poppins, not Gazella, and that is a rule not a preference: the display face
   is only ever used at >=28px (brand law), and these are 15px section labels.
   The old 32px Gazella instance was itself a bug — savoy-child/style.css asked
   for 32px at (0,2,1) and style.css:982 held it at 18px on the ID column, so
   what actually rendered was an 18px display face, which is the one size the
   kit forbids outright. */
body.kc-rd #nm-header .kc-nav .kc-nav__head {
	display: flex;
	align-items: center;
	text-align: left;
	gap: var(--kc-space-2);
	min-height: 44px;
	padding: 0 0 var(--kc-space-2);
	margin-bottom: var(--kc-space-2);
	border-bottom: 1px solid var(--border);
	color: var(--text);
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 700;
	line-height: 1.2;
	letter-spacing: .01em;
	text-transform: uppercase;
}
body.kc-rd #nm-header .kc-nav .kc-nav__head:hover {
	color: var(--text);
	filter: none;
	border-bottom-color: var(--border-strong);
}
body.kc-rd #nm-header .kc-nav .kc-nav__head.is-here {
	color: var(--trust);
	border-bottom-color: var(--trust);
}
body.kc-rd #nm-header .kc-nav .kc-nav__headtext {
	display: flex;
	flex-direction: column;
	gap: 1px;
	min-width: 0;
}
body.kc-rd #nm-header .kc-nav .kc-nav__headmeta {
	font-family: var(--label-font);
	font-size: 11px;
	font-weight: 400;
	letter-spacing: 0;
	text-transform: none;
	color: var(--text-faint);
	font-variant-numeric: tabular-nums;
}

/* The 34px art tile. Fixed box so a missing image (CBD has no category
   thumbnail — owner question Q6) leaves the heading on the same baseline as
   its four neighbours instead of hanging 34px to the left of them. */
body.kc-rd #nm-header .kc-nav .kc-nav__thumb {
	flex: 0 0 auto;
	display: block;
	width: 34px;
	height: 34px;
	border-radius: var(--kc-radius-sm);
	background: var(--surface-sunken);
	overflow: hidden;
}
body.kc-rd #nm-header .kc-nav .kc-nav__thumb img {
	display: block;
	width: 34px;
	height: 34px;
	object-fit: cover;
}

/* 40.3.4  Link rows.
   40px, not the 48px floor, and deliberately: this panel only renders from
   992px up (Savoy hides .nm-main-menu below that — the phone gets §40.4
   instead), so it is a pointer surface. Thirteen flower rows at 48px would add
   ~100px of panel for no benefit to anyone using a mouse. The @media
   (hover: none) block at the end of this sub-section restores the floor for
   the touch devices that do land in the 992-1199px band, same idiom as
   parts/20-buttons.css.

   (1,4,3) — #nm-header for the ID column, four classes to clear
   .nm-menu .sub-menu li a (0,2,2) and the panel's own nesting. */
body.kc-rd #nm-header .kc-nav .kc-nav__list {
	margin: 0;
	padding: 0;
	list-style: none;
}
/* Two text columns, 12px apart, and the Flower track's 2.15fr share is tuned
   to the same constraint: at 1280 the longest label in this panel
   ("Pre-Rolled Joints", 118px at 14px Poppins) plus its gap and its number
   needs ~137px of track, and the widest 1fr sibling label ("Disposable Pens")
   needs ~133px. Measured both at 1280 and 1440 with every disclosure open;
   nothing wraps at either. Move either number and something wraps. */
body.kc-rd #nm-header .kc-nav .kc-nav__list--split {
	column-count: 2;
	column-gap: var(--kc-space-3);
}
body.kc-rd #nm-header .kc-nav .kc-nav__list li {
	margin: 0;
	break-inside: avoid;
}
body.kc-rd #nm-header .kc-nav .kc-nav__list li a.kc-nav__link {
	display: flex;
	align-items: center;
	justify-content: space-between;
	text-align: left;
	gap: var(--kc-space-2);
	min-height: 40px;
	padding: var(--kc-space-1) 0;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 500;
	line-height: 1.25;
	text-transform: none;
	letter-spacing: 0;
	color: var(--text);
	transition: color .12s ease;
}
body.kc-rd #nm-header .kc-nav .kc-nav__list li a.kc-nav__link:hover {
	color: var(--text);
	filter: none;
	text-decoration: underline;
	text-underline-offset: 3px;
}
body.kc-rd #nm-header .kc-nav .kc-nav__row.is-here > a.kc-nav__link {
	color: var(--trust);
	font-weight: 700;
}
@media (hover: none) {
	body.kc-rd #nm-header .kc-nav .kc-nav__list li a.kc-nav__link {
		min-height: var(--btn-min-height);
	}
}

/* The count.
   A number, not a badge: brand law says nothing in the menu may look like a
   control you can press, so no pill, no border, no background — the one thing
   in this store that spends money is Add to cart. --label-font is the kit's
   data face and is inherently tabular; font-variant-numeric is belt and braces
   for the fallback stack, and it is what stops the right-hand edge of the
   column shimmering as the digits change between rows. */
/* The label takes the slack; the number never shrinks and never wraps. */
body.kc-rd #nm-header .kc-nav .kc-nav__label {
	min-width: 0;
	text-align: left;
}
body.kc-rd #nm-header .kc-nav .kc-nav__n {
	flex: 0 0 auto;
	font-family: var(--label-font);
	font-size: 11px;
	font-weight: 400;
	color: var(--text-faint);
	font-variant-numeric: tabular-nums;
}

/* 40.3.5  Nothing in stock.
   Five of the 32 subcategory links led to a category with zero buyable
   products, and the worst of them — AA+, 78 products, none of them purchasable
   — sat SECOND in the Flower column. PHP sinks these to the bottom of their
   list (order otherwise untouched: re-sorting a menu by stock level every time
   the warehouse moves is a muscle-memory tax on repeat customers); this greys
   them so the eye skips them first.

   Muted, never removed. The link still works, still crawls, still ranks — the
   underlying problem is a catalogue one (CLAUDE.md §3.6: 488 products flagged
   out of stock, 42 of them demonstrably mislabelled) and a menu is not the
   place to hide it. --text-muted, not --text-faint: this has to stay legible,
   it is a de-emphasis and not a disabled state. */
body.kc-rd #nm-header .kc-nav .kc-nav__row.is-empty > a.kc-nav__link {
	color: var(--text-muted);
}
body.kc-rd #nm-header .kc-nav .kc-nav__row.is-empty > a.kc-nav__link:hover {
	color: var(--text);
}

/* 40.3.6  "Also in …" — the seven labels that link to their own parent.
   Batteries / Vape Kits / Accessories -> /vape/, Live Resin / Distillate /
   Full-Spectrum Oil -> /concentrate/, Isolates -> /cbd/. In templatera 14976
   all seven carry the PARENT href, so clicking one lands you on the page you
   were already looking at. They are folded, not deleted (a link is a link, and
   URL retirement is the 301 project's job) and not repointed at their real
   terms (three of those have zero products — pointing the header at empty
   archives is CLAUDE.md §6.3 #11 done by hand).

   <details> rather than a JS disclosure: it is keyboard-operable and
   screen-reader-labelled for free, and it costs the panel one 28px row per
   column instead of three or four full rows. */
body.kc-rd #nm-header .kc-nav .kc-nav__more {
	margin-top: var(--kc-space-2);
}
body.kc-rd #nm-header .kc-nav .kc-nav__more > summary {
	display: flex;
	align-items: center;
	gap: 6px;
	min-height: 28px;
	list-style: none;
	cursor: pointer;
	font-family: var(--label-font);
	font-size: 11px;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--text-faint);
}
body.kc-rd #nm-header .kc-nav .kc-nav__more > summary::-webkit-details-marker { display: none; }
body.kc-rd #nm-header .kc-nav .kc-nav__more > summary::after {
	content: "";
	width: 5px;
	height: 5px;
	border-right: 1px solid currentColor;
	border-bottom: 1px solid currentColor;
	transform: rotate(45deg) translate(-1px, -1px);
}
body.kc-rd #nm-header .kc-nav .kc-nav__more[open] > summary::after {
	transform: rotate(225deg) translate(-1px, -1px);
}
body.kc-rd #nm-header .kc-nav .kc-nav__more > summary:hover { color: var(--text); }
/* 🔴 box-sizing is declared here, and it is not boilerplate.
   The document sets `*, ::before, ::after { box-sizing: inherit }` on top of a
   border-box root, so everything inherits border-box — except that a theme
   rule flips the bare <ul> inside this <details> to content-box, and every
   descendant then inherits THAT. Under content-box, `min-height: 32px` is the
   CONTENT height and the inherited 8px 24px padding is added on top, so these
   rows measured 48px instead of 32px and "Full-Spectrum Oil" wrapped onto
   three lines in a 126px track. Diagnosed by walking computed box-sizing up
   the ancestor chain, not by reading stylesheets.
   Padding is declared for the same reason: undeclared, these links inherit
   `padding: 8px 27px` from .nm-menu .sub-menu li a, which is a dropdown-row
   value and has nothing to do with a folded list. */
body.kc-rd #nm-header .kc-nav .kc-nav__more ul,
body.kc-rd #nm-header .kc-nav .kc-nav__more li,
body.kc-rd #nm-header .kc-nav .kc-nav__more li a.kc-nav__link--quiet {
	box-sizing: border-box;
}
body.kc-rd #nm-header .kc-nav .kc-nav__more ul {
	margin: 0;
	padding: 0;
	list-style: none;
}
body.kc-rd #nm-header .kc-nav .kc-nav__more li a.kc-nav__link--quiet {
	display: flex;
	align-items: center;
	text-align: left;
	min-height: 32px;
	padding: 0 0 0 var(--kc-space-3);
	font-family: var(--kc-font-body);
	font-size: 13px;
	line-height: 1.25;
	font-weight: 400;
	text-transform: none;
	letter-spacing: 0;
	color: var(--text-muted);
}
body.kc-rd #nm-header .kc-nav .kc-nav__more li a.kc-nav__link--quiet:hover {
	color: var(--text);
	filter: none;
}
/* Same touch correction as the main rows: 992-1199px includes tablets, and a
   32px target under a finger is not a target. */
@media (hover: none) {
	body.kc-rd #nm-header .kc-nav .kc-nav__more li a.kc-nav__link--quiet,
	body.kc-rd #nm-header .kc-nav .kc-nav__more > summary {
		min-height: var(--btn-min-height);
	}
}

/* 40.3.7  The rail.
   Navigation only. The marquee directly above the header already carries the
   delivery promise, the $200 threshold and the free gift; repeating them here
   would put one message under two owners.

   The Deals tile is the panel's single merchandising slot and the only place a
   price appears anywhere in the menu (brand law: one price per surface, and
   weights in grams — 1 oz is 28G). Tinted block, no border, no shadow, no
   hover lift: it is a link to a category, and nothing in this menu is allowed
   to imitate a buy control. */
body.kc-rd #nm-header .kc-nav .kc-nav__rail {
	display: flex;
	flex-direction: column;
	gap: var(--kc-space-1);
	padding-left: var(--kc-space-5);
	border-left: 1px solid var(--border);
}
body.kc-rd #nm-header .kc-nav .kc-nav__deal {
	display: flex;
	align-items: center;
	text-align: left;
	gap: var(--kc-space-3);
	padding: var(--kc-space-3);
	margin-bottom: var(--kc-space-2);
	border-radius: var(--kc-radius-md);
	background: var(--surface-sunken);
	color: var(--text);
	text-transform: none;
	letter-spacing: 0;
}
body.kc-rd #nm-header .kc-nav .kc-nav__deal:hover {
	color: var(--text);
	filter: none;
}
body.kc-rd #nm-header .kc-nav .kc-nav__deal-shot {
	flex: 0 0 auto;
	width: 56px;
	height: 56px;
	border-radius: var(--kc-radius-sm);
	overflow: hidden;
	background: var(--card-shot-bg);
}
body.kc-rd #nm-header .kc-nav .kc-nav__deal-shot img {
	display: block;
	width: 56px;
	height: 56px;
	object-fit: cover;
}
body.kc-rd #nm-header .kc-nav .kc-nav__deal-text {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}
body.kc-rd #nm-header .kc-nav .kc-nav__deal-name {
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 700;
	line-height: 1.2;
}
body.kc-rd #nm-header .kc-nav .kc-nav__deal-price {
	font-family: var(--label-font);
	font-size: 12px;
	color: var(--text);
	font-variant-numeric: tabular-nums;
}
body.kc-rd #nm-header .kc-nav .kc-nav__deal-price .amount { font-family: inherit; }
body.kc-rd #nm-header .kc-nav .kc-nav__deal-meta {
	font-family: var(--label-font);
	font-size: 11px;
	color: var(--text-faint);
	font-variant-numeric: tabular-nums;
}
body.kc-rd #nm-header .kc-nav .kc-nav__railrow {
	display: flex;
	align-items: center;
	text-align: left;
	gap: var(--kc-space-2);
	min-height: 40px;
	padding: 0;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 500;
	line-height: 1.25;
	text-transform: none;
	letter-spacing: 0;
	color: var(--text);
}
body.kc-rd #nm-header .kc-nav .kc-nav__railrow .kc-nav__label { flex: 1 1 auto; }
body.kc-rd #nm-header .kc-nav .kc-nav__railrow:hover {
	color: var(--text);
	filter: none;
	text-decoration: underline;
	text-underline-offset: 3px;
}
body.kc-rd #nm-header .kc-nav .kc-nav__railrow.is-here {
	color: var(--trust);
	font-weight: 700;
}
@media (hover: none) {
	body.kc-rd #nm-header .kc-nav .kc-nav__railrow { min-height: var(--btn-min-height); }
}

/* 992-1199px: the rail becomes a strip under the columns. See §40.3.2 for why
   (side rail leaves the five category tracks 108px wide and six labels wrap;
   below the columns they get 140px and nothing wraps) and for why this block
   lives here rather than up there with the grid change. */
@media (max-width: 1199px) {
	body.kc-rd #nm-header .kc-nav .kc-nav__rail {
		flex-direction: row;
		flex-wrap: wrap;
		align-items: center;
		justify-content: flex-start;
		gap: var(--kc-space-5);
		padding-left: 0;
		padding-top: var(--kc-space-4);
		border-left: 0;
		border-top: 1px solid var(--border);
	}
	body.kc-rd #nm-header .kc-nav .kc-nav__deal {
		flex: 0 0 auto;
		margin-bottom: 0;
		min-width: 230px;
	}
	body.kc-rd #nm-header .kc-nav .kc-nav__railrow {
		flex: 0 0 auto;
	}
	body.kc-rd #nm-header .kc-nav .kc-nav__railrow .kc-nav__label { flex: 0 0 auto; }
}

/* 40.3.8  The Psilocybin column, still suppressed.
   Round 1 found the mushroom LINKS hidden but the column they lived in still
   rendering, complete with a full-bleed photograph of caps painted on it by
   savoy-child/style.css:1696 with !important — 235x60 on desktop 1440, 335x80
   on phone. That whole structure is gone now; inc-nav.php emits the three
   anchors inside one container and this hides the container.

   Rendered-then-hidden rather than omitted, on purpose: deleting them would
   quietly remove three URLs from the site's internal link graph, and retiring
   a URL is a 301 job (CLAUDE.md §5 #3), not a menu job. The rule dies on its
   own the day those redirects ship and the markup stops being generated. */
body.kc-rd .kc-nav .kc-nav__retired {
	display: none;
}


/* --------------------------------------------------------------------------
   40.4  Mobile drawer  (div.mobile-container-nav-menu)
   --------------------------------------------------------------------------
   Rebuilt in this round. The drawer used to be 2,738px — 4.2 screens — with
   2,486px of that inside ONE <li>: the Shop row, holding the same injected
   mega block as the desktop panel. Eleven of the twelve links on the first
   screen were flower subcategories; Gear (51 products) and Our Locations (the
   local-SEO hub, on a store whose main battleground is local search) had no
   phone entry point at all; and the Shop row's own <a> was blanked by the
   script (item.innerText = "") so it rendered as an unlabelled strip.

   inc-nav.php now renders the drawer server-side and flat: every top-level
   destination on the first screen, subcategories behind a per-row disclosure.
   86.5% of this store's search traffic is mobile, so this is the copy of the
   menu that matters most.

   ⚠️ The open/close handler for the hamburger lived in the script that is no
   longer emitted. assets/parts-js/40-nav.js owns it now. If the drawer ever
   stops opening, that file is the first place to look — not this one.

   Nothing in this section needs #nm-header: .mobile-container-nav-menu sits
   AFTER </header>, so #nm-header li.menu-item a never reached it. The rows are
   also no longer .menu-item, so .mobile-menu > li.menu-item > a (0,2,2) and
   .nav.mobile-menu .menu-item a (0,3,1) — the two rules that used to centre
   and uppercase everything here — no longer match either.
   -------------------------------------------------------------------------- */

/* 40.4.1  Geometry.
   The child theme hard-codes top:116px (138px under 650px) — the height of the
   OLD chrome. kc-redesign.css §21/§22 changed that chrome, so both numbers are
   wrong and the panel either floats below the header or tucks under it.
   kc-redesign.js measures the real chrome and publishes --kc-chrome-h on
   :root; read it rather than guess. The 116px fallback is today's value, so a
   JS failure degrades to current behaviour rather than to a broken panel.
   (0,3,1) beats .mobile-container-nav-menu.active (0,2,0) at every breakpoint,
   including inside the max-width:650px block — a media query adds no weight. */
/* 🔴 `right: 0` is load-bearing, not tidying.
   savoy-child/style.css:1487 gives this panel `position:fixed; left:0;
   height:100%` and NO width, so it is shrink-to-fit. Until this round it was
   stretched to the viewport by an inline `width: 100vw` that the injected
   script wrote onto the .sub-menu inside it. Server-rendering the drawer
   removed that inline style, and the panel immediately collapsed to about
   225px of a 375px screen with the page showing through beside it — caught in
   the first phone screenshot after deploy, not by reasoning about it. */
body.kc-rd .mobile-container-nav-menu {
	right: 0;
	max-width: 100%;
	background: var(--panel-bg);
	transition: top .24s ease;
}
body.kc-rd .mobile-container-nav-menu.active {
	top: var(--kc-chrome-h, 116px);
	box-shadow: var(--panel-shadow);
}

/* 🔴 DO NOT PUT `visibility: hidden` ON THE CLOSED PANEL.
   Kept as a warning even though the specific mechanism has changed. The old
   injector compared `item.innerText == "SHOP"`, and innerText returns RENDERED
   text: making the subtree unrendered made that comparison fail and the whole
   category block silently never injected — no console error, five bare links,
   and only visible if you screenshot the menu. The drawer is server-rendered
   now so that exact failure cannot recur, but this theme reads innerText in
   several places. General rule for this codebase: never make an element
   unrendered before code that reads its text has run. The closed panel is
   already invisible — the theme parks it off-screen via `top`. */

/* (0,4,1) beats .mobile-menu (0,1,0). overflow-y was `scroll`, which paints a
   permanent gutter; `auto` plus overscroll containment stops the page behind
   from scrolling once the list bottoms out. */
body.kc-rd .mobile-container-nav-menu .nav.mobile-menu {
	height: calc(100% - var(--kc-chrome-h, 116px));
	overflow-y: auto;
	overflow-x: hidden;
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
	background: var(--panel-bg);
	/* env() needs its own fallback: a bare env() the UA does not know
	   invalidates the whole declaration, taking the top padding with it. */
	padding: var(--kc-space-2) 0 max(var(--kc-space-6), env(safe-area-inset-bottom, 0px));
	margin: 0;
	list-style: none;
}

/* 40.4.2  Rows.
   Left-aligned, hairline-separated, the whole row is the target. The old rows
   were centred 18px caps with a 25px margin under each and roughly 21px of
   tappable height against a 48px floor. */
body.kc-rd .mobile-container-nav-menu .mobile-menu > li.kc-nav-m__li {
	margin: 0;
	text-align: left;
	border-bottom: 1px solid var(--border);
	list-style: none;
}
body.kc-rd .mobile-container-nav-menu .mobile-menu > li.kc-nav-m__li:last-child {
	border-bottom: 0;
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__row {
	display: flex;
	align-items: stretch;
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__link {
	flex: 1 1 auto;
	display: flex;
	align-items: center;
	gap: var(--kc-space-3);
	min-height: var(--btn-min-height);
	min-width: 0;
	padding: var(--kc-space-2) var(--gutter);
	font-family: var(--kc-font-body);
	font-size: 16px;
	font-weight: 600;
	line-height: 1.25;
	text-align: left;
	text-transform: none;
	letter-spacing: 0;
	color: var(--text);
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__name {
	flex: 1 1 auto;
	min-width: 0;
}
/* The 34px art tile, same box as the desktop heading so a missing image (CBD)
   does not shift the label. Rows without art — Sale, Brands, Our Locations,
   About, Sign in — deliberately have no tile: they are not categories and a
   blank square in front of each would read as five broken images. */
body.kc-rd .mobile-container-nav-menu .kc-nav__thumb {
	flex: 0 0 auto;
	display: block;
	width: 34px;
	height: 34px;
	border-radius: var(--kc-radius-sm);
	background: var(--surface-sunken);
	overflow: hidden;
}
body.kc-rd .mobile-container-nav-menu .kc-nav__thumb img {
	display: block;
	width: 34px;
	height: 34px;
	object-fit: cover;
}
body.kc-rd .mobile-container-nav-menu .kc-nav__n {
	flex: 0 0 auto;
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	color: var(--text-faint);
	font-variant-numeric: tabular-nums;
}

/* 40.4.3  Current state — absent entirely before the redesign. */
body.kc-rd .mobile-container-nav-menu .mobile-menu > li.kc-nav-m__li.is-here > .kc-nav-m__row > .kc-nav-m__link {
	color: var(--trust);
	background: var(--surface-sunken);
	box-shadow: inset 4px 0 0 var(--trust);
}

/* 40.4.4  The disclosure button.
   A <button>, not a second <a href="#">: it opens a panel, it does not
   navigate, and a fake destination in the header would show up in the href
   diff this surface is accepted against. 48px wide so the target clears the
   floor on its own without adding height to the row, and separated from the
   link's own tap area by a hairline so a customer can see that the row has two
   halves — tap the name to go to the category, tap the chevron to open it. */
body.kc-rd .mobile-container-nav-menu .kc-nav-m__toggle {
	flex: 0 0 auto;
	width: 52px;
	min-height: var(--btn-min-height);
	display: flex;
	align-items: center;
	justify-content: center;
	background: none;
	border: 0;
	border-left: 1px solid var(--border);
	border-radius: 0;
	padding: 0;
	margin: 0;
	color: var(--text-muted);
	cursor: pointer;
	box-shadow: none;
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__chev {
	display: block;
	width: 9px;
	height: 9px;
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: rotate(45deg) translate(-2px, -2px);
	transition: transform .16s ease;
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__toggle[aria-expanded="true"] {
	color: var(--text);
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__toggle[aria-expanded="true"] .kc-nav-m__chev {
	transform: rotate(225deg) translate(-2px, -2px);
}

/* 40.4.5  The opened subcategory list.
   [hidden] is the state, set in the markup and toggled in 40-nav.js — no
   height animation, because animating an accordion inside a scrolling drawer
   is how you get a list that jumps under the finger mid-tap. Indented to the
   art-tile grid so the child rows line up under their parent's label rather
   than under its thumbnail. */
body.kc-rd .mobile-container-nav-menu .kc-nav-m__sub {
	margin: 0;
	padding: 0 0 var(--kc-space-2);
	list-style: none;
	background: var(--surface-sunken);
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__sub[hidden] { display: none; }
body.kc-rd .mobile-container-nav-menu .kc-nav-m__subli {
	margin: 0;
	list-style: none;
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__subli > a {
	display: flex;
	align-items: center;
	gap: var(--kc-space-3);
	min-height: var(--btn-min-height);
	padding: var(--kc-space-1) var(--gutter) var(--kc-space-1) calc(var(--gutter) + 34px + var(--kc-space-3));
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 500;
	line-height: 1.25;
	text-align: left;
	text-transform: none;
	letter-spacing: 0;
	color: var(--text);
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__subli.is-empty > a { color: var(--text-muted); }
body.kc-rd .mobile-container-nav-menu .kc-nav-m__subli.is-quiet > a {
	color: var(--text-muted);
	font-weight: 400;
	font-size: 14px;
}
body.kc-rd .mobile-container-nav-menu .kc-nav-m__subli.is-here > a {
	color: var(--trust);
	font-weight: 700;
}

/* 40.4.5b  SEO parity rows (SEO_PARITY_AUDIT A2).
   The production mobile menu carried two long-tail anchors on every page —
   «About Weed Online Dispensary» → /about/ and «Brands We Offer at KingCrop
   Online Weed Dispensary» → /brand/ — and the rebuild dropped both, which the
   parity gate flags as site-wide copy loss. Owner iron rule: live-site copy
   may only be added to, never removed, so inc-nav.php appends them verbatim
   as the drawer's last two rows. Styled as footnotes, not nav rows: small,
   regular weight, muted ink — but still real, visible links (no display:none,
   no visibility tricks: hidden anchor text is worthless to the crawler and
   reads as cloaking). Touch height stays on the 44px floor from the base
   .kc-nav-m__link rule, which is deliberately NOT overridden here. */
body.kc-rd .mobile-container-nav-menu .mobile-menu > li.kc-nav-m__li--seo > .kc-nav-m__row > .kc-nav-m__link {
	font-size: 12.5px;
	font-weight: 400;
	color: var(--text-muted);
}

/* 40.4.6  Hamburger bars — the Customizer paints them #707070 at (0,1,1).
   Savoy's own open state (body.mobile-menu-open) rotates lines 1 and 3 into an
   X and blanks line 2; nothing to add but the colour. */
body.kc-rd #nm-mobile-menu-button .nm-menu-icon span {
	background: var(--text);
	height: 2px;
}


/* --------------------------------------------------------------------------
   40.5  Login popup  (#nm-login-popup-wrap) — CONTAINER ONLY
   --------------------------------------------------------------------------
   OWNERSHIP, resolved after reading the sibling partials rather than assuming:
   50-account.css owns the login/register component, and its rules genuinely
   reach in here. #customer_login is a CHILD of #nm-login-popup-wrap, so every
   `body.kc-rd #customer_login …` rule in that file already applies inside this
   popup — the panes, the title, the fields, the stacked labels, the two
   buttons, the divider rule, the focus rings, and a fix I had missed entirely
   (the lost-password trigger reuses magnific-popup's .mfp-close, which pins it
   absolutely into the corner of the card; 50-account.css puts it back in the
   row and gives it a 48px target).

   I had written a parallel treatment for all of that. It had to go, and not
   because it was worse — because it was SECOND. My selectors were ID-scoped
   (#nm-login-popup-wrap) against their class-scoped ones, so mine would have
   silently won on specificity regardless of partial order, and won only on the
   handful of properties I happened to declare. The result would have been one
   component with two authors: their static stacked label wearing my muted
   colour, their ink lost-password link repainted pine by me. That is the
   failure mode CLAUDE.md §6.7 #4 is about, and the brief's instruction for
   this surface was explicit — do not restyle inputs, coordinate through the
   tokens. Ceding it is the coordination.

   What remains below is the one thing no sibling touches and the only part
   that is genuinely menu-surface work: the overlay container itself. Savoy
   paints it flat white with no border (shop.css, .nm-login-popup-wrap, (0,1,0));
   (1,1,1) repaints it as a panel so the white login card inside it reads as a
   card on a surface rather than as a white box on a white box.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-login-popup-wrap {
	background: var(--panel-bg);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-lg);
	box-shadow: var(--panel-shadow);
}



/* --------------------------------------------------------------------------
   40.6  Focus + motion — every interactive element on this surface
   --------------------------------------------------------------------------
   Neither the parent nor the child theme defines a single :focus-visible rule
   anywhere in the nav, so keyboard users currently get whatever the UA draws
   (nothing, in Safari, for an <a> without an outline). Nothing here removes an
   outline; it only replaces the default with a higher-contrast one.

   The popup's interior is NOT listed: 50-account.css already rings everything
   under #customer_login, which is inside it. Repeating those selectors here
   would have been two files declaring one outline — and at the same weight, so
   which one applied would have depended on nothing more than the numeric
   prefix on a filename.
   -------------------------------------------------------------------------- */

body.kc-rd .nm-menu a:focus-visible,
body.kc-rd .nm-menu button:focus-visible,
body.kc-rd .nm-menu summary:focus-visible,
body.kc-rd .mobile-container-nav-menu a:focus-visible,
body.kc-rd .mobile-container-nav-menu button:focus-visible,
body.kc-rd #nm-mobile-menu-button:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 2px;
	border-radius: var(--kc-radius-sm);
}

@media (prefers-reduced-motion: reduce) {
	/* Savoy fades every dropdown in with a 0.3s `nm_menu_anim` keyframe, and
	   the child theme slides the offscreen panel with `transition: all 1s`. */
	body.kc-rd .nm-menu .sub-menu {
		animation: none !important;
	}
	body.kc-rd .mobile-container-nav-menu,
	body.kc-rd .mobile-container-nav-menu.active,
	body.kc-rd #nm-header .nm-main-menu .nm-menu > li > a,
	body.kc-rd #nm-header li.menu-item-has-children > a::after,
	body.kc-rd #nm-header .kc-nav .kc-nav__list li a.kc-nav__link,
	body.kc-rd .mobile-container-nav-menu .kc-nav-m__chev,
	body.kc-rd .nm-menu .sub-menu li a {
		transition: none !important;
	}
	/* Nothing inside the popup is listed — 50-account.css carries its own
	   reduced-motion block for #customer_login. */
}


/* --------------------------------------------------------------------------
   40.7  THE DESKTOP HAMBURGER — it should never have been on desktop
   --------------------------------------------------------------------------
   Owner, 2026-08-27: "桌面右上角那个图标点开一片空白."

   DIAGNOSIS (staging, 1280x900, homepage, measured — not inferred):
     - The icon is Savoy's own #nm-mobile-menu-button. It is printed
       unconditionally by savoy-child/template-parts/header/header-layout.php
       (<li class="nm-menu-offscreen">, line 60) and nothing in either theme
       hides it above 992px. Rect [1228, 49, 53x48] at 1280 — top right corner,
       exactly where the owner pointed.
     - Clicking it runs 40-nav.js, which adds .active to
       .mobile-container-nav-menu. That panel is position:fixed, 1280x900,
       z-index 1001, background var(--panel-bg), and .active parks it at
       top: var(--kc-chrome-h) = 109px.
     - Its contents do NOT render at this width: the drawer's own
       ul.nav.mobile-menu computes to display:none above 992 (theme rule).
       So the panel is a full-viewport sheet of cream over the page with all
       fourteen of its rows present in the DOM and none of them painted.
       That is the "一片空白" — a 1280x791 blank rectangle, and the only way
       out of it is to find the same 53px icon again.

   Desktop already has the whole menu inline (Shop + the index panel + five
   more top-level rows). The drawer is not a fallback there, it is a trap.

   TWO RULES, because hiding the button is not the same as disarming the panel.
   The button goes first (display:none also takes it out of the tab order — it
   was the 11th focusable element on the page). The panel is then made
   unreachable at this width whatever puts .active on it: a resize from phone
   to desktop with the drawer open used to leave the blank sheet behind, and
   40-nav.js now also strips .active on that crossing (belt and braces — the
   CSS alone is enough, the JS keeps aria-expanded honest).
   -------------------------------------------------------------------------- */
@media (min-width: 992px) {
	/* !important is not decoration here and it is not a specificity dodge:
	   kc-redesign.css:724 declares `body.kc-rd #nm-right-menu-ul > li
	   { display: inline-flex !important }` — an !important on the exact
	   property, in a file this surface does not own. Measured: without the
	   flag the button still rendered at [1228,49,53x48] after deploy. The
	   rule that needs narrowing is listed in the report; when it is, this
	   flag comes off. */
	body.kc-rd #nm-right-menu-ul > li.nm-menu-offscreen { display: none !important; }

	/* (0,3,0) — beats .mobile-container-nav-menu.active (0,2,0). The drawer's
	   markup is still served (its two SEO-parity anchors stay in the HTML at
	   every width, as they were: the theme was already display:none-ing the
	   list itself up here, so no rendered text changes). */
	body.kc-rd .mobile-container-nav-menu,
	body.kc-rd .mobile-container-nav-menu.active { display: none; }
}


/* --------------------------------------------------------------------------
   40.8  COLUMN HEADINGS AFTER THE CATEGORY RENAME  (nav-0827)
   --------------------------------------------------------------------------
   §40.3.3 sized these headings for the words that were hard-coded into
   inc-nav.php: FLOWERS (7), EDIBLES (7), VAPES (5), EXTRACT (7), CBD (3).
   The labels are read from the terms now, and the 2026-08-27 restructure
   renamed two of them: EXTRACT → CONCENTRATES (12) and FLOWERS → WEED (4).

   Measured at 1280 the moment that shipped: a 1fr track is 139px, the 34px
   art tile plus its 8px gap leaves 97px, and CONCENTRATES at 15px/700/upper
   needs ~120px. One word — nothing to wrap at — so it ran straight across the
   rule into the CBD column. Visible in
   scripts/surface_shots/nav-0827/after-desk-topright-clicked.png (the first
   deploy of this round), which is why the shot is kept.

   Two rules, and the order matters:
     - a hard floor (`overflow-wrap: anywhere`) so NO term name can ever spill
       into the next column again, whatever the catalogue is renamed to next;
     - a size step below 1340 so the names that exist today still fit on one
       line and never actually hit that floor. Above 1340 the track is 165px
       and 15px fits, so nothing changes on a large screen.
   The disclosure summary ("Also in Concentrates") gets the same treatment: it
   is 11px, but it is prefixed and was wrapping to two lines in the same track.
   -------------------------------------------------------------------------- */
body.kc-rd #nm-header .kc-nav .kc-nav__headname {
	min-width: 0;
	overflow-wrap: anywhere;
}
body.kc-rd #nm-header .kc-nav .kc-nav__more > summary { min-width: 0; }

/* Sized against the LONGEST label the catalogue can currently produce, not
   against the ones that happen to be short. CONCENTRATES is 12 characters of
   uppercase Poppins 700; a 1fr track is 139px at 1280 and 165px at 1440, and
   the art tile plus its gap has to come out of that first. 12px/28px leaves
   ~12px of slack at the tightest width (1280) — verified by counting line
   boxes on the heading span at 1024 / 1280 / 1440, not by eye. */
body.kc-rd #nm-header .kc-nav .kc-nav__cols {
	grid-template-columns: 1.95fr repeat(4, minmax(0, 1fr));
}
body.kc-rd #nm-header .kc-nav .kc-nav__head {
	font-size: 12px;
	letter-spacing: .02em;
	gap: 6px;
}
body.kc-rd #nm-header .kc-nav .kc-nav__head .kc-nav__thumb,
body.kc-rd #nm-header .kc-nav .kc-nav__head .kc-nav__thumb img {
	width: 24px;
	height: 24px;
}
body.kc-rd #nm-header .kc-nav .kc-nav__more > summary {
	font-size: 10px;
	letter-spacing: .02em;
}
/* 992–1199 only: the legacy child-theme sheet (assets/css/main.css) declares
   `.nm-menu li a { font-size: 14px !important }` in exactly this band, and the
   panel's headings are <a>s inside .nm-menu — so above they take the 12px, and
   here they silently took 14px and wrapped again. This is the one width where
   the flag is needed; the rule to narrow is listed in the report. */
@media (max-width: 1199px) {
	body.kc-rd #nm-header .kc-nav .kc-nav__head { font-size: 12px !important; }
}


/* --------------------------------------------------------------------------
   40.9  THE DRAWER HAS TO BE ABOVE THE CATALOGUE DOCK  (nav-0827)
   --------------------------------------------------------------------------
   Found while screenshotting the drawer's current-page state on
   /product-category/weed/aaa/: the catalogue's sticky filter bar (.kc-dock,
   position:fixed, z-index 1200 — another surface's component) paints on top of
   the open drawer, so the last two rows of the menu sit under a black pill
   that belongs to the page behind it.

   Not caused by this round — neither z-index was touched — but it is this
   surface's panel that is being covered, so it is fixed from this side rather
   than by reaching into someone else's partial. Only the OPEN state is raised:
   the closed drawer keeps 1001 so nothing about the normal stacking changes.
   1201 is one above the dock and still below the header (1002 is below it, but
   the drawer starts at --kc-chrome-h, i.e. under the header, so it cannot
   cover it) and far below the side-cart panel (100000).
   Reported to the orchestrator: the tidier long-term fix is for the dock to
   hide itself while a full-screen drawer is open.
   -------------------------------------------------------------------------- */
body.kc-rd .mobile-container-nav-menu.active { z-index: 1201; }


/* --------------------------------------------------------------------------
   40.10  THE PANEL OUTLIVES THE POINTER  (fix2-search-nav, F09)
   --------------------------------------------------------------------------
   Partner file: assets/parts-js/40-nav.js §40.6, which carries the measured
   defect (the panel closes on step 2 of a 30-step diagonal to "All brands"),
   the geometry that causes it, and the intent algorithm. This file only moves
   the open state off `:hover` and onto a class, because `display` cannot be
   transitioned and `:hover` cannot be delayed.

   THE GATE IS `html.kc-nav-intent`, AND IT IS THE WHOLE SAFETY STORY.
   That class is added by 40-nav.js §40.6 at parse time. If that file does not
   load, does not parse, is deferred away by an optimiser, or bails because the
   device has no fine pointer, the class never appears, NEITHER rule below
   matches, and Savoy's own
       .nm-menu li:not(.no-hover):hover > .sub-menu { display: block }   (0,4,1)
   keeps running exactly as it does today. The failure mode of the JS is
   "today's menu", never "no menu".

   SPECIFICITY, counted rather than assumed:
     closer      (1,4,3)   #nm-main-menu-ul beats Savoy's (0,4,1) on the ID
                           column, so the plain hover open stops firing.
     opener      (1,5,3)   one class more than the closer.
     focus-within(1,5,3)   MUST be restated here. §40.2's keyboard rule is
                           (0,5,2) and would lose to the closer above — tabbing
                           into SHOP would stop reaching its 33 links, which is
                           a WCAG 2.4.3 regression handed out for free. It is
                           re-declared at the closer's weight, after it.
   Scoped to #nm-main-menu-ul, not .nm-menu: .nm-menu is also ul#nm-right-menu-ul
   (the account / search / bag cluster), and nothing there should ever depend
   on a class this file's JS owns.

   >=992px only: below that Savoy hides .nm-main-menu outright and the drawer
   in §40.4 is the only menu, so these rules would have nothing to act on and
   the JS does not run there either.
   -------------------------------------------------------------------------- */

@media (min-width: 992px) {
	html.kc-nav-intent body.kc-rd #nm-main-menu-ul > li.menu-item-has-children > .sub-menu {
		display: none;
	}
	html.kc-nav-intent body.kc-rd #nm-main-menu-ul > li.menu-item-has-children.kc-nav-open > .sub-menu {
		display: block;
	}
	html.kc-nav-intent body.kc-rd #nm-main-menu-ul > li.menu-item-has-children:focus-within > .sub-menu {
		display: block;
	}

	/* The chevron already rotates on :hover / :focus-within (§40.1). While the
	   panel is held open by intent the pointer is often no longer on the
	   trigger, so the arrow would spring back while the panel it points at is
	   still on screen. Same declaration, keyed to the same class that holds
	   the panel, so the two never disagree. */
	html.kc-nav-intent body.kc-rd #nm-header li.menu-item-has-children.kc-nav-open > a::after {
		opacity: 1;
	}
}

/* ===== 42-marquee.css ===== */
/* ==========================================================================
   42. Top announcement marquee — direction C (owner's pick, 2026-08-21)
   --------------------------------------------------------------------------
   Pine ground, 38px, Poppins 12/700, a line icon in front of every offer and
   no separator glyph — the icon does that job. Loads after kc-redesign.css
   §21, so these rules win ties on equal specificity; nothing here needs
   !important.

   Two things in §21 are still load-bearing and deliberately NOT overridden:
     .kc-marquee position / background / overflow  (1520-1533)
     :root --kc-marquee-h                          (1515)
   A partial may not define :root, so the 34 -> 38 height change is
   redeclared below on body.kc-rd. Custom properties inherit, and all three
   consumers of the token (.kc-marquee's own height, #nm-header's top offset,
   and — via kc-redesign.js's measured --kc-chrome-h — .nm-header-placeholder)
   are inside <body>, so body-level is enough and, unlike setting it from JS,
   it is right on the very first painted frame.
   ========================================================================== */

body.kc-rd {
	/* Direction C is 4px taller than the bar it replaces. #nm-header reads
	   this for its top offset; get it wrong and the header sits 4px inside
	   the bar. 42-marquee.js mirrors the MEASURED height onto documentElement
	   as well, for anything that ever reads the token above <body>. */
	--kc-marquee-h: 38px;
}

.kc-marquee {
	/* Fallback duration for the pre-JS frame; 42-marquee.js replaces it with
	   groupWidth / speed so the pixel rate is the same at every viewport. */
	--kc-mq-dur: 24s;
	/* The colour is stated here on purpose. Inheriting body text (dark ink) on
	   the pine bar produces a bar that is legible only where a word happens to
	   carry an accent colour — a half-invisible state that survived weeks on
	   nativenic before anyone noticed, because everyone "saw" the highlighted
	   word and assumed the rest was there.
	   (Verified on staging before this change: computed colour was already
	   rgb(255,251,242) on rgb(30,91,60) = 7.77:1. Not broken here — restated
	   in tokens so it cannot silently become inherited later.) */
	color: var(--on-trust);
}

/* flex: 1 0 auto is doing exact work here, and the obvious alternative is a
   trap. The track is a flex ITEM of .kc-marquee. Writing `min-width: 100%` to
   make it fill the bar in the no-JS case overrides min-width:auto, which is the
   only thing stopping a flex item from shrinking below its content — the track
   then collapses to exactly viewport width while its passes overflow it, and
   translateX(-50%) silently becomes "half the window" instead of "one group".
   Measured on staging when I did it: track 1280px wide holding 3282px of
   passes, loop point jumping. grow:1 fills the bar when the content is
   narrower; shrink:0 keeps the box at max-content when it is wider, which is
   what -50% has to mean. */
.kc-marquee-track {
	display: flex;
	flex: 1 0 auto;
	width: max-content;
	justify-content: space-evenly;
	animation: none;
}

.kc-marquee-pass { display: flex; flex: 0 0 auto; }

.kc-marquee-item {
	display: inline-flex;
	align-items: center;
	gap: 9px;
	padding: 0 22px;
	/* Optical centring of the whole group inside the bar, which is a different
	   problem from the icon-vs-text fix below. align-items:center centres the
	   line BOX; this copy is all caps, so its ink has no descender and sits
	   high inside that box — measured by scanning the bar's ink band at DSF 8:
	   11.00px of pine above, 15.38px below a 38px bar, i.e. 2.19px high.
	   translateY, not padding: the JS clones passes until they cover the
	   viewport and derives the animation duration from the measured group
	   width, so anything that changes layout width changes the loop maths. */
	transform: translateY(2.19px);
	/* Brand law: the marquee is not a brand surface, so Poppins, not Gazella.
	   It was rendering in Roboto purely by inheriting <body>. tabular-nums
	   keeps "$200" from wobbling between webfont and fallback. */
	font-family: var(--kc-font-body);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: .08em;
	line-height: 1;
	font-variant-numeric: tabular-nums;
}

.kc-marquee-text { display: block; }

/* --------------------------------------------------------------------------
   The icon-sits-low fix. Read all of this before changing any number.
   --------------------------------------------------------------------------
   Owner's one note on direction C was that the icon sits below the words.
   There are TWO causes, and the second one is invisible to every DOM-based
   measurement, which is why this is a pixel-scan job and not a rect job.

   (1) align-items:center centres BOXES, and a run of capitals is not centred
       in its own box. Poppins Bold, read off the shipped woff2: upem 2048,
       hhea ascent 2269 (1.1079em), descent -1281 (0.6255em), cap height 1444
       (0.7051em). With line-height:1 the 1.7334em content box sits in a 1em
       line box, so half-leading is -0.3667em, the baseline lands 0.7412em
       below the line-box top and the cap line 0.0361em below it. The optical
       centre of the capitals is (0.0361 + 0.7412) / 2 = 0.3887em — 0.1113em
       ABOVE the geometric centre align-items:center is aiming at. That is
       1.34px at 12px, on an 8.45px cap height. Hence the translateY below,
       kept in em so it tracks the font size, and applied to the icon because
       moving the text would drag it off the bar's baseline.

   (2) Neither the icon nor the text paints where the DOM says it does, and
       they are quantised differently. Two separate browser behaviours, both
       invisible to getBoundingClientRect():

       (2a) Chromium rounds an inline SVG's viewport origin to a WHOLE CSS
            PIXEL (halves up) before rasterising, while getBoundingClientRect()
            and getScreenCTM() keep reporting the unrounded position — the ink
            can be up to 0.5px from every number the DOM will give you.
            Measured across box tops 9.000 → 12.500, the model
            round(top) - top predicts the offset with 0.000 residual at every
            sample. A CSS transform is applied AFTER the rounding and is
            perfectly continuous, so a transform cannot cancel it.

            So leave nothing to round: the bar is 38px and the icon is 14px,
            giving a box top of (38 - 14) / 2 = 12 exactly. An ODD icon size
            would land it on 12.5 and paint the whole icon half a pixel low
            again. The 14 is load-bearing, not a taste decision.

       (2b) The text baseline is snapped DOWN to a whole CSS pixel. Poppins
            Bold at 12px/1 puts the unsnapped baseline 8.88px below the line
            box top; measured in a clean lab page with an integer block top it
            paints at exactly 8.00px, and the same 0.88px appears on staging.
            So the letters sit 0.88px higher than their metrics predict, and
            the icon — which is not snapped — has to come up to meet them.

   Hence the lift below is written as two terms rather than one magic number:
   the em term is (1), the px term is the measured (2b). Pixel-scanned residual
   after both, all four icons, 375 and 1280, is in docs/redesign/marquee-c.md.
   ⚠️ (2b) is deterministic only while the line box top is an integer, which is
   why every height in this file is even. If the bar height, the icon size or
   the font size changes, re-run the scan — do not scale this number by hand.

   Per-icon ink asymmetry is corrected inside each viewBox instead (see
   kc_rd_marquee_icon() in inc-topbar.php), because that lives in SVG user
   space and is not subject to (2a). */
.kc-marquee-item svg,
.kc-marquee-pause svg {
	width: 14px;
	height: 14px;
	flex: 0 0 14px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.7;
	stroke-linecap: round;
	stroke-linejoin: round;
	overflow: visible;       /* a viewBox nudge must not clip the artwork */
	/* (1) cap-optical-centre, scales with the font | (2b) measured baseline snap */
	transform: translateY(calc(-0.1113em - 0.88px));
}

/* The track is exactly two identical groups, so -50% is exactly one group and
   the loop point is invisible. JS guarantees group width >= viewport width,
   which is the whole fix: content never runs out on the right. */
@keyframes kc-marquee-slide {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(-50%, 0, 0); }
}

.kc-marquee.is-fitted .kc-marquee-track {
	justify-content: flex-start;
	will-change: transform;
	animation: kc-marquee-slide var(--kc-mq-dur) linear infinite;
}

/* --------------------------------------------------------------------------
   Pause control — WCAG 2.2.2
   --------------------------------------------------------------------------
   The bar auto-scrolls for longer than 5s, so it needs a mechanism to pause
   it. The previous build relied on :hover and :focus-within. :focus-within was
   dead code — nothing inside the bar was focusable, so twelve tab presses
   never put focus in it — and :hover does not exist on a phone, where 86.5% of
   this store's traffic is. A real button is the only one of the three that
   works for everyone, so :focus-within is gone rather than resuscitated: with
   a button inside the bar it would actively fight the button (focus the
   control, press Play, and focus-within holds it paused).

   Rendered by JS, not PHP, on purpose: the animation itself is gated on
   .is-fitted, so with JS off or reduced-motion on there is nothing to pause
   and a server-rendered button would be a dead control.

   Target size is 64 x 38 — see the width note below. Noted for the owner in
   the report: 38px of height is all a 38px bar can give, and .kc-marquee clips
   its own overflow so the hit area cannot spill past it. */
.kc-marquee-pause {
	position: absolute;
	top: 0;
	right: 0;
	z-index: 2;
	/* 64 x 38, not 44 x 38, and the extra 20px is not padding for its own sake:
	   the offers scroll UNDER this control, so the pine has to be fully opaque
	   before the glyph starts or the two read as one smudge. 64px gives the
	   gradient 35px to finish in and leaves the last 29px solid. It also buys a
	   bigger target — 64 x 38 against WCAG 2.5.8's 24 x 24; the project's own
	   44 x 44 is capped by the 38px bar, which clips its own overflow. */
	width: 64px;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: flex-end;
	margin: 0;
	padding: 0 15px 0 0;
	border: 0;
	border-radius: 0;
	-webkit-appearance: none;
	appearance: none;
	/* Fade the pine in rather than draw a panel edge — a panel would read as a
	   control you could buy from, which §2 of the brief forbids. */
	background: linear-gradient(to right, rgba(0, 0, 0, 0) 0, var(--kc-pine) 55%);
	color: inherit;
	/* The lift on the glyph is calc(-0.1113em - ...), and em here would be the
	   bar's inherited 15px rather than the offers' 12px — a third of a pixel of
	   difference, but the whole point of this file is that thirds of a pixel are
	   what the owner noticed. */
	font-size: 12px;
	cursor: pointer;
}
.kc-marquee-pause:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: -5px;
}

/* Solid, unlike the offer icons: a hairline pause bar at 14px is unreadable,
   and this is a control rather than a piece of the typographic line.
   The dimming lives HERE and not on the button: opacity on the button would
   take its own pine backdrop down with it and let the scrolling text show
   straight through the control. */
.kc-marquee-pause svg {
	fill: currentColor;
	stroke: none;
	opacity: .78;
	transition: opacity .16s ease;
}
.kc-marquee-pause:hover svg,
.kc-marquee-pause:focus-visible svg { opacity: 1; }

.kc-marquee.is-fitted.is-paused .kc-marquee-track { animation-play-state: paused; }

/* Hover-to-read stays, with one guard. Pressing Play while the pointer is
   still over the bar would otherwise leave hover holding it paused and make
   the button look broken — worse on touch, where :hover sticks after a tap.
   JS sets .kc-hover-off on an explicit resume and clears it on pointerleave. */
.kc-marquee.is-fitted:hover:not(.kc-hover-off) .kc-marquee-track {
	animation-play-state: paused;
}

/* Re-stated at higher specificity than the .is-fitted rule above for the same
   reason: §21's version sat at (0,1,0) and would be beaten by .is-fitted,
   silently re-enabling motion for the people who asked for none. Media queries
   add no specificity, which is the easy half of this to forget. */
@media (prefers-reduced-motion: reduce) {
	.kc-marquee.is-fitted .kc-marquee-track {
		animation: none;
		justify-content: space-evenly;
		will-change: auto;
	}
}

/* ===== 45-header-fix.css ===== */
/* ==========================================================================
   45-header-fix.css — shell surface (header chrome / footer / breadcrumb)
   Owner: shell-v3 agent. One surface per file; nobody else edits this.
   Everything is scoped body.kc-rd so it leaves with the kill switch.
   ==========================================================================

   §45.1 WHY THE BAG WAS A WHITE BOX (2026-08-23 diagnosis, /cart/ page)
   --------------------------------------------------------------------------
   Two independent defects stacked:

   A. Savoy's cart-page layout rule
          .woocommerce-cart .woocommerce > form
              { float:left; padding-right:73px; width:calc(100% - 384px); }
      hijacks the HEADER search form: FiboSearch's wrapper div carries the
      class "woocommerce", so on the cart page (body.woocommerce-cart) the
      header's form.dgwt-wcas-search-form matches ".woocommerce > form".
      230px wrapper − 384px = negative → content width 0; the leftover
      62px of borders+padding is the empty white rectangle in
      scripts/surface_shots/funnel/f-cart-desk.png. Cart page only — the
      home/catalog header search renders fine.

   B. The right menu never fits. Savoy sizes nav.nm-right-menu without
      counting the injected 266px search <li> (230px bar + 6px margin +
      25/30px legacy margin-right from style.css "header
      #nm-header-search-form" — the old icon-expand search's spacing, still
      matching because the persistent bar reuses that id). Measured: the UL
      is 468px at every width; the nav gets the leftover flex space
      (313px @1280 / 353px @1440 / 452px @1920), so the UL spills past the
      viewport: bag fully offscreen @1280, bag half-cut @1440 (the ledger's
      old account — never actually fixed), hamburger clipped @1920.
   -------------------------------------------------------------------------- */

/* --- A. Un-hijack the header search form (all widths, incl. mobile cart).
       (0,3,0) beats Savoy's .woocommerce-cart .woocommerce > form (0,2,1)
       without !important; the stock plugin geometry is exactly this. */
body.kc-rd .kc-fibo-header .dgwt-wcas-search-form {
	float: none;
	width: 100%;
	padding: 0;
	margin: 0;
}

/* --- B. Desktop header: the right menu owns its width; the search bar is
       the spring that gives way, never the bag or the hamburger. */
@media (min-width: 992px) {

	/* Kill the old icon-search spacing that rides in on the reused id.
	   (1,1,1) beats style.css "header #nm-header-search-form" (1,0,1). */
	body.kc-rd #nm-header-search-form.kc-fibo-header { margin-right: 0; }

	/* The nav may shrink only down to all-icons + a minimum search field;
	   below its preferred size the search li absorbs the squeeze. Keeping
	   flex-grow 0 leaves the main menu as the only grower, so menu
	   centering at wide viewports is unchanged. */
	body.kc-rd .nm-header-col nav.nm-right-menu {
		flex: 0 1 auto;
		min-width: min-content;
	}
	body.kc-rd #nm-right-menu-ul {
		flex: 0 1 auto;
		min-width: 0;
	}

	/* Search li: preferred 236px (230 bar + 6 margin), floor 150px — below
	   that the placeholder is unreadable and the field looks broken.
	   width (not flex-basis) so the li's intrinsic contribution to the
	   nav's max-content is the full 236px: with basis alone, intrinsic
	   sizing fell back to the placeholder text width and the bar rendered
	   195px even on a 1920 viewport with 650px of slack. */
	body.kc-rd #nm-right-menu-ul > li:has(.kc-fibo-header) {
		flex: 0 1 auto;
		width: 236px;
		min-width: 150px;
	}

	/* The main menu keeps its links un-clipped no matter what: its floor is
	   its own ink. The squeeze lands on the search field (down to its 150px
	   floor), never on menu items sliding under the icons. */
	body.kc-rd .nm-header-col .nm-main-menu { min-width: max-content; }
	/* Sign-in, bag, hamburger are rigid — they never give. */
	body.kc-rd #nm-right-menu-ul > li.nm-menu-account:not(:has(.kc-fibo-header)),
	body.kc-rd #nm-right-menu-ul > li.nm-menu-cart,
	body.kc-rd #nm-right-menu-ul > li.nm-menu-offscreen {
		flex-shrink: 0;
	}

	/* The bar itself fills whatever its li gets. Overrides kc-redesign §14's
	   fixed width:230px (and its 210px @≤1200 tier) — same specificity,
	   later file wins. The plugin's min-width:230px on the wrapper would
	   otherwise put a floor under the squeeze. */
	body.kc-rd .kc-fibo-header {
		flex: 1 1 auto;
		width: auto;
		min-width: 0;
	}
	body.kc-rd .kc-fibo-header .dgwt-wcas-search-wrapp { min-width: 0; }
}

/* Mid-desktop (992–1180px): the 6-item main menu at 16px link padding needs
   641px of ink; give some of it back so the icons keep their floor. */
@media (min-width: 992px) and (max-width: 1180px) {
	body.kc-rd #nm-main-menu-ul > li > a { padding-left: 10px; padding-right: 10px; }
}

/* --- Phantom 30px horizontal scroll, sitewide (found while verifying, and
       measured identical with this file blocked = pre-existing, not ours).
       Batch 7 hid the old fixed announcement text (#header-top-bar-green-text)
       but its Templatera ROW still renders: 1px tall, margin 0 -15px, in an
       unpadded wrap → scrollWidth 1310 on a 1280 viewport, on every page.
       Hide the row itself; the kill switch (no body.kc-rd) restores it. */
body.kc-rd .nm-page-wrap .nm-row:has(#header-top-bar-green-text) { display: none; }


/* ==========================================================================
   §45.2 FOOTER SHELL (wave 3) — layered ON TOP of 90-footer.css
   --------------------------------------------------------------------------
   90-footer.css (previous wave, not mine to edit) already grids the rows and
   pins colour/contrast/focus. What it deliberately kept — a 48px touch floor
   on all 39 city links at every viewport — is what still reads as a wall on
   desktop (dispatch: "视觉重且廉价"). This section:
     a) tightens the city directory on fine-pointer desktops only,
     b) styles the <details> accordion + compliance row that inc-shell.php
        builds around the untouched Templatera output,
   CASCADE NOTE: this file is 45-*, i.e. EARLIER than 90-footer.css, so ties
   lose. Rules that must beat 90's use a doubled id (#nm-footer#nm-footer,
   one specificity class up). They are marked [beats 90.x] and listed in the
   report for consolidation into the footer surface whenever it gets an owner.
   ========================================================================== */

/* --- a) Desktop directory: pointer devices don't need a 48px thumb floor.
       Pitch 56px → 30px, the wall drops from ~620px to ~360px without losing
       a link. Touch-screen desktops (pointer:coarse) keep 90's floor. */
@media (min-width: 992px) and (hover: hover) and (pointer: fine) {
	/* [beats 90.2 a.row-title (1,3,3) via double id] */
	body.kc-rd #nm-footer#nm-footer .fast-weed-delivery-cities-list p a.row-title {
		min-height: 28px;
		padding: 2px 0;
		font-size: 14px;
	}
	/* [beats 90.2 .wpb_wrapper > p (1,2,2)] */
	body.kc-rd #nm-footer#nm-footer .fast-weed-delivery-cities-list .wpb_wrapper > p {
		margin-bottom: 2px;
	}
	body.kc-rd #nm-footer#nm-footer .fast-weed-delivery-cities-list .wpb_wrapper > p > img {
		width: 10px;
		opacity: .35;
	}
}

/* --- b) The accordion shell. Desktop: invisible (summary hidden, details
       open = today's layout). Phone/tablet: the summary is the row, the
       Templatera title row hides (the summary carries the same label), and
       the native details toggle does the collapsing — no JS required to
       reach any link, JS only sets the default state per viewport. */
body.kc-rd details.kc-shell-cities > summary { list-style: none; }
body.kc-rd details.kc-shell-cities > summary::-webkit-details-marker { display: none; }

/* Inside <details>, the title row's un-gridded .col-sm-12 resolves to
   100% + its 30px gutter (measured 1310 in a 1280 row — A/B attributed to
   the wrapper; the other two rows are immune only because 90.2/90.3 already
   grid them with width:auto!important). Same medicine for the title col,
   plus clip as a belt so no stray full-bleed child of the band can ever
   re-open the phantom horizontal scroll. !important matches 90's own use
   against the grid float stack. */
body.kc-rd details.kc-shell-cities { overflow-x: clip; }
body.kc-rd details.kc-shell-cities .nm-row.fast-weed-delivery-cities-list-title > .nm_column.wpb_column {
	width: auto !important;
	float: none;
}

@media (min-width: 992px) {
	body.kc-rd details.kc-shell-cities > summary { display: none; }
}

@media (max-width: 991px) {
	body.kc-rd .kc-shell-cities-summary {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--kc-space-3);
		min-height: var(--btn-min-height);
		padding: var(--kc-space-4) 15px;
		cursor: pointer;
		color: var(--surface);
		font-family: var(--label-font);
		font-size: 12px;
		font-weight: 600;
		letter-spacing: .12em;
		text-transform: uppercase;
		-webkit-tap-highlight-color: rgba(255, 251, 242, .22);
	}
	body.kc-rd .kc-shell-cities-summary:focus-visible {
		outline: 3px solid var(--action);
		outline-offset: -3px;
	}
	body.kc-rd .kc-shell-cities-chevron {
		flex: 0 0 auto;
		width: 9px;
		height: 9px;
		border-right: 2px solid var(--surface);
		border-bottom: 2px solid var(--surface);
		transform: rotate(45deg);           /* points down = "will expand" */
		transition: transform .15s ease;
		opacity: .62;
	}
	body.kc-rd details.kc-shell-cities[open] .kc-shell-cities-chevron {
		transform: rotate(225deg);          /* points up = "will collapse" */
	}
	/* The Templatera heading row duplicates the summary's label on phones.
	   [beats 90.2 title-row padding (1,6,0)!important via double id +
	   !important — display, not padding, so no tug-of-war] */
	body.kc-rd #nm-footer#nm-footer .nm-row.fast-weed-delivery-cities-list-title {
		display: none !important;
	}
	/* Hairline under the summary so the collapsed row reads as a band of the
	   footer, not a stray label. */
	body.kc-rd details.kc-shell-cities {
		border-bottom: 1px solid rgba(255, 251, 242, .14);
	}
	/* With the title row hidden, the list row's vc_custom top padding is a
	   dead 40px band between the summary and the first city. [beats the
	   vc_custom_* inline (0,1,0)!important and 90.2's pb via double id] */
	body.kc-rd #nm-footer#nm-footer .nm-row.fast-weed-delivery-cities-list {
		padding-top: 0 !important;
	}
}

/* --- c) Compliance row (markup from inc-shell.php; wording pending owner
       sign-off — see report). Readable by design: 90.7's hazard note warns
       against the child theme's 40%-opacity fine print; this row never
       matches those selectors and pins its own contrast. */
body.kc-rd .kc-shell-legal {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--kc-space-2) var(--kc-space-3);
	max-width: 1280px;
	margin: 0 auto;
	padding: var(--kc-space-4) 15px var(--kc-space-5);
	border-top: 1px solid rgba(255, 251, 242, .14);
	color: var(--surface);
	font-family: var(--label-font);
	font-size: 12px;
	letter-spacing: .06em;
	line-height: 1.6;
	text-align: center;
}
body.kc-rd .kc-shell-legal-item { opacity: .72; }
body.kc-rd .kc-shell-legal-sep  { opacity: .35; }
body.kc-rd .kc-shell-19 {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 2px 8px;
	border: 1px solid rgba(255, 251, 242, .45);
	border-radius: var(--kc-radius-md);
	font-weight: 600;
	letter-spacing: .08em;
}
@media (max-width: 767px) {
	body.kc-rd .kc-shell-legal-sep { display: none; }
	body.kc-rd .kc-shell-legal { flex-direction: column; gap: var(--kc-space-1); }
}


/* ==========================================================================
   §45.3 BREADCRUMB — one line on phones (site-shell component)
   --------------------------------------------------------------------------
   The archive breadcrumb (.breadcrumbs — the schema itemListElement block; a
   plain-CSS fix, the markup and schema are untouched) is centred flex via
   style.css `body.archive .breadcrumbs`. At 375px the crumb text wraps
   INSIDE its flex-item spans ("HOME PAGE" stacked on two lines, "1 OZ
   DEALS" ragged-right — scripts/surface_shots/wave2/w2-cards-phone.png).
   Fix: one left-aligned line that scrolls horizontally, scrollbar hidden,
   with a right-edge fade cue while there is more to scroll (45-shell.js
   toggles the fade classes; with no JS it is simply a clean scrollable
   line). Same specificity (0,2,1) as the style.css rule — the parts file
   loads later and takes the tie.
   ========================================================================== */
@media (max-width: 991px) {
	body.kc-rd .breadcrumbs {
		display: block;
		white-space: nowrap;
		overflow-x: auto;
		text-align: left;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
	}
	body.kc-rd .breadcrumbs::-webkit-scrollbar { display: none; }
	body.kc-rd .breadcrumbs span { white-space: nowrap; }

	/* Fade only while scrollable content remains to the right. */
	body.kc-rd .breadcrumbs.kc-shell-fade:not(.kc-shell-fade-end) {
		-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
		mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
	}
}


/* ==========================================================================
   §45.4  MOBILE HEADER — one row: burger · wordmark · icons   (nav-0827)
   --------------------------------------------------------------------------
   Owner, 2026-08-27: "手机端的菜单我希望你去学习 Native Nick 的做法,把汉堡菜单
   放在最左边,右边放三个 icon,留出一个法语切换的按钮,法语我们之后再做。"

   BEFORE (staging, 375x812, measured):
     row 1  SIGN IN (text, 76px) · wordmark · bag · burger (24px wide)
     row 2  full-width search field
     #nm-header 112px tall; with the marquee, 150px of chrome = 18.5% of the
     screen before a customer sees anything. The burger's own tap target was
     24 x 48 — the row it sits in is 44+, the control is not.

   AFTER
     one row  [≡ 44x44] · wordmark · [search 44][account 44][bag 52]
     #nm-header 64px, chrome 102px.

   WHY THE WORDMARK IS NOT ON THE VIEWPORT'S CENTRE LINE, and why that is
   arithmetic rather than taste: at 375 the row has 345px of usable width. A
   burger (44) on the left and three targets on the right (44+44+52 = 140)
   leave 161px between them, which is enough for the 127px wordmark — but a
   TRUE centre would need the two clusters to be the same width, i.e. each side
   at most (375-127)/2 = 124px. The right cluster is 140. There is no
   arrangement of "one control left, three right, 44px each, 127px wordmark"
   that centres on 375; something has to give and the candidates are the
   wordmark's size or its centring. Shrinking a brand asset is the more
   expensive of the two, so the wordmark keeps its 34px height and is centred
   in the space BETWEEN the clusters (--kc-hdr-shift below is exactly half the
   asymmetry, 48px). Reported to the owner in docs/redesign/nav-0827.md with
   the alternative (search moves to the left of the burger → 88/88, perfectly
   symmetric) as a one-line switch.

   NOTHING HERE ADDS HEIGHT. That is the acceptance condition and it is met by
   112 → 64.
   ========================================================================== */

@media (max-width: 991px) {

	/* --- 45.4.1 The row itself. ---------------------------------------- */
	/* Half the asymmetry between the left cluster (44) and the right one
	   (140): the wordmark is absolutely positioned on the viewport centre by
	   the legacy assets/css/main.css:6102, and this shifts it left by exactly
	   the amount that makes its two gaps equal. If the language slot is turned
	   on the right cluster grows by 44 and this grows by 22 — see 45.4.6. */
	body.kc-rd #nm-header { --kc-hdr-shift: 48px; }

	body.kc-rd #nm-header { padding-top: 8px !important; padding-bottom: 8px !important; }
	body.kc-rd .nm-header-col {
		display: flex;
		align-items: center;
		min-height: 48px;
		padding-left: 15px;
		padding-right: 15px;
	}

	/* The right nav becomes the whole row so the burger can sit at its left
	   edge; the icons stay pinned right by justify-content. Savoy floats this
	   nav and gives it an auto width — both have to go or the burger lands in
	   the middle of the row. */
	body.kc-rd .nm-header-col nav.nm-right-menu {
		flex: 1 1 auto;
		width: 100%;
		min-width: 0;
		padding: 0;
		float: none;
	}
	/* nowrap is the whole point: kc-redesign.css §14's mobile block wraps this
	   list so the search field can take a second row. It does not any more. */
	body.kc-rd #nm-right-menu-ul {
		display: flex;
		width: 100%;
		flex-wrap: nowrap !important;   /* [beats kc-redesign.css §14's wrap !important] */
		align-items: center;
		justify-content: flex-end;
		row-gap: 0;
		margin: 0;
	}

	/* --- 45.4.2 Order. -------------------------------------------------- */
	/* Source order is [lang][account][search][cart][burger]; the row reads
	   burger · (wordmark) · lang · search · account · cart.

	   Why search sits closest to the wordmark and the bag on the outer edge:
	   the bag is the one control customers hunt for by muscle memory and every
	   store on the internet puts it in that corner (88% of this store's revenue
	   is repeat customers — muscle memory is not a small thing here). Account
	   is the least used of the three, so it takes the middle: 61% of this
	   store's buyers have no account at all. Search takes the inner slot, next
	   to the brand, where it is the first thing the thumb passes moving right
	   from the wordmark. */
	body.kc-rd #nm-right-menu-ul > li.nm-menu-offscreen { order: -1; margin-right: auto; margin-left: 0; }
	body.kc-rd #nm-right-menu-ul > li.kc-hdr-lang { order: 1; }
	body.kc-rd #nm-right-menu-ul > li:has(.kc-fibo-header) {
		order: 2;
		flex: 0 0 44px;                 /* was `flex:0 0 100%` = the second row */
		width: 44px;
		min-width: 0;
		padding: 0 !important;          /* [beats kc-redesign.css §19's mobile !important] */
		margin: 0 !important;
	}
	body.kc-rd #nm-right-menu-ul > li.nm-menu-account:not(:has(.kc-fibo-header)) { order: 3; flex: 0 0 44px; width: 44px; }
	/* padding-right:22px comes from kc-redesign.css:3513 — a 2026-08 band-aid for
	   the 1440 half-cut bag that shell-v3 §7 already listed as obsolete. Left in
	   place it makes this cell 54px wide with a 52px anchor overflowing it to the
	   LEFT, which is what put the bag on top of the account glyph in the first
	   deploy of this round. */
	/* flex-basis, not `auto`: with auto the li resolved to 32px while the anchor
	   inside it stayed 52px and overflowed to the LEFT, on top of the account
	   glyph. Measured, twice — the first fix (killing kc-redesign.css:3513's
	   22px padding) moved the number but not the cause. Every cell in this
	   cluster now states its own width so the row's arithmetic is closed and
	   --kc-hdr-shift below stays true. */
	body.kc-rd #nm-right-menu-ul > li.nm-menu-cart {
		order: 4;
		margin-left: 0;
		padding-right: 0;
		flex: 0 0 52px;
		width: 52px;
	}

	body.kc-rd #nm-right-menu-ul > li { flex: 0 0 auto; }

	/* --- 45.4.3 The burger: a real 44x44 target. ------------------------ */
	/* It was 24x48 — the three bars are 21px wide and the anchor shrink-wrapped
	   them. Padding, not width, so the theme's own X animation (which
	   transforms the bars, not the box) is untouched. */
	body.kc-rd #nm-right-menu-ul > li.nm-menu-offscreen > a#nm-mobile-menu-button {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 44px;
		height: 44px;
		padding: 0;
	}

	/* --- 45.4.4 Sign-in becomes the account glyph. ---------------------- */
	/* The theme already owns this icon — it is \ea08 in the child theme's
	   custom-icons font, and it is what desktop shows. The legacy
	   assets/css/main.css:6234 turns it back into the words "SIGN IN" below
	   992 (font-size:14px !important) and hides the glyph (:before
	   display:none). Both are reversed here; the ONLY reason these two carry
	   !important is that the declarations they beat are !important in a file
	   this surface does not own — listed for deletion in the report, at which
	   point these two can drop the flag.
	   The anchor still contains the text "Sign In", so its accessible name is
	   unchanged and nothing was removed from the DOM. */
	body.kc-rd #nm-right-menu-ul > li.nm-menu-account > a#nm-menu-account-btn {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 44px;
		height: 44px;
		padding: 0 !important;          /* [beats legacy main.css:6234 !important] */
		font-size: 0 !important;        /* [beats legacy main.css:6234 !important] */
		margin: 0;
	}
	body.kc-rd #nm-right-menu-ul > li.nm-menu-account > a#nm-menu-account-btn::before {
		display: inline-block;
		font-size: 24px;
		color: var(--text);
	}

	/* --- 45.4.4b Which "0" is the cart's. ------------------------------- */
	/* 🔴 header-layout.php:61 prints a SECOND cart-count span, inside the
	   hamburger's <li> (`nm_get_cart_contents_count()` again, for a mobile
	   layout Savoy no longer uses). It is hidden by
	       .header-mobile-alt .nm-right-menu #nm-right-menu-ul li:last-of-type
	       .count { display: none }
	   in the legacy assets/css/main.css:6089 — a POSITIONAL selector. 40-nav.js
	   §40.5 moves the hamburger's <li> to the front of the list for focus
	   order, so `:last-of-type` stopped meaning "the hamburger" and started
	   meaning "the bag": the stray 0 appeared at x=-24 and THE BAG ICON
	   DISAPPEARED (the artwork is that span's ::after). Caught in the phone
	   screenshot, not in any measurement — every rect was still correct.
	   Both states are pinned here by identity instead of position, so neither
	   depends on where anything sits in the list. Doubled id (2,3,1) to beat
	   main.css's (1,4,1). */
	body.kc-rd #nm-header #nm-right-menu-ul > li.nm-menu-offscreen > .nm-menu-cart-count { display: none; }
	body.kc-rd #nm-header #nm-menu-cart-btn > .nm-menu-cart-count { display: inline-block; }

	/* --- 45.4.5 The bag: same badge, tighter box. ----------------------- */
	/* The count span and its ::after (the bag artwork) are NOT touched — the
	   badge keeps its exact offsets inside .count, which is what "购物车角标位置
	   不变形" asks for. All that changes is that the anchor around it stops
	   being 16px-padded and becomes a fixed 52px flex cell, so the row's right
	   cluster has a width this file can do arithmetic with. */
	body.kc-rd #nm-right-menu-ul > li.nm-menu-cart > a#nm-menu-cart-btn {
		display: flex;
		align-items: center;
		justify-content: flex-end;   /* see note */
		width: 52px;
		height: 44px;
		padding: 0;
		/* a legacy -20px margin-left rode in on this anchor and pulled the whole
		   52px box left, out of its cell and under the account glyph. Measured
		   as computed style (margin "0px 0px 0px -20px"), not guessed — two
		   earlier fixes moved the symptom because they addressed the li. */
		margin: 0;
	}
	/* flex-end, not center: the bag artwork is .count's ::after at right:15px,
	   so .count's 45px box has its ink pushed left of centre (bag -2..30,
	   number 28..45). Centring the BOX therefore pushes the ink towards the
	   account glyph next door. Right-aligning the box puts the bag where the
	   eye expects it and leaves 11px of clear space between the two glyphs.
	   Nothing inside .count is touched — the badge keeps its own offsets. */

	/* --- 45.4.6 The wordmark. ------------------------------------------ */
	/* main.css:6102 pins this absolutely at left:50% translateX(-50%) and
	   savoy-child/style.css:4956 drags it to 45% below 500px; the div is also
	   capped at 100px there while the <img> inside it is 127px, so the "centre"
	   was the centre of a box narrower than its own content. Declared in full
	   here (1,3,0 beats the id rule at 1,1,0) rather than patched. */
	body.kc-rd #nm-header .nm-header-col .nm-header-logo {
		position: absolute;
		top: 50%;
		left: 50%;
		width: auto;
		max-width: calc(100% - 220px);
		margin: 0;
		transform: translate(calc(-50% - var(--kc-hdr-shift)), -50%);
		line-height: 0;
	}
	body.kc-rd #nm-header .nm-header-col .nm-header-logo img.nm-logo {
		display: block;
		height: 34px;
		width: auto;
	}
	/* The wordmark link was a 127x34 target — the one control in the mobile
	   header still under the floor after this round, and it is the "go home"
	   link. Padding only: the <img> keeps its size, and the block is centred on
	   the row by the translate above, so nothing moves. */
	body.kc-rd #nm-header .nm-header-col .nm-header-logo > a {
		display: flex;
		align-items: center;
		min-height: 44px;
	}

	/* --- 45.4.7 The language slot. -------------------------------------- */
	/* OFF (today): zero-width, no ink, no tab stop. It holds its place in the
	   flex order so that switching it on is an option flip, not a re-layout.
	   ON: 44x44 like its neighbours, and the wordmark gives back the 44px it
	   costs — 30px tall instead of 34, and the shift grows to 70. Both states
	   have been rendered and screenshotted. */
	body.kc-rd #nm-right-menu-ul > li.kc-hdr-lang--reserved {
		flex: 0 0 0;
		width: 0;
		padding: 0;
		margin: 0;
		overflow: hidden;
	}
	body.kc-rd #nm-right-menu-ul > li.kc-hdr-lang { flex: 0 0 auto; }
	body.kc-rd .kc-hdr-lang__btn {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 44px;
		height: 44px;
		padding: 0;
		font-family: var(--label-font);
		font-size: 12px;
		font-weight: 700;
		letter-spacing: .08em;
		line-height: 1;
		color: var(--text);
		text-transform: uppercase;
	}
	body.kc-rd.kc-lang-on #nm-header { --kc-hdr-shift: 70px; }
	body.kc-rd.kc-lang-on #nm-header .nm-header-col .nm-header-logo img.nm-logo { height: 26px; }
}

/* ===== 50-account.css ===== */
/* ==========================================================================
   50-account.css — SURFACE: My Account (Group A)
   Login + register, lost-password dialog, account navigation, dashboard,
   orders list, single order view, addresses, account details, and the
   "Buy it again" block rendered by inc-account.php.

   --------------------------------------------------------------------------
   DOM VERIFIED (fetched and read, not guessed)
   --------------------------------------------------------------------------
   Logged-out /my-account/ pulled from staging and read as raw HTML:
     .nm-row.nm-row-boxed.vc_row.wpb_row.my-account          <- WPBakery row
       > h1.vc_custom_heading[style="…font-family:Abril Fatface…"]
       > .wpb_text_column > div.woocommerce
           > #customer_login.nm-myaccount-login > .nm-myaccount-login-inner
             > #nm-login-wrap.inline.slide-up.fade-in
                 > div.login-form-old-h2          (a DIV, not an h2)
                 > form.login
                     > p.form-row.form-row-wide ×2
                     > p.form-row.form-group      (remember-me + lost password)
                     > p.form-actions > button.woocommerce-Button.button[name=login]
                                      > .nm-login-form-divider
                                      > a#nm-show-register-button.button.border
             > #nm-register-wrap > form.register  (email only + p.form-actions)
       > #lost-pswd.modal.fade > .modal-dialog.modal-lg > .modal-content
           > .nm-myaccount-lost-reset-password > form.lost_reset_password
             (button.close, div.lost-pswd-old-h2, p.form-row.form-row-first,
              p.form-actions > button.woocommerce-Button.button,
              button.close-button.button)

   🔴 `id="customer_login"` occurs TWICE in that document: once here and once
   inside #nm-login-popup-wrap, the header login pop-up owned by
   40-nav-menu.css. Every login rule below is therefore scoped through
   `.my-account`, an ancestor of the page instance only. A bare
   `#customer_login` selector from this file would tie 40-nav-menu.css on
   specificity and beat it on file order — silently taking over another
   agent's surface. Do not remove the `.my-account` scope.

   Logged-in views read from the templates on the staging box, because those
   pages are behind a customer login:
     savoy/woocommerce/myaccount/navigation.php
       nav.woocommerce-MyAccount-navigation
         > .nm-MyAccount-user > .nm-MyAccount-user-image
                              > .nm-MyAccount-user-info > .nm-username
         > ul > li.is-active > a                 (bare <ul>, no class)
     savoy/woocommerce/myaccount/my-account.php   -> .woocommerce-MyAccount-content
     savoy/woocommerce/myaccount/dashboard.php    -> .nm-MyAccount-dashboard
     savoy/woocommerce/myaccount/orders.php
       table.woocommerce-orders-table.woocommerce-MyAccount-orders.shop_table
       every td carries data-title="<column name>"; column ids are order-number,
       order-date, order-status, order-total, order-actions; each tr carries
       .woocommerce-orders-table__row--status-<status>; actions render as
       a.woocommerce-button.button.view / .pay / .cancel
     savoy/woocommerce/myaccount/view-order.php
       p.nm-order-info > mark.order-number / .order-date / .order-status
     woocommerce/templates/order/order-details.php
       table.woocommerce-table--order-details — NO data-title anywhere, which
       is why §11's card conversion is keyed to the orders table alone
     savoy/woocommerce/myaccount/my-address.php
       .woocommerce-Address.col-sm-6 > header.woocommerce-Address-title > h3,
       address, a.edit
     savoy-child/woocommerce/myaccount/form-edit-account.php
       form.woocommerce-EditAccountForm > p.woocommerce-form-row.form-row,
       fieldset > h3, a.del
   Account menu items are unfiltered by the theme (grepped savoy-child: no
   woocommerce_account_* filters). Two active plugins inject endpoints —
   points-and-rewards ("Points") and wordfence login-security. The Points tab
   renders its own .woocommerce-MyAccount-points.shop_table with NO data-title,
   so stacking `.shop_table` generically would erase its headers. Everything
   in §10–§11 is keyed to table.woocommerce-orders-table only.

   --------------------------------------------------------------------------
   PEER PARTIALS — deliberately NOT duplicated here
   --------------------------------------------------------------------------
   10-forms.css   owns the control vocabulary and names my-account in its own
     scope line: field skin, 16px sizing, the un-floating of
     `#customer_login .form-row-wide > label` and
     `.woocommerce-MyAccount-content .woocommerce-form-row > label`, the
     required/optional markers, select2, and the re-enabling of the
     display:none "Remember me" checkbox at
     `#customer_login p.form-group > label > input[type=checkbox]`.
     None of that is repeated here. It also owns the help text on this surface
     — it names `.woocommerce-MyAccount-content .form-row > span > em` and the
     register form's password line verbatim — so §7 keeps row margins only.
     The ONE field gap it cannot reach is
     #lost-pswd: main.css styles that dialog through the id at (1,1,1)/(1,2,1),
     which out-ranks every (0,3,2) `:is()` scope in 10-forms, and the dialog is
     not one of the three floating-label islands it lists. §4 closes it.
   20-buttons.css owns the button system — §20.2 already names
     `.woocommerce-Button.button` as the account form's primary and §20.3 names
     `.button.border` / `.close-button.button` as secondary, plus base
     geometry, hover/press and the focus ring. §8 here fights only the three
     child-theme rules that carry ids and therefore out-rank it, plus the
     geometry the child forces on this surface and the per-role assignment
     inside the orders table, which a generic partial cannot know.
   40-nav-menu.css owns #nm-login-popup-wrap (the second #customer_login).
   80-notices.css owns notice chrome, INCLUDING on this surface.
     wc-template-hooks.php:326 hooks woocommerce_output_all_notices onto
     woocommerce_account_content at priority 5, so every account endpoint can
     print `.woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-*`
     inside .woocommerce-MyAccount-content — which is exactly what 80-notices'
     `<wrapper> > .nm-shop-notice` selectors are for. The one notice 80-notices
     cannot reach is the empty-orders div, which orders.php prints straight
     into the content column with .woocommerce-Message and no .nm-shop-notice;
     §13 restores its call to action and re-orders it, keyed to
     .woocommerce-Message so it cannot spill onto the shared class.
     Rule of thumb for this file: a bare `.woocommerce-info` descendant
     selector is always wrong here.

   --------------------------------------------------------------------------
   COMPETING SELECTORS BEATEN (measured from the served files)
   --------------------------------------------------------------------------
   Cascade order on the page: savoy/shop.css -> savoy-child/style.css ->
   savoy-child/assets/css/main.css -> kc-redesign.css -> kc-parts.css ->
   <style class="nm-custom-styles"> (theme options, INLINE, LAST in <head>).
   The theme-option block is emitted after every stylesheet, so an equal
   specificity loses to it. Everything below wins on specificity, not order.

   savoy/assets/css/shop.css
     @991 .woocommerce-MyAccount-orders tr .…__cell-order-total {display:none}
          🔴 the ORDER TOTAL is deleted on every phone                  (0,2,1)
     @550 .woocommerce-MyAccount-orders tr .…__cell-order-date {display:none}
                                                                       (0,2,1)
     6093 .woocommerce-MyAccount-content .woocommerce-Message a {display:none}
          🔴 removes the only action from the empty-orders state        (0,2,1)
     6144 .woocommerce-MyAccount-navigation ul li a                     (0,1,3)
     6156 …ul li a:hover, …ul li.is-active a {color:#282828}            (0,2,3)
     6198 .woocommerce-MyAccount-orders tr th, tr td {padding:14px}     (0,1,2)
     6218 .woocommerce-MyAccount-orders tr td:last-child a             (0,1,3)
     6133 / 6165 .woocommerce-MyAccount-navigation / -content
          {overflow:hidden} — clips focus rings at the container edge   (0,1,0)
     6400 .nm-order-info {color:#eee; background:#282828}              (0,1,0)
     5786 #nm-login-wrap / #nm-register-wrap {position:absolute} — this is the
          login/register toggle. Never re-position these two.           (1,0,0)
     third-party/magnific-popup.css:116 .mfp-close
          {display:block; position:absolute; right:5px; top:5px;
           text-align:center; padding:0}
          the "Lost Your Password?" trigger carries class="mfp-close". Its
          position is neutralised by shop.css:5888 `.login .lost_password a
          {position:relative}` (0,2,1), so the link is NOT in the card corner
          — but display:block, text-align:center and right:5px all survive
          because nothing overrides them. Cleaned up in §3.             (0,1,0)

   savoy-child/assets/css/main.css
     4986 .woocommerce-account #customer_login #nm-login-wrap
          {background:#fff; box-shadow:0 4px 9px 2px rgba(0,0,0,.25)
           !important} — !important is unavoidable, see §3              (2,1,0)
     5072 …#customer_login .button:not(#nm-show-register-button)
          :not(#nm-show-login-button) {background:#FFDA48}
          out-ranks 20-buttons.css §20.2 (0,3,1) — fought in §8         (3,2,0)
     4975 …#customer_login .button#nm-show-register-button
          {border:2px solid #E2E2E2}
          out-ranks 20-buttons.css §20.3 (0,3,1) — fought in §8         (2,2,0)
     5087 …#customer_login .nm-login-form-divider span {background:#fff} (1,2,1)
     4915 …#customer_login p:nth-of-type(3) {display:flex;
          justify-content:space-between} — also lands on the REGISTER form's
          p.form-actions, which is its third <p>                        (1,1,1)
     2452 .woocommerce-MyAccount-content .button {background:#FFDA48;
          width:280px; height:56px; margin:40px 0}                      (0,2,0)
          🔴 also what breaks the Buy Again grid: kc-redesign.css §9's
          `.kc-ba-add` is only (0,1,0) and loses, so every card button is
          280×56 with 40px of margin inside a grid cell. Fixed in §9.
     2521 .woocommerce-account.woocommerce-orders .button {width:110px}  (0,3,0)
     2362 .my-account ul:not([class]) li a {color:#AA2934}              (0,2,3)
     2367 .my-account ul:not([class]) li.is-active a                    (0,3,3)
     2416 .woocommerce-MyAccount-content h3 {font-size:20px}            (0,2,1)
     2440 .woocommerce-MyAccount-content a.del {color:#AA2934}          (0,3,0)
     4226 #lost-pswd .woocommerce-Button {background:#FFDA48}
          out-ranks 20-buttons.css §20.2 (0,3,1) on id count            (1,1,0)
     4145 #lost-pswd .close-button {border:2px solid #E2E2E2}
          out-ranks 20-buttons.css §20.3 (0,3,1) on id count            (1,1,0)
     4173 #lost-pswd .close {font-size:40px; color:#BDBDBD}             (1,1,0)
     4207 #lost-pswd .form-row label {position:absolute; opacity:.4}
          out-ranks 10-forms.css §6 (0,3,2) on id count                 (1,1,1)
     4220 #lost-pswd .form-row.focus label {font-size:12px}             (1,2,1)
     4201 #lost-pswd .form-row input {padding:0 15px; height:56px}      (1,1,1)
     (main.css also carries #lost-pswd .user_login rules at 4240–4248. They are
      NOT in this list and nothing here answers them: `user_login` exists in the
      served markup only as the id on the input, never as a class on a wrapper,
      so those theme rules match nothing to begin with.)

   <style class="nm-custom-styles"> (theme options, inline, last in <head>)
     body {color:#777777; font-family:Roboto}                          (0,0,1)
     a {color:#dc9814}                                                 (0,0,1)
     .woocommerce-MyAccount-navigation ul li a, .nm-MyAccount-user-info
       .nm-username, .nm-MyAccount-dashboard, … {font-size:16px}       (0,1,3)

   Element inline style
     h1.vc_custom_heading style="font-family:Abril Fatface" — only !important
     beats an inline declaration; see §2.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. Surface ground and type
   The kit's body face is Poppins, self-hosted with 400/600/700 @font-face in
   savoy-child/style.css:492 — already downloaded on every page. The theme
   option puts Roboto on <body>, so the face is set per container here rather
   than globally: flipping the whole site's body font is the lead's call.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-account .nm-page-wrap,
body.kc-rd.woocommerce-account .nm-page-wrap-inner { background: var(--surface); }

body.kc-rd .my-account #customer_login,
body.kc-rd .woocommerce-MyAccount-navigation,
body.kc-rd .woocommerce-MyAccount-content,
body.kc-rd .nm-MyAccount-user,
body.kc-rd #lost-pswd {
	font-family: var(--kc-font-body);
	color: var(--text);
}

/* shop.css puts overflow:hidden on both account columns, which clips a focus
   ring at the container edge. flow-root keeps the float containment those
   rules actually exist for, without the clipping. */
body.kc-rd .woocommerce-MyAccount-navigation,
body.kc-rd .woocommerce-MyAccount-content {
	overflow: visible;
	display: flow-root;
}


/* --------------------------------------------------------------------------
   2. Page title
   Brand surface, >= 28px, not a product name, not a control label — the kit's
   three admission tests for the display face all pass. Abril Fatface is a
   fifth face that appears nowhere in the kit; Gazella is already loaded.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-account .my-account h1.vc_custom_heading {
	/* !important is the only way past style="font-family:Abril Fatface" on the
	   element itself. Scoped to this one H1 — nothing else is affected. */
	font-family: var(--kc-font-display) !important;
	font-size: clamp(28px, 7vw, 40px);
	line-height: 1.05;
	letter-spacing: -0.01em;
	color: var(--text);
	margin: 0 0 var(--kc-space-5);
}


/* --------------------------------------------------------------------------
   3. Login and register cards
   #nm-login-wrap / #nm-register-wrap are absolutely positioned siblings that
   swap via .inline / .fade-in. Position is never touched or the toggle breaks.
   Fields, labels and the "Remember me" control belong to 10-forms.css.
   -------------------------------------------------------------------------- */

body.kc-rd .my-account #customer_login #nm-login-wrap,
body.kc-rd .my-account #customer_login #nm-register-wrap {
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-radius: var(--card-radius);
	padding: var(--kc-space-6) var(--kc-space-5);
	/* main.css:4986 sets a 9px-blur drop shadow with !important, so !important
	   is the only way to remove it. Kit cards are flat with a hairline. */
	box-shadow: none !important;
}

body.kc-rd .my-account #customer_login div.login-form-old-h2 {
	font-family: var(--kc-font-body);
	font-size: 22px;
	font-weight: 600;
	color: var(--text);
	padding: 0 0 var(--kc-space-5);
}

/* The divider label sits on top of a 1px rule, so its background has to match
   the card or it shows as a mismatched block. */
body.kc-rd .my-account #customer_login .nm-login-form-divider:before { background: var(--border); }
body.kc-rd .my-account #customer_login .nm-login-form-divider span {
	background: var(--card-bg);
	color: var(--text-muted);
	font-size: 13px;
}

/* Row layout only — the checkbox and its label are 10-forms.css §9. The gap
   keeps the two 48px targets in this row from touching. */
body.kc-rd .my-account #customer_login p:nth-of-type(3) {
	gap: var(--kc-space-3);
	margin-bottom: var(--kc-space-4);
}

/* The trigger reuses magnific-popup's .mfp-close class. shop.css:5888
   `.login .lost_password a {position:relative}` (0,2,1) out-ranks
   `.mfp-close {position:absolute}` (0,1,0), so it is NOT pinned to the card
   corner — but the rest of the close-button styling does survive, because
   nothing overrides it: display:block, text-align:center, right:5px (which
   under position:relative shifts the link 5px left) and padding:0. Made a
   plain inline link again, with a real tap target.
   Second selector: main.css:7015 sets font-size:12px on this link below
   768px at (2,2,1) — the id on #nm-login-wrap is the only way past it. */
body.kc-rd .my-account #customer_login .lost_password a,
body.kc-rd .my-account #customer_login #nm-login-wrap .lost_password a {
	position: static;
	display: inline-flex;
	align-items: center;
	width: auto;
	height: auto;
	min-height: var(--btn-min-height);
	line-height: 1.4;
	padding: 0;
	text-align: left;
	font-size: 14px;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 2px;
	touch-action: manipulation;
}
body.kc-rd .my-account #customer_login .lost_password a:hover { color: var(--trust); }

/* main.css:4915 targets the third <p> of anything inside #customer_login, so
   the REGISTER form's p.form-actions inherits the remember-me row's flex
   treatment. Put it back to a block. */
body.kc-rd .my-account #customer_login .register p.form-actions {
	display: block;
	justify-content: flex-start;
}
/* Card rhythm only. The "a password will be sent to your email address" line
   is help text and 10-forms.css:476 already names it
   (`.register > p:not(.form-row):not(.form-actions)`, 13px, muted) — this file
   used to restate it at 14px and win on specificity, which is two partials
   disagreeing about one element. Size and colour stay with the forms owner. */
body.kc-rd .my-account #customer_login .register p:not([class]) {
	margin-bottom: var(--kc-space-4);
}


/* --------------------------------------------------------------------------
   4. Lost-password dialog
   No other partial claims #lost-pswd (grepped across parts/). Because
   main.css reaches this dialog through the id, 10-forms.css's (0,3,2) scopes
   lose to it and the floating label survives here alone — so the un-float and
   the field skin are repeated for this one container, at (1,2,2) / (1,3,2).
   -------------------------------------------------------------------------- */

body.kc-rd #lost-pswd .modal-content {
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-radius: var(--kc-radius-lg);
	box-shadow: var(--panel-shadow);
}
body.kc-rd #lost-pswd .nm-myaccount-lost-reset-password { position: relative; }
body.kc-rd #lost-pswd div.lost-pswd-old-h2 {
	font-family: var(--kc-font-body);
	font-size: 22px;
	font-weight: 600;
	color: var(--text);
}
body.kc-rd #lost-pswd p:not([class]) {
	font-size: 14px;
	line-height: 1.6;
	color: var(--text-muted);
}

body.kc-rd #lost-pswd .form-row label {
	position: static;
	display: block;
	top: auto;
	left: auto;
	transform: none;
	opacity: 1;
	pointer-events: auto;
	text-transform: none;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	line-height: 1.35;
	color: var(--text);
	margin: 0;
	padding: 0 0 var(--kc-space-2);
}
body.kc-rd #lost-pswd .form-row input.input-text {
	width: 100%;
	height: auto;
	min-height: var(--field-height);
	/* 0 15px in the theme, with 20px of top padding left over from the
	   floating label — the value sat 10px below centre. */
	padding: 0 var(--kc-space-4);
	background: var(--field-bg);
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
	color: var(--text);
	font-family: var(--kc-font-body);
	/* 16px, not 14 — anything under 16 makes iOS Safari zoom the page on
	   focus, and 86.5% of this store's traffic is mobile. */
	font-size: 16px;
}
body.kc-rd #lost-pswd .form-row input.input-text:focus { border-color: var(--text); }

body.kc-rd #lost-pswd .close {
	width: var(--btn-min-height);
	height: var(--btn-min-height);
	font-size: 28px;
	line-height: 1;
	color: var(--text);
	right: var(--kc-space-2);
	top: var(--kc-space-2);
	touch-action: manipulation;
}
body.kc-rd #lost-pswd .close:hover { color: var(--urgent); }


/* --------------------------------------------------------------------------
   5. Account navigation
   Desktop: a quiet list in the sidebar. Mobile (§11): a two-column grid of
   48px pills, so all seven endpoints stay on screen instead of costing seven
   full-width rows of scroll above the content on every account page.
   -------------------------------------------------------------------------- */

body.kc-rd .woocommerce-MyAccount-navigation ul {
	list-style: none;
	margin: 0;
	padding: 0;
}
body.kc-rd .woocommerce-MyAccount-navigation ul li {
	float: none;
	clear: none;
	margin: 0;
	list-style: none;
}
body.kc-rd .my-account .woocommerce-MyAccount-navigation ul li a,
body.kc-rd .woocommerce-MyAccount-navigation ul li a {
	display: flex;
	align-items: center;
	min-height: var(--btn-min-height);
	margin: 0;
	padding: var(--kc-space-2) var(--kc-space-3);
	font-size: 15px;
	font-weight: 400;
	color: var(--text);
	border: 1px solid transparent;
	border-bottom: 1px solid var(--border);
	border-radius: 0;
	text-decoration: none;
	touch-action: manipulation;
}
body.kc-rd .woocommerce-MyAccount-navigation ul li:last-child a { border-bottom-color: transparent; }
body.kc-rd .my-account .woocommerce-MyAccount-navigation ul li a:hover,
body.kc-rd .woocommerce-MyAccount-navigation ul li a:hover {
	color: var(--trust);
	background: var(--surface-sunken);
}
/* Current page is marked three ways — weight, ground and rule — so it never
   depends on colour alone. */
body.kc-rd .my-account .woocommerce-MyAccount-navigation ul li.is-active a,
body.kc-rd .woocommerce-MyAccount-navigation ul li.is-active a {
	color: var(--text);
	font-weight: 600;
	background: var(--surface-sunken);
	border-color: var(--border);
	border-left: 3px solid var(--trust);
	padding-left: calc(var(--kc-space-3) - 2px);
}


/* --------------------------------------------------------------------------
   6. User block
   -------------------------------------------------------------------------- */

body.kc-rd .nm-MyAccount-user {
	background: var(--surface-sunken);
	border: 1px solid var(--border);
	border-radius: var(--card-radius);
	padding: var(--kc-space-4);
	margin-bottom: var(--kc-space-5);
	text-align: left;
	min-height: 0;
}
body.kc-rd .nm-MyAccount-user-info .nm-username {
	font-size: 15px;
	line-height: 1.4;
	color: var(--text-muted);
	overflow-wrap: anywhere;
}
body.kc-rd .nm-MyAccount-user-info .nm-username strong {
	color: var(--text);
	font-weight: 600;
}
body.kc-rd .nm-MyAccount-user-image { max-width: 48px; margin-bottom: var(--kc-space-2); }


/* --------------------------------------------------------------------------
   7. Content shell, headings, inline links, field-row spacing
   -------------------------------------------------------------------------- */

body.kc-rd .woocommerce-MyAccount-content h2,
body.kc-rd .woocommerce-edit-address .woocommerce-MyAccount-content h2 {
	font-family: var(--kc-font-body);
	font-size: 20px;
	font-weight: 600;
	color: var(--text);
	margin: 0 0 var(--kc-space-4);
}
body.kc-rd .woocommerce-MyAccount-content h3,
body.kc-rd .woocommerce-MyAccount-content h4 {
	font-family: var(--kc-font-body);
	font-size: 17px;
	font-weight: 600;
	color: var(--text);
	margin: var(--kc-space-5) 0 var(--kc-space-3);
}
body.kc-rd .nm-MyAccount-dashboard {
	font-size: 15px;
	line-height: 1.65;
	color: var(--text);
}
/* The theme option paints every <a> #dc9814. Inline links are ink plus a rule;
   the accent stays reserved for the one action on the view. */
body.kc-rd .nm-MyAccount-dashboard a,
body.kc-rd .woocommerce-MyAccount-content address a {
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 2px;
}
body.kc-rd .nm-MyAccount-dashboard a:hover { color: var(--trust); }

/* Destructive action stays a link, never a button, and still reads as the one
   dangerous thing on the view — but the tomato is on the rule, not the glyphs.
   --urgent resolves to --kc-tomato-600 and measures 4.14:1 on --surface,
   4.28:1 on --card-bg: both under the 4.5:1 body threshold, and 15px/400 is
   not large text (WCAG large = 24px regular / 18.66px bold), so as text colour
   it fails 1.4.3. A 2px underline is a non-text indicator and owes 3:1, which
   the same colour clears. The kit has no dark-tomato text pairing; adding one
   (--urgent-text) is a token-layer change and belongs to the kc-redesign.css
   owner, not to three local patches. */
body.kc-rd .woocommerce-MyAccount-content a.del {
	display: inline-flex;
	align-items: center;
	min-height: var(--btn-min-height);
	color: var(--text);
	font-size: 15px;
	text-decoration: underline;
	text-decoration-color: var(--urgent);
	text-decoration-thickness: 2px;
	text-underline-offset: 3px;
	touch-action: manipulation;
}

/* Row rhythm. The display-name hint (`.form-row > span > em`) is NOT here:
   10-forms.css:474 already names that exact selector with the same treatment,
   so the copy that used to sit below was duplication, not gap-filling. */
body.kc-rd .woocommerce-MyAccount-content .woocommerce-form-row,
body.kc-rd .woocommerce-MyAccount-content .form-row { margin-bottom: var(--kc-space-4); }

body.kc-rd .woocommerce-EditAccountForm fieldset {
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
	padding: var(--kc-space-4);
	margin: var(--kc-space-5) 0;
}
body.kc-rd .woocommerce-EditAccountForm fieldset h3 { margin-top: 0; }


/* --------------------------------------------------------------------------
   8. Buttons — only the battles 20-buttons.css cannot win
   That partial assigns primary and secondary generically at (0,3,1). Four
   child-theme rules carry ids and out-rank it; those are the only colour
   rules restated. The rest is geometry the child forces on this surface, or a
   role assignment inside the orders table.
   -------------------------------------------------------------------------- */

/* main.css:5072 (3,2,0) — this is (3,3,1). */
body.kc-rd .my-account #customer_login .button:not(#nm-show-register-button):not(#nm-show-login-button) {
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border: 2px solid var(--btn-primary-border);
	border-radius: var(--kc-radius-md);
	box-shadow: var(--btn-primary-shadow);
	font-weight: 700;
	min-height: var(--btn-min-height);
}
/* main.css:4975 (2,2,0) — this is (2,3,1). */
body.kc-rd .my-account #customer_login .button#nm-show-register-button,
body.kc-rd .my-account #customer_login .button#nm-show-login-button {
	background: transparent;
	color: var(--text);
	border: 2px solid var(--text);
	border-radius: var(--kc-radius-md);
	box-shadow: none;
	font-weight: 600;
	min-height: var(--btn-min-height);
}
/* main.css:4226 / 4145 (1,1,0) — these are (1,2,1) / (1,3,1). */
body.kc-rd #lost-pswd .woocommerce-Button {
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border: 2px solid var(--btn-primary-border);
	border-radius: var(--kc-radius-md);
	box-shadow: var(--btn-primary-shadow);
	font-weight: 700;
	min-height: var(--btn-min-height);
}
body.kc-rd #lost-pswd .close-button.button {
	background: transparent;
	color: var(--text);
	border: 2px solid var(--text);
	border-radius: var(--kc-radius-md);
	box-shadow: none;
	font-weight: 600;
	min-height: var(--btn-min-height);
}

/* Geometry only: main.css:2452 pins every button in the account content to a
   280×56 block with 40px of vertical margin. Colour comes from
   20-buttons.css §20.2 through .woocommerce-Button.button. */
body.kc-rd .woocommerce-MyAccount-content .button,
body.kc-rd .woocommerce-MyAccount-content button.button {
	width: auto;
	height: auto;
	min-height: var(--btn-min-height);
	margin: 0;
}
body.kc-rd .woocommerce-MyAccount-content p > .button,
body.kc-rd .woocommerce-MyAccount-content p > button.button {
	width: 100%;
	margin: var(--kc-space-4) 0 0;
}
/* Address book "Edit" / "Add" — a plain <a>, so no button partial reaches it.
   Navigation, not a commit: secondary. */
body.kc-rd .woocommerce-MyAccount-content a.edit {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--btn-min-height);
	padding: var(--kc-space-2) var(--kc-space-5);
	background: transparent;
	color: var(--text);
	border: 2px solid var(--text);
	border-radius: var(--kc-radius-md);
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	text-decoration: none;
	touch-action: manipulation;
}
body.kc-rd .woocommerce-MyAccount-content a.edit:hover { background: var(--surface-sunken); }


/* --------------------------------------------------------------------------
   9. Buy it again — markup from inc-account.php, styling from
   kc-redesign.css §9. Those rules are (0,1,0) and lose to the child theme's
   (0,2,0) button rule, which is what currently blows the grid apart. Only the
   stolen properties are restated; nothing else from §9 is duplicated.
   -------------------------------------------------------------------------- */

body.kc-rd .woocommerce-MyAccount-content .kc-buy-again h3 {
	margin: 0 0 var(--kc-space-3);
	font-size: 17px;
}
body.kc-rd .woocommerce-MyAccount-content .kc-buy-again .kc-ba-add {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: auto;
	min-height: var(--btn-min-height);
	margin: auto 0 0;
}
body.kc-rd .woocommerce-MyAccount-content .kc-buy-again .kc-ba-price {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	color: var(--text);
}
body.kc-rd .woocommerce-MyAccount-content .kc-buy-again .kc-ba-title { color: var(--text); }


/* --------------------------------------------------------------------------
   10. Orders list — desktop
   The theme's answer to a narrow screen is to delete columns: shop.css hides
   the ORDER TOTAL below 991px and the ORDER DATE below 550px. On a phone the
   customer is left with a number, a status and a button, and no money at all.
   Both come back here; §11 turns the table into cards instead.
   -------------------------------------------------------------------------- */

body.kc-rd table.woocommerce-orders-table th.woocommerce-orders-table__header-order-total,
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-total,
body.kc-rd table.woocommerce-orders-table th.woocommerce-orders-table__header-order-date,
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-date {
	display: table-cell;
}

body.kc-rd table.woocommerce-orders-table { width: 100%; border-collapse: collapse; }
body.kc-rd .woocommerce-MyAccount-content table.woocommerce-orders-table tr th {
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--text-muted);
	text-align: left;
	padding: 0 var(--kc-space-3) var(--kc-space-2) 0;
}
body.kc-rd table.woocommerce-orders-table tr td {
	padding: var(--kc-space-3) var(--kc-space-3) var(--kc-space-3) 0;
	border-top: 1px solid var(--border);
	color: var(--text);
	font-size: 15px;
	vertical-align: middle;
}
body.kc-rd table.woocommerce-orders-table tr:last-child td { border-bottom: 1px solid var(--border); }

/* Order numbers, dates and money are data: mono, tabular, full-contrast ink. */
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-number a,
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-date time,
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-total .woocommerce-Price-amount {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	color: var(--text);
}
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-number a {
	font-size: 16px;
	font-weight: 700;
	text-decoration: none;
}
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-number a:hover { text-decoration: underline; }
/* The cell reads "$133.00 for 3 items" as one text node, so the amount is
   weighted and the count is not — and neither is ever grey. */
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-total {
	color: var(--text);
	font-size: 14px;
}
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-total .woocommerce-Price-amount {
	font-size: 16px;
	font-weight: 700;
}

/* Status. Only two states are marked — settled and gone wrong. Everything else
   is full-contrast ink with weight, so the amber accent is never spent on a
   label.
   Settled takes the colour: --trust is 7.8:1 on --surface and 8.0:1 on the
   mobile --card-bg, so it is legal as text. Gone-wrong does NOT: --urgent is
   4.14:1 on --surface, 4.28:1 on --card-bg and 3.65:1 on --surface-sunken,
   all under 4.5:1, and 15px/600 is not large text. This is the field that
   tells a customer whether an order they paid for died — it cannot be the one
   that fails on the 86.5%-mobile ground. So the word stays ink at 600 and the
   tomato moves to a left rule, a non-text indicator that only owes 3:1. Same
   left-edge vocabulary as §5's current page and §12's order-info panel. */
body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-status {
	font-weight: 600;
	color: var(--text);
}
body.kc-rd table.woocommerce-orders-table tr[class*="--status-completed"] td.woocommerce-orders-table__cell-order-status { color: var(--trust); }
/* (0,4,4) — beats the mobile `tbody tr td {border:0}` block at (0,2,5) on
   class count, so the rule survives the card layout too. */
body.kc-rd table.woocommerce-orders-table tr[class*="--status-cancelled"] td.woocommerce-orders-table__cell-order-status,
body.kc-rd table.woocommerce-orders-table tr[class*="--status-failed"] td.woocommerce-orders-table__cell-order-status,
body.kc-rd table.woocommerce-orders-table tr[class*="--status-refunded"] td.woocommerce-orders-table__cell-order-status {
	color: var(--text);
	border-left: 3px solid var(--urgent);
	padding-left: var(--kc-space-3);
}

/* Row actions. "Pay" is the only one that moves money — 61% of recent orders
   are e-Transfer, i.e. placed before they are paid — so it is the only
   primary here. View and Cancel stay quiet. main.css:2521 pins these to
   110px wide at (0,3,0); this is (0,4,2). */
body.kc-rd.woocommerce-account .woocommerce-MyAccount-orders td.woocommerce-orders-table__cell-order-actions {
	white-space: normal;
	text-align: left;
}
body.kc-rd.woocommerce-account .woocommerce-MyAccount-orders td.woocommerce-orders-table__cell-order-actions a.button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: auto;
	height: auto;
	min-height: var(--btn-min-height);
	margin: 0 var(--kc-space-2) var(--kc-space-2) 0;
	padding: var(--kc-space-2) var(--kc-space-4);
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	background: transparent;
	color: var(--text);
	border: 2px solid var(--text);
	border-radius: var(--kc-radius-md);
	box-shadow: none;
	text-decoration: none;
	touch-action: manipulation;
}
body.kc-rd.woocommerce-account .woocommerce-MyAccount-orders td.woocommerce-orders-table__cell-order-actions a.button.pay {
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border-color: var(--btn-primary-border);
	box-shadow: var(--btn-primary-shadow);
	font-weight: 700;
}
/* Cancel: tomato on the 2px border, ink on the label. Same reason as the
   status cell — 15px/600 in --urgent is 4.14:1 on --surface and 4.28:1 inside
   the mobile card, both short of 4.5:1; the border is non-text and owes 3:1. */
body.kc-rd.woocommerce-account .woocommerce-MyAccount-orders td.woocommerce-orders-table__cell-order-actions a.button.cancel {
	border-color: var(--urgent);
	color: var(--text);
}


/* --------------------------------------------------------------------------
   11. Orders list -> stacked cards below 768px, plus the rest of mobile
   Every column keeps its meaning: the label comes from the data-title the
   WooCommerce template already writes on each cell, so no copy is invented
   and no column is dropped.
   -------------------------------------------------------------------------- */

@media all and (max-width: 767px) {

	/* Kept in the accessibility tree, taken off the screen — with the block
	   layout below, display:none would drop it and leaving it visible would
	   render the header row as a stray line of text. */
	body.kc-rd .woocommerce-MyAccount-content table.woocommerce-orders-table thead {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip: rect(0 0 0 0);
		white-space: nowrap;
	}

	body.kc-rd table.woocommerce-orders-table,
	body.kc-rd table.woocommerce-orders-table tbody,
	body.kc-rd table.woocommerce-orders-table tbody tr { display: block; width: 100%; }

	body.kc-rd table.woocommerce-orders-table tbody tr {
		background: var(--card-bg);
		border: 1px solid var(--card-border);
		border-radius: var(--card-radius);
		padding: var(--kc-space-3) var(--kc-space-4);
		margin: 0 0 var(--kc-space-3);
	}

	body.kc-rd table.woocommerce-orders-table th.woocommerce-orders-table__header-order-total,
	body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-total,
	body.kc-rd table.woocommerce-orders-table th.woocommerce-orders-table__header-order-date,
	body.kc-rd table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-date,
	body.kc-rd table.woocommerce-orders-table tbody tr td {
		display: flex;
		align-items: baseline;
		justify-content: space-between;
		gap: var(--kc-space-3);
		width: 100%;
		padding: var(--kc-space-2) 0;
		border: 0;
		border-bottom: 1px solid var(--border);
		text-align: right;
	}
	body.kc-rd table.woocommerce-orders-table tbody tr:last-child td { border-bottom: 1px solid var(--border); }

	body.kc-rd table.woocommerce-orders-table tbody tr td:before {
		content: attr(data-title);
		flex: 0 0 auto;
		font-family: var(--label-font);
		font-size: 12px;
		font-weight: 400;
		letter-spacing: .1em;
		text-transform: uppercase;
		color: var(--text-muted);
		text-align: left;
	}

	/* The number is the card's headline and the obvious thing to tap to open
	   the order. The cell is align-items:baseline, which sizes a flex item to
	   its own line box — 18px x the 1.6 inherited from shop.css:6165 is ~29px,
	   under both floors the kit names (44pt iOS / 48dp Android) — and the
	   cell's padding does not extend the anchor's hit area. So the anchor gets
	   a real 48px box and the cell gives up its own vertical padding, or the
	   headline row gains 16px of dead height. Every other target on this
	   surface is already pinned to --btn-min-height; this was the one gap. */
	body.kc-rd table.woocommerce-orders-table tbody tr td.woocommerce-orders-table__cell-order-number {
		align-items: center;
		padding-top: 0;
		padding-bottom: 0;
	}
	body.kc-rd table.woocommerce-orders-table tbody tr td.woocommerce-orders-table__cell-order-number a {
		display: inline-flex;
		align-items: center;
		min-height: var(--btn-min-height);
		font-size: 18px;
	}

	/* Actions sit at the foot of the card, full width, no label. */
	body.kc-rd table.woocommerce-orders-table tbody tr td.woocommerce-orders-table__cell-order-actions {
		flex-wrap: wrap;
		gap: var(--kc-space-2);
		border-bottom: 0;
		padding-top: var(--kc-space-3);
		padding-bottom: 0;
	}
	body.kc-rd table.woocommerce-orders-table tbody tr td.woocommerce-orders-table__cell-order-actions:before { content: none; }
	body.kc-rd.woocommerce-account .woocommerce-MyAccount-orders td.woocommerce-orders-table__cell-order-actions a.button {
		flex: 1 1 140px;
		margin: 0;
	}

	/* Address name fields stop sharing a line. */
	body.kc-rd .woocommerce-MyAccount-content .form-row-first,
	body.kc-rd .woocommerce-MyAccount-content .form-row-last,
	body.kc-rd .woocommerce-MyAccount-content .woocommerce-form-row--first,
	body.kc-rd .woocommerce-MyAccount-content .woocommerce-form-row--last {
		float: none;
		width: 100%;
	}

	/* Navigation: two-column grid of pills, every endpoint on screen. */
	body.kc-rd .woocommerce-MyAccount-navigation ul {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: var(--kc-space-2);
	}
	body.kc-rd .my-account .woocommerce-MyAccount-navigation ul li a,
	body.kc-rd .woocommerce-MyAccount-navigation ul li a {
		justify-content: center;
		text-align: center;
		background: var(--card-bg);
		border: 1px solid var(--border);
		border-radius: var(--kc-radius-md);
		font-size: 14px;
	}
	body.kc-rd .my-account .woocommerce-MyAccount-navigation ul li.is-active a,
	body.kc-rd .woocommerce-MyAccount-navigation ul li.is-active a {
		background: var(--surface-sunken);
		border: 2px solid var(--text);
		padding-left: var(--kc-space-3);
	}

	body.kc-rd .my-account #customer_login #nm-login-wrap,
	body.kc-rd .my-account #customer_login #nm-register-wrap { padding: var(--kc-space-5) var(--kc-space-4); }

	body.kc-rd .my-account #customer_login.nm-myaccount-login { padding-bottom: var(--kc-space-7); }
}


/* --------------------------------------------------------------------------
   12. Single order view
   -------------------------------------------------------------------------- */

body.kc-rd .woocommerce-MyAccount-content p.nm-order-info,
body.kc-rd .nm-order-view p.nm-order-info {
	background: var(--surface-sunken);
	color: var(--text);
	border: 1px solid var(--border);
	border-left: 3px solid var(--trust);
	border-radius: var(--card-radius);
	padding: var(--kc-space-4);
	margin: 0 0 var(--kc-space-5);
	font-size: 15px;
	line-height: 1.6;
}
body.kc-rd p.nm-order-info mark,
body.kc-rd p.nm-order-info .order-number,
body.kc-rd p.nm-order-info .order-date,
body.kc-rd p.nm-order-info .order-status {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
	color: var(--text);
	background: none;
}

/* Order details is already a two-column table with its head hidden by the
   theme, and it carries no data-title, so it stays a table — two columns fit
   375px without help. */
body.kc-rd .woocommerce-order-details__title,
body.kc-rd .woocommerce-column__title {
	font-family: var(--kc-font-body);
	font-size: 18px;
	font-weight: 600;
	color: var(--text);
}
body.kc-rd table.woocommerce-table--order-details { color: var(--text); }
body.kc-rd table.woocommerce-table--order-details tbody tr td,
body.kc-rd table.woocommerce-table--order-details tbody tr:first-child td { border-color: var(--border); }
body.kc-rd table.woocommerce-table--order-details td.product-total,
body.kc-rd table.woocommerce-table--order-details td.product-total .woocommerce-Price-amount,
body.kc-rd table.woocommerce-table--order-details tfoot tr td,
body.kc-rd table.woocommerce-table--order-details tfoot tr td .woocommerce-Price-amount {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	color: var(--text);
}
body.kc-rd table.woocommerce-table--order-details tfoot tr th {
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--text-muted);
	text-align: left;
}
body.kc-rd table.woocommerce-table--order-details tfoot tr:last-child th,
body.kc-rd table.woocommerce-table--order-details tfoot tr:last-child td {
	font-size: 18px;
	font-weight: 700;
	color: var(--text);
	border-color: var(--border);
}
body.kc-rd table.woocommerce-table--order-details tfoot tr:last-child th { letter-spacing: .04em; }

body.kc-rd .woocommerce-OrderUpdates.commentlist li .comment-text .meta,
body.kc-rd .nm-order-view .commentlist li .comment-text .meta {
	font-family: var(--label-font);
	font-size: 12px;
	letter-spacing: .04em;
	color: var(--text-muted);
}
body.kc-rd .woocommerce-OrderUpdates.commentlist li .comment-text .description { color: var(--text); }

body.kc-rd .order-again { text-align: left; }
body.kc-rd .order-again .button { margin-top: var(--kc-space-4); }


/* --------------------------------------------------------------------------
   13. Addresses and the empty state
   The address columns are Savoy grid cells (col-sm-6), so the panel treatment
   goes on the <address> rather than the column — a border on the column would
   make the two panels touch across the gutter.
   -------------------------------------------------------------------------- */

body.kc-rd .woocommerce-MyAccount-content .woocommerce-Address address {
	display: block;
	background: var(--surface-sunken);
	border: 1px solid var(--border);
	border-radius: var(--card-radius);
	padding: var(--kc-space-4);
	margin: 0 0 var(--kc-space-3);
	font-style: normal;
	line-height: 1.6;
	color: var(--text);
}
body.kc-rd .woocommerce-MyAccount-content .woocommerce-Address .woocommerce-Address-title h3 { margin-top: 0; }

/* shop.css:6093 `.woocommerce-MyAccount-content .woocommerce-Message a
   {display:none}` strips the only action out of the empty-orders state and
   leaves a dead end. 20-buttons.css already ties that rule and wins on file
   order; this is the explicit, documented version of the same result.
   Keyed to .woocommerce-Message like the rule below, and for the same reason:
   the theme rule being fought is itself keyed to .woocommerce-Message, so a
   `.woocommerce-info` branch would fight nothing and would only reach into
   80-notices.css's buttons. Buttons in the notices that woocommerce_output_
   all_notices prints into this column are 80-notices.css §80.2. */
body.kc-rd .woocommerce-MyAccount-content .woocommerce-Message a.button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--btn-min-height);
	margin-top: var(--kc-space-3);
}
/* The empty-orders template prints the button before its own sentence, which
   reads as a button with an orphan line under it. One focusable element in the
   box, so tab order is unaffected.
   🔴 Keyed to .woocommerce-Message, not to .woocommerce-info, and this matters:
   .woocommerce-info is not private to this notice. wc-template-hooks.php:326
   hooks woocommerce_output_all_notices onto woocommerce_account_content at
   priority 5, so every notice-level message on every account endpoint is
   printed inside .woocommerce-MyAccount-content as
   `<div class="nm-shop-notice woocommerce-info">` (notices/notice.php via
   Savoy's override). Those belong to 80-notices.css, which gives them a
   ::before state word — and a ::before is the first flex item, so a bare
   `.woocommerce-info` here would column-reverse that eyebrow to the bottom of
   the box and align-items:flex-start would shrink the message off its border
   rule. The empty-orders div is printed straight by orders.php, carries
   .woocommerce-Message and never carries .nm-shop-notice, so this pair
   isolates it. Do not widen this selector. */
body.kc-rd .woocommerce-MyAccount-content .woocommerce-Message.woocommerce-info {
	display: flex;
	flex-direction: column-reverse;
	align-items: flex-start;
	gap: var(--kc-space-2);
}

body.kc-rd .woocommerce-MyAccount-content .woocommerce-pagination {
	display: flex;
	gap: var(--kc-space-3);
	padding-top: var(--kc-space-5);
}
body.kc-rd .woocommerce-MyAccount-content .woocommerce-pagination .button {
	width: auto;
	margin: 0;
	padding: var(--kc-space-2) var(--kc-space-5);
	background: transparent;
	color: var(--text);
	border: 2px solid var(--text);
	box-shadow: none;
	font-weight: 600;
}


/* --------------------------------------------------------------------------
   14. Focus — never removed, never colour-only, never the same as hover.
   Fields are 10-forms.css §4 and buttons are 20-buttons.css §20.1; this is
   the links and the navigation, which neither of them reaches.
   -------------------------------------------------------------------------- */

body.kc-rd .my-account #customer_login a:focus-visible,
body.kc-rd #lost-pswd a:focus-visible,
body.kc-rd #lost-pswd .close:focus-visible,
body.kc-rd .woocommerce-MyAccount-navigation ul li a:focus-visible,
body.kc-rd .woocommerce-MyAccount-content a:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}


/* --------------------------------------------------------------------------
   15. Reduced motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	body.kc-rd .my-account #customer_login *,
	body.kc-rd #lost-pswd *,
	body.kc-rd .woocommerce-MyAccount-navigation *,
	body.kc-rd .woocommerce-MyAccount-content * {
		/* !important because the theme sets `transition: all .3s ease`
		   directly on several of these elements. */
		transition: none !important;
		animation: none !important;
	}
	body.kc-rd .woocommerce-MyAccount-content .button:hover,
	body.kc-rd .woocommerce-MyAccount-content .button:active,
	body.kc-rd .my-account #customer_login .button:hover,
	body.kc-rd .my-account #customer_login .button:active { transform: none; }
}

/* ===== 51-login.css ===== */
/* ==========================================================================
   51-login.css — SURFACE: 结账注册/登录卡(合一)/ 付款后建号卡
   Markup 来自 inc-login.php。2026-08-27 owner 改版:注册卡与 "Welcome back"
   登录卡合并为**同一张卡的三态**(.kc-pl-reg[data-state=new|checking|known]),
   位置 = checkout-v5 §3 的插槽(Email 正下方),不再 prepend 到 form.checkout。
   fragment 只替换 .kc-pl-reg-copy 文案区 —— 状态属性与密码输入原地保留。

   ⚠️ 本文件**不碰结账错误框**。
   `.woocommerce-error` 的呈现完全归 80-notices.css 的 owner —— 那边已经做全了
   (番茄色左边框、padding、以及把主题 `.woocommerce-error .wc-backward{display:none}`
    藏掉的恢复链接重新放出来,见 80-notices.css:51 与 :404)。
   若发现错误里的 `.showlogin` 链接仍被藏,**报给 notices owner**,别自己压。

   与 52-rewards.css 的关系:那个是 My Account 里的 Rewards surface,本文件是结账/
   thank-you 上的登录与建号。两者都用 --trust 表示"客人自己的东西",其余不共享组件。

   tokens only(build_kc_css.py --check 会拒绝原始品牌 hex)。
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. 结账注册/登录卡(插槽内,一张卡三态)
   new      新邮箱:标题一行 + 正文一行 + Create a password
   checking 探测中:一行 spinner
   known    已注册邮箱:Welcome back + 密码框 + Forgot / sign-in link
   -------------------------------------------------------------------------- */
.kc-pl-reg {
	border: 1px solid var(--border);
	border-left: 3px solid var(--action);
	border-radius: var(--kc-radius-md);
	/* 竖向内距比其它卡紧一档:这张卡夹在 Email 与 "Delivery address" 之间,
	   每多 8px 都把地址块推下首屏。 */
	padding: var(--kc-space-3) var(--kc-space-5);
	margin: 0 0 var(--kc-space-2);
	background: var(--card-bg, var(--surface-raised));
}

/* 三态互斥 */
.kc-pl-reg[data-state="new"] .kc-pl-checking,
.kc-pl-reg[data-state="new"] .kc-pl-known,
.kc-pl-reg[data-state="checking"] .kc-pl-new,
.kc-pl-reg[data-state="checking"] .kc-pl-known,
.kc-pl-reg[data-state="known"] .kc-pl-new,
.kc-pl-reg[data-state="known"] .kc-pl-checking {
	display: none;
}

/* [hidden] 的 p 别被主题的 display 规则放出来 */
.kc-pl-reg [hidden] {
	display: none;
}

.kc-pl-reg h4 {
	font-family: var(--kc-font-body);
	font-size: 16px;
	font-weight: 600;
	line-height: 1.3;
	margin: 0 0 2px;
}

.kc-pl-reg h4 .kc-pl-num {
	font-variant-numeric: tabular-nums;
}

.kc-pl-reg p {
	font-size: 14px;
	color: var(--text-muted);
	margin: 0;
}

/* ---- new 态:建密码字段 ---- */
.kc-pl-field {
	margin-top: var(--kc-space-2);
}

/* Show/Hide 是**并排的兄弟控件,不是浮在输入框上的浮层**。
   为什么不浮:Savoy 那条 `.woocommerce-billing-fields .form-row:not(#billing_city_field)
   :not(#billing_state_field)… input { padding: 0 15px }` 带 3 个 ID 级特异性(:not() 里的
   ID 照算),我们的 `padding-right: 64px` 打不过它 —— 2026-08-27 实测:375 手机上
   placeholder "Create a password (8+ characters)" 直接压在 SHOW 上,糊成一团。
   并排就与 padding 无关了,以后换文案也不会再撞。 */
.kc-pl-pwbox {
	display: flex;
	align-items: stretch;
	gap: var(--kc-space-2);
}

.kc-pl-pwbox .input-text {
	flex: 1 1 auto;
	min-width: 0;
}

.kc-pl-eye {
	flex: 0 0 auto;
	min-width: 56px;
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
	background: var(--surface);
	padding: 0 var(--kc-space-3);
	font-family: var(--kc-font-body);
	font-size: 12px;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--text-muted);
	cursor: pointer;
}

.kc-pl-eye:hover,
.kc-pl-eye:focus-visible {
	color: var(--text);
}

/* WC core(woocommerce.js:115-134)给结账页每个密码框的父节点加 .password-input 并塞
   自己的 show/hide 按钮 —— 会和我们的 Show/Hide 叠在同一个右上角。卡内藏掉 core 那颗,
   作用域仅 .kc-pl-pwbox,结账页其它密码框不受影响。 */
.kc-pl-pwbox .show-password-input {
	display: none;
}

.kc-pl-hint {
	font-size: 13px;
	color: var(--text-muted);
	margin-top: 4px;
}

/* ---- checking 态 ---- */
.kc-pl-checking {
	display: flex;
	align-items: center;
	gap: var(--kc-space-2);
	min-height: 44px;
	font-size: 14px;
	color: var(--text-muted);
}

.kc-pl-spin {
	width: 14px;
	height: 14px;
	flex: 0 0 auto;
	border: 2px solid var(--border);
	border-top-color: var(--action);
	border-radius: 50%;
	animation: kc-pl-rot 0.7s linear infinite;
}

@keyframes kc-pl-rot {
	to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
	.kc-pl-spin { animation: none; }
}

/* ---- known 态 ---- */
/* 🔴 2026-08-27 验收退回 S1:原来这里有 .kc-pl-kn-line(「{N} points waiting」)。
   那一行的数字来自**未登录就能打**的探测接口 = 对外泄露客户余额,已连同
   响应里的 name/points 一起砍掉,规则随之删除(留着就是死规则)。 */

.kc-pl-row {
	display: flex;
	gap: var(--kc-space-2);
	align-items: stretch;
	margin-top: var(--kc-space-2);
}

.kc-pl-row .kc-pl-pwbox {
	flex: 1 1 180px;
	min-width: 0;
}

.kc-pl-row .button.kc-pl-go {
	flex: 0 0 auto;
	min-height: 44px;
	height: 44px;
	padding: 0 var(--kc-space-4);
}

.kc-pl-err {
	color: var(--urgent);
	font-size: 13.5px;
	margin: var(--kc-space-2) 0 0;
}

/* 🟠 F3:sent 现在排在 .kc-pl-links **之前**(发完链接后找回入口不再被藏)。
   ⚠️ 本块的 margin/color/font-size 实际由上面的 `.kc-pl-reg p`(0,1,1)接管 ——
   `.kc-pl-sent` 只有 (0,1,0),打不过。留着是为了表达意图;真要改这里的间距,
   选择器必须写成 `.kc-pl-reg p.kc-pl-sent`。(2026-08-27 用 CDP getMatchedStyles 实测) */
.kc-pl-sent {
	color: var(--text);
	font-size: 13.5px;
	margin: var(--kc-space-2) 0 0;
}

/* 两个文字动作:触控 44px 由 min-height 撑。
   ⚠️ 这里的 margin 同样打不过 `.kc-pl-reg p`(0,1,1)—— 实际生效值是 0,
   卡片高度一直是按 margin:0 来的(验收报告 155px 那个数就是这么来的)。别再写负外边距
   骗自己,要收高度得提高特异性。 */
.kc-pl-links {
	display: flex;
	align-items: center;
	gap: var(--kc-space-2);
	margin: 0;
	font-size: 13.5px;
}

/* 🔴 legacy 暗层:Savoy main.css 有一条
   `#customer_details p:last-child { margin-bottom: 15px !important; }`(ID + !important)。
   F3 把 .kc-pl-sent 挪到 .kc-pl-links 之前以后,links 就成了 #customer_details 里的
   p:last-child,凭空吃进 15px —— known 态卡高 155 → 170(2026-08-27 CDP 实测坐实)。
   用同等武器压回去,卡高回到改版前的口径。 */
#customer_details .kc-pl-reg p.kc-pl-links {
	margin-bottom: 0 !important;
}

.kc-pl-links a,
.kc-pl-linkbtn {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	color: var(--text-muted);
	text-decoration: underline;
	text-underline-offset: 2px;
}

.kc-pl-linkbtn {
	border: 0;
	background: none;
	padding: 0;
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	cursor: pointer;
}

.kc-pl-links a:hover,
.kc-pl-links a:focus-visible,
.kc-pl-linkbtn:hover,
.kc-pl-linkbtn:focus-visible {
	color: var(--text);
}

.kc-pl-dot {
	color: var(--text-faint);
}

/* ---- 探测被限流(429)时的一句提示 ---- */
/* 卡级元素,三态之外:429 会安全降级回 new 态,提示必须在 new 态也看得见。
   markup 是 div 不是 p —— 上面那条 legacy `#customer_details p:last-child` 只吃 p,
   写成 p 会再吃一次 15px !important。常驻态 [hidden] = 零高度,不占版面。 */
.kc-pl-note {
	font-size: 13.5px;
	line-height: 1.35;
	color: var(--text-muted);
	margin-top: var(--kc-space-2);
}

/* --------------------------------------------------------------------------
   2. 链接登录后的「去设个密码」提示
   ⚠️ 默认**不渲染**:inc-login.php 的 KC_Login::PW_NUDGE = false(文案待 owner 过目)。
   样式先备好,owner 拍板文案后一行开关即可上线,不用再动 CSS。
   -------------------------------------------------------------------------- */
.kc-pl-nudge {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--kc-space-2);
	border: 1px solid var(--border);
	border-left: 3px solid var(--action);
	border-radius: var(--kc-radius-md);
	padding: var(--kc-space-3) var(--kc-space-4);
	margin: 0 0 var(--kc-space-4);
	background: var(--card-bg, var(--surface-raised));
	font-size: 14px;
	line-height: 1.4;
	color: var(--text-muted);
}

.kc-pl-nudge a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 2px;
}

/* --------------------------------------------------------------------------
   3. Thank-you 页的建号卡
   不在结账路径上加摩擦 —— 把 61% 的游客留到**付款完成后**再转化
   -------------------------------------------------------------------------- */
.kc-pl-claim {
	border: 1px solid var(--border);
	border-left: 3px solid var(--action);
	border-radius: var(--kc-radius-md);
	padding: var(--kc-space-4) var(--kc-space-5);
	margin: var(--kc-space-5) 0;
	background: var(--card-bg, var(--surface-raised));
}

.kc-pl-claim p {
	font-size: 15px;
	color: var(--text-muted);
	margin: 0;
}

.kc-pl-claim p + p {
	margin-top: var(--kc-space-4);
}

.kc-pl-claim strong {
	color: var(--text);
	font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   4. 手机
   -------------------------------------------------------------------------- */
@media (max-width: 600px) {
	.kc-pl-claim {
		padding: var(--kc-space-4);
	}

	.kc-pl-nudge {
		padding: var(--kc-space-3);
		font-size: 13.5px;
	}

	.kc-pl-reg {
		padding: var(--kc-space-3) var(--kc-space-4);
	}

	.kc-pl-reg h4 {
		font-size: 15px;
	}

	.kc-pl-reg p {
		font-size: 13.5px;
		line-height: 1.35;
	}

	/* 两个文字动作在 375 上必须**共处一行**:换行后 "Forgot password?" 和
	   "Email me a sign-in link" 各折成两行、中间那个 · 悬在半空,读起来像四个链接。
	   收字号 + 禁折行,触控高度仍是 44px。 */
	.kc-pl-links {
		font-size: 12.5px;
	}

	.kc-pl-links a,
	.kc-pl-linkbtn {
		white-space: nowrap;
		font-size: 12.5px;
	}
}

/* ===== 52-rewards.css ===== */
/* ==========================================================================
   52-rewards.css — SURFACE: Rewards（积分 / 兑换礼物 / 生日）
   Markup 来自 inc-rewards.php。设计稿 artifact eab93257。

   与 50-account.css 的关系:那个文件负责账户外壳（标题/导航/内容框/按钮基座/
   焦点态），本文件只画 Rewards 自己的组件。**不重复定义外壳**，也不覆盖它。
   数字前缀 52 > 50 = 平局时本文件赢，但除了导航里的余额药丸没有平局。

   铁律
   - 松绿(--trust) = 客人自己挣来的。已解锁的礼物用它；未解锁一律安静墨色。
     系统绝不用绿色替客人预选任何东西。
   - Gazella(--kc-font-display) 只用于 ≥28px。余额大数字是 40px+ 但它是**数据**
     不是品牌面 → 仍用 body 字体 + tabular-nums（数字对齐比风格重要）。
   - 兑换按钮是唯一花分的控件；徽章/标签无边框无阴影不可点。
   - tokens only（build_kc_css.py --check 会拒绝原始品牌 hex）
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. 通用容器
   -------------------------------------------------------------------------- */
.kc-rw,
.kc-rw-hub {
	display: flex;
	flex-direction: column;
	gap: var(--kc-space-4);
}

.kc-rw-card {
	background: var(--card-bg, var(--surface-raised));
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
	padding: var(--kc-space-5);
}

.kc-rw-eyebrow {
	font-family: var(--kc-font-body);
	font-size: 11px;
	font-weight: 600;
	line-height: 1;
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--text-faint);
	margin: 0;
}

.kc-rw-num {
	font-variant-numeric: tabular-nums;
}

.kc-rw-h {
	font-family: var(--kc-font-body);
	font-size: 19px;
	font-weight: 600;
	line-height: 1.3;
	margin: var(--kc-space-6) 0 0;
}

.kc-rw-sub {
	font-size: 14.5px;
	color: var(--text-muted);
	margin: var(--kc-space-2) 0 0;
}

/* --------------------------------------------------------------------------
   2. 余额
   -------------------------------------------------------------------------- */
.kc-rw-bal-row {
	display: flex;
	gap: var(--kc-space-6);
	flex-wrap: wrap;
	align-items: flex-end;
	justify-content: space-between;
}

.kc-rw-big {
	font-family: var(--kc-font-body);
	font-size: clamp(40px, 6vw, 58px);
	font-weight: 400;
	line-height: 1;
	letter-spacing: -.03em;
	font-variant-numeric: tabular-nums;
	margin: var(--kc-space-1) 0 0;
}

.kc-rw-worth {
	font-size: 15px;
	color: var(--text-muted);
	margin: var(--kc-space-1) 0 0;
}

.kc-rw-worth b {
	color: var(--text);
	font-weight: 600;
}

/* 进度条 —— 松绿,因为这是客人自己攒出来的 */
.kc-rw-track {
	height: 8px;
	border-radius: 99px;
	background: var(--surface-sunken);
	overflow: hidden;
	margin-top: var(--kc-space-4);
}

.kc-rw-track i {
	display: block;
	height: 100%;
	background: var(--trust);
	border-radius: 99px;
}

.kc-rw-trackcap {
	display: flex;
	justify-content: space-between;
	gap: var(--kc-space-3);
	flex-wrap: wrap;
	font-size: 13px;
	color: var(--text-muted);
	margin-top: var(--kc-space-2);
}

/* --------------------------------------------------------------------------
   3. 兑换阶梯
   -------------------------------------------------------------------------- */
.kc-rw-ladder {
	list-style: none;
	margin: var(--kc-space-5) 0 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
	gap: var(--kc-space-3);
}

.kc-rw-rung {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--kc-space-2);
	padding: var(--kc-space-4) var(--kc-space-3);
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
}

.kc-rw-rung form {
	margin: 0;
	margin-top: auto;
}

/* 已解锁 = 松绿 = 客人挣来的 */
.kc-rw-rung.open {
	border-color: var(--trust);
	background: color-mix(in srgb, var(--trust) 8%, transparent);
}

.kc-rw-rung.open .kc-rw-cost {
	color: var(--trust);
}

.kc-rw-rung.next {
	border-color: var(--border-strong);
}

.kc-rw-rung.shut {
	opacity: .55;
}

/* 标签:纯信息,无边框无阴影不可点 —— 绝不长得像购买控件 */
.kc-rw-tag {
	position: absolute;
	top: -9px;
	left: var(--kc-space-3);
	font-family: var(--kc-font-body);
	font-size: 10px;
	font-weight: 600;
	line-height: 1;
	letter-spacing: .1em;
	text-transform: uppercase;
	padding: 4px 7px;
	border-radius: 99px;
	background: var(--trust);
	color: var(--on-trust);
}

.kc-rw-tag.amber {
	background: var(--action);
	color: var(--on-action);
}

.kc-rw-nm {
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	line-height: 1.25;
}

.kc-rw-cost {
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	line-height: 1;
	color: var(--text-muted);
	font-variant-numeric: tabular-nums;
}

.kc-rw-retail {
	font-size: 12px;
	color: var(--text-faint);
}

.kc-rw-empty {
	font-size: 14.5px;
	color: var(--text-muted);
	margin: var(--kc-space-4) 0 0;
}

/* --------------------------------------------------------------------------
   4. 按钮 —— 只补 50-account.css / 20-buttons.css 覆盖不到的形态
   -------------------------------------------------------------------------- */
.kc-rw-btn {
	display: inline-block;
	appearance: none;
	border: 0;
	cursor: pointer;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	line-height: 1;
	padding: 11px var(--kc-space-4);
	border-radius: var(--kc-radius-sm);
	background: var(--action);
	color: var(--on-action);
	text-align: center;
	text-decoration: none;
}

/* 主题给 .woocommerce-MyAccount-content 里的 a 加了下划线 —— 按钮不该长得像链接。
   用 元素+类 提高特异性压过它，而不是叠 !important（AGENT_BRIEF §3）。 */
a.kc-rw-btn,
a.kc-rw-btn:hover,
a.kc-rw-btn:focus {
	text-decoration: none;
}

.kc-rw-btn.ghost {
	background: transparent;
	border: 1px solid var(--border-strong);
	color: var(--text);
}

.kc-rw-btn.wide {
	width: 100%;
}

.kc-rw-btn[disabled] {
	background: var(--surface-sunken);
	color: var(--text-faint);
	cursor: not-allowed;
}

/* --------------------------------------------------------------------------
   5. 已兑换待发 / 说明条
   -------------------------------------------------------------------------- */
.kc-rw-pending {
	display: flex;
	gap: var(--kc-space-4);
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	margin-top: var(--kc-space-5);
	padding: var(--kc-space-4) var(--kc-space-5);
	background: color-mix(in srgb, var(--trust) 8%, transparent);
	border: 1px solid var(--trust);
	border-radius: var(--kc-radius-md);
}

.kc-rw-pending h3 {
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	margin: 0;
}

.kc-rw-pending p {
	font-size: 14px;
	color: var(--text-muted);
	margin: var(--kc-space-1) 0 0;
}

.kc-rw-pending form {
	margin: 0;
}

.kc-rw-locked {
	display: block;
	font-size: 13px;
	color: var(--text-muted);
	background: var(--surface-sunken);
	border-radius: var(--kc-radius-sm);
	padding: var(--kc-space-2) var(--kc-space-3);
	margin: var(--kc-space-4) 0 0;
}

/* --------------------------------------------------------------------------
   6. 怎么赚
   -------------------------------------------------------------------------- */
.kc-rw-earn {
	list-style: none;
	margin: var(--kc-space-4) 0 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	gap: var(--kc-space-3);
}

.kc-rw-earn li {
	display: flex;
	flex-direction: column;
	gap: var(--kc-space-1);
	padding: var(--kc-space-4) var(--kc-space-3);
	border: 1px dashed var(--border-strong);
	border-radius: var(--kc-radius-md);
}

.kc-rw-earn b {
	font-size: 14px;
	font-weight: 600;
}

.kc-rw-earn span {
	font-size: 13px;
	color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   7. 生日提示 + 表单
   -------------------------------------------------------------------------- */
.kc-rw-prompt {
	display: flex;
	gap: var(--kc-space-4);
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	background: var(--card-bg, var(--surface-raised));
	border: 1px solid var(--border);
	border-left: 3px solid var(--action);
	border-radius: var(--kc-radius-md);
	padding: var(--kc-space-4) var(--kc-space-5);
}

.kc-rw-prompt-l {
	display: flex;
	gap: var(--kc-space-3);
	align-items: center;
}

.kc-rw-cake {
	font-size: 24px;
	line-height: 1;
}

.kc-rw-prompt h3 {
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	margin: 0;
}

.kc-rw-prompt p {
	font-size: 14px;
	color: var(--text-muted);
	margin: 2px 0 0;
}

.kc-rw-bday {
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
	padding: var(--kc-space-4) var(--kc-space-5);
	margin: 0 0 var(--kc-space-5);
}

.kc-rw-bday legend {
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	padding: 0 var(--kc-space-2);
}

.kc-rw-bday-lede {
	font-size: 14.5px;
	color: var(--text-muted);
	margin: 0 0 var(--kc-space-4);
}

.kc-rw-bday-row {
	display: flex;
	gap: var(--kc-space-2);
	max-width: 340px;
}

.kc-rw-bday-row select {
	flex: 1;
	min-width: 0;
}

.kc-rw-hint {
	font-size: 12.5px;
	color: var(--text-muted);
	margin: 6px 0 0;
}

/* --------------------------------------------------------------------------
   8. 导航里的余额药丸
   -------------------------------------------------------------------------- */
/* ⚠️ 内容由 inc-rewards.php::pip_style() 用 ::after 的 content 注入 ——
   菜单标签走 esc_html()，HTML 塞不进去（2026-08-26 实踩）。 */
.woocommerce-MyAccount-navigation li.kc-rw-nav > a::after {
	display: inline-block;
	margin-left: var(--kc-space-2);
	padding: 3px 7px;
	border-radius: 99px;
	background: var(--action);
	color: var(--on-action);
	font-family: var(--kc-font-body);
	font-size: 11px;
	font-weight: 600;
	line-height: 1;
	font-variant-numeric: tabular-nums;
	vertical-align: 1px;
}

/* 选中态的导航项底色是 --trust，琥珀药丸在上面会打架 → 转成半透明白 */
.woocommerce-MyAccount-navigation li.kc-rw-nav.is-active > a::after {
	background: rgba(255, 255, 255, .22);
	color: inherit;
}

/* --------------------------------------------------------------------------
   9. 购物车里的来源标 —— 让"达档送的"和"用分换的"分得清
   -------------------------------------------------------------------------- */
.kc-rw-src {
	display: inline-block;
	margin-left: var(--kc-space-2);
	font-family: var(--kc-font-body);
	font-size: 11px;
	font-weight: 600;
	line-height: 1;
	letter-spacing: .06em;
	text-transform: uppercase;
	color: var(--trust);
}

/* --------------------------------------------------------------------------
   10. 手机
   -------------------------------------------------------------------------- */
@media (max-width: 600px) {
	.kc-rw-card {
		padding: var(--kc-space-4);
	}

	.kc-rw-bal-row {
		gap: var(--kc-space-4);
	}

	.kc-rw-ladder {
		grid-template-columns: 1fr 1fr;
	}

	.kc-rw-pending,
	.kc-rw-prompt {
		padding: var(--kc-space-4);
	}
}

@media (prefers-reduced-motion: reduce) {
	.kc-rw-track i {
		transition: none;
	}
}

/* --------------------------------------------------------------------------
   11. PDP:"Earn N points with this size."
   铁律:纯信息。无边框、无阴影、无 hover 位移、不可点 —— 绝不能长得像购买控件。
   也不出现金额(价格每处只出现一次,大价格在 .kc-pline)。
   -------------------------------------------------------------------------- */
.kc-rw-pdp-earn {
	margin: var(--kc-space-2) 0 0;
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	line-height: 1.4;
	color: var(--text-muted);
	font-variant-numeric: tabular-nums;
	/* 占位时保持行高,变体切换不跳版 */
	min-height: 1.4em;
}

/* 🔴 2026-08-27 修复(owner 报「电脑端 CTA 做歪了」):这一行是 WooCommerce 的
   `woocommerce_before_add_to_cart_button` 打出来的,落点在 .woocommerce-variation-add-to-cart
   —— 主题把那个容器排成会换行的 flex 行(数量步进器 + Add to cart)。作为普通 flex item,
   本行实测吃掉 190px,而按钮是 flex:1 1 0% 只能吃剩下的:1280 下按钮塌到 109px,
   "Add to cart" 被折成三行。修法是让它独占一整行(同 .kc-qline 的做法,24-buy-value.css §24),
   而不是去压按钮的宽度。DOM 里它本来就排在步进器之前,所以不需要 order。 */
body.kc-rd.single-product form.cart .kc-rw-pdp-earn {
	flex: 0 0 100%;
	margin: 0 0 var(--kc-space-1);
}

/* --------------------------------------------------------------------------
   12. 流水表(只读,数据源见 inc-rewards.php::history())
   -------------------------------------------------------------------------- */
.kc-rw-scrollx {
	overflow-x: auto;
	margin-top: var(--kc-space-4);
}

.kc-rw-ledger {
	width: 100%;
	border-collapse: collapse;
	font-size: 14px;
	min-width: 340px;
}

.kc-rw-ledger th {
	text-align: left;
	font-family: var(--kc-font-body);
	font-size: 11px;
	font-weight: 600;
	line-height: 1;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--text-faint);
	padding: 0 0 var(--kc-space-3);
	border-bottom: 1px solid var(--border);
	white-space: nowrap;
}

.kc-rw-ledger td {
	padding: var(--kc-space-3) var(--kc-space-3) var(--kc-space-3) 0;
	border-bottom: 1px solid var(--border);
	vertical-align: top;
}

.kc-rw-ledger td:last-child {
	text-align: right;
	padding-right: 0;
	font-weight: 600;
	white-space: nowrap;
}

/* 赚 = 松绿(客人自己挣的);扣 = 安静墨色,不用警示色,兑换不是坏事 */
.kc-rw-ledger td.plus  { color: var(--trust); }
.kc-rw-ledger td.minus { color: var(--text-muted); }

.kc-rw-ord {
	color: var(--text-faint);
	font-variant-numeric: tabular-nums;
}

/* ===== 60-cart-page.css ===== */
/* ==========================================================================
   60 — CART PAGE  (/cart/ · page-id 824 · body.woocommerce-cart)
   Group A · shopping surface. Data first: no display face, one accent, and the
   accent is spent entirely on Proceed to Checkout.
   --------------------------------------------------------------------------
   DOM VERIFIED against staging (basic-auth curl + a local harness that serves
   the real page with the real 35 stylesheets, measured with getComputedStyle
   at 375px). The markup is a child-theme override and is NOT stock Woo — do
   not reason from woocommerce/templates/cart/cart.php:

     .woocommerce-wrap-custom-cart            flex row; column at <=768
       form.woocommerce-cart-form
         table.shop_table.shop_table_responsive.cart.woocommerce-cart-form__contents
           tbody
             tr.woocommerce-cart-form__cart-item.cart_item   <- THREE tds, not six
               td.product-thumbnail            > a > img
               td.nm-product-details           (display:flex, wraps)
                   > a                          product name
                   > ul.variation > li > div.variation-<Attr> x2   (label, value)
                   > div.product-price          <- display:none from the theme
                   > div.nm-product-quantity-pricing
                       > div.product-quantity
                           > .nm-quantity-wrap.qty-show
                               label, label.nm-qty-label-abbrev
                               .quantity > .nm-qty-minus + input.qty + .nm-qty-plus
                         (free-gift rows print a bare "1" here instead)
                       > div.product-subtotal
               td.product-remove > a.remove     (position:absolute, 16x16)
             tr > td.actions[colspan=3]
                 div.coupon                     <- visibility:hidden by Savoy
                 a#nm-cart-continue-button.button.border
                 button[name=update_cart].button.border
       .cart-collaterals
         .nm-coupon-wrap > .nm-coupon-inner
             a#nm-coupon-btn, .nm-coupon > input#nm-coupon-code + input#nm-apply-coupon-btn
         h2  "Cart totals"                      <- display:none from the theme
         .cart_totals > table.shop_table
             tr.cart-subtotal            th + td
             tr.woocommerce-shipping-totals  th(hidden) + td
                 p.nm-shipping-th-title, ul#shipping_method (display:none), p.woocommerce-shipping-destination
             tr.order-total              <- display:none !important from the theme
             tr.wc-proceed-to-checkout > td > a.checkout-button.button.alt.wc-forward
       .nm-cart-empty (empty state)      ul.nm-shop-notice, p.icon, p.return-to-shop > a.button.wc-backward

   NOT PRESENT on this cart, so styled defensively only (no markup invented):
     tr.fee (EMT / conditional-fee plugin), tr.cart-discount, tr.error.min-order,
     .shipping-calculator-form. The fee row is rendered by
     woo-conditional-product-fees-for-checkout and only materialises once a
     payment method is chosen at checkout. Nothing here computes, restates or
     annotates a fee — the rate is stated once, in the checkout payment
     description, and nowhere else.

   COMPETING SELECTORS BEATEN (file : selector : specificity)
     child/assets/css/main.css  .shop_table.cart .product-remove a
                                color:#acacac !important            (0,3,2)!
     child/assets/css/main.css  .shop_table.cart .product-remove a:hover
                                color:#AA2934  !important           (0,3,2)!
     child/assets/css/main.css  .woocommerce-cart .cart .nm-product-details a
                                font-size:16px !important           (0,3,1)!
     child/assets/css/main.css  .woocommerce-cart-form__cart-item .product-thumbnail
                                width:100% !important  @<=479       (0,3,1)!
     child/assets/css/main.css  .woocommerce-cart .cart-collaterals h2 {display:none}   (0,2,1)
     child/assets/css/main.css  .nm-coupon-inner #nm-apply-coupon-btn  bg #AA2934       (1,1,0)
     child/style.css            body.woocommerce-cart .nm-coupon-inner #nm-apply-coupon-btn
                                bg #2B2520                          (1,2,1)
     child/style.css            body.woocommerce-cart .checkout-button.button.alt.wc-forward
                                border-radius:10px !important       (0,5,0)!
     child/style.css            body.woocommerce-cart .shop_table.cart td.actions > a|button
                                bg #EFEFEF, radius 10px, width:47% !important   (0,4,3)!
     child/style.css            body.woocommerce-cart .product-thumbnail
                                background:#f2f2f2 !important       (0,3,1)!
     child/style.css            body.woocommerce-cart .cart-collaterals .shop_table
                                tbody > tr:first-child | tr.order-total  1px #E2E2E2 + radius 10px  (0,4,3)
     child/style.css            body.woocommerce-cart .shop_table.cart tr
                                border-bottom:1px solid #E2E2E2     (0,4,1)
     savoy/shop.css             .shop_table.cart .product-quantity .quantity .qty
                                width:20px (17px @<=400)            (0,3,2)
     savoy/shop.css             .shop_table.cart .nm-product-quantity-pricing .product-subtotal
                                color:#282828                       (0,3,0)
     savoy/shop.css             .shop_table.cart .nm-product-details a  color:#282828 18px  (0,3,1)

   Cascade note: kc-redesign.css (and therefore kc-parts.css) is enqueued AFTER
   savoy-child/style.css and savoy-child/assets/css/main.css, so equal weight
   already wins. Every selector below still carries body.kc-rd so the whole
   surface dies with the kill switch, and the extra classes are there to beat
   specificity rather than to reach for !important. Where !important appears it
   is beating an existing !important, and says so on the line.

   DELIBERATELY NOT CHANGED — see the report:
     tr.order-total {display:none !important}  showing a "Total" that excludes
       delivery and the e-transfer fee is a money-display decision, not a
       presentation one.
     .woocommerce-cart .product-price {display:none}  the per-unit price on a
       store running a tier-pricing plugin (CLAUDE.md §6.1 #5) may not be the
       price actually charged; un-hiding it is not a CSS call.
     .nm-shop-notice chrome — owned by the notices surface.
   ========================================================================== */


/* --------------------------------------------------------------------------
   60.0  Page-local component properties
   NOT tokens — tokens live in kc-redesign.css and this file must never define
   :root. These name a coupling between rules inside this file that would
   otherwise be the same magic number written in four places and drifting apart
   the first time one of them changes.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-cart {
	/* The remove control is 48x48 and is parked in the row's top-right corner at
	   every width. Everything that shares that corner reserves its width plus a
	   gutter, so resizing the control is one edit here, not four below.
	   Not a spacing-scale value: it is derived from a control's size. */
	--kc-cart-remove-gutter: 56px;
}


/* --------------------------------------------------------------------------
   60.1  Ground, and the two panels the page is made of
   -------------------------------------------------------------------------- */

/* kc-redesign.css paints body.kc-rd with the paper ground, but .nm-page-wrap
   lays plain white over it on every page of the site. That is a global fix and
   belongs in the shared file (flagged for the lead) — scoped to this page here
   so the white line-item cards have the ground they are designed against. If
   the global fix lands, this rule becomes a no-op rather than a conflict. */
body.kc-rd.woocommerce-cart .nm-page-wrap {
	background: var(--surface);
}

/* NOTE — a change I wrote and then reverted after measuring, recorded so the
   next person does not repeat it: between 769 and 991 the wrapper is still
   flex-direction:row while .cart-collaterals is width:100%, which reads like a
   crush waiting to happen. Measured at 900px it is not: flex shrinks the two
   children to 430 + 395 and the summary stays beside the items, which is
   better than stacking. Left alone deliberately.
   -------------------------------------------------------------------------- */

/* Savoy floats a z-index:1001 on this heading; it has no business above the
   header. Restored below as the summary's label. */
body.kc-rd.woocommerce-cart .cart-collaterals > h2 {
	display: block;
	z-index: auto;
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 500;
	letter-spacing: .10em;
	text-transform: uppercase;
	color: var(--text-muted);
	padding: 0 0 var(--kc-space-3);
	margin: 0;
}


/* --------------------------------------------------------------------------
   60.2  Line items — desktop refinements
   -------------------------------------------------------------------------- */

/* The row is the positioning context for the absolutely-placed remove control.
   It computes to relative today via an ancestor accident; pin it. */
body.kc-rd.woocommerce-cart .shop_table.cart tr.cart_item {
	position: relative;
	border-bottom: 1px solid var(--border);
	border-top: 0 none;
}
body.kc-rd.woocommerce-cart .shop_table.cart tr.cart_item > td {
	border-top: 0 none;
}

/* Product shot: stone bed per the kit, no tint. Beats the #f2f2f2 !important
   and the brightness(.95) filter the child theme paints over every thumbnail. */
body.kc-rd.woocommerce-cart .shop_table.cart .product-thumbnail,
body.kc-rd.woocommerce-cart .shop_table.cart .product-thumbnail > a {
	background: var(--card-shot-bg) !important; /* beats child (0,3,1)!important */
	border-radius: var(--kc-radius-sm);
	overflow: hidden;
}
body.kc-rd.woocommerce-cart .shop_table.cart .product-thumbnail > a > img,
body.kc-rd.woocommerce-cart .shop_table.cart .product-thumbnail > img {
	filter: none;
	display: block;
	width: 100%;
	height: 64px;
	object-fit: contain;
}

/* Product name: ink, 600. Never grey, never the display face. */
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details > a {
	font-family: var(--kc-font-body);
	font-size: 15.5px !important; /* beats .woocommerce-cart .cart .nm-product-details a 16px !important */
	font-weight: 600;
	line-height: 1.35;
	color: var(--text);
	text-decoration: none;
	/* the remove control is parked in the row's top-right corner at every
	   width; reserve its width + gutter so the two hit areas never overlap */
	padding-right: var(--kc-cart-remove-gutter);
}
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details > a:hover {
	filter: none;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* Variation / discount lines: label quiet, value full ink. */
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details ul.variation {
	width: 100%;
	margin: var(--kc-space-1) 0 0;
	padding: 0;
	list-style: none;
}
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details ul.variation li {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--kc-space-2);
	padding: 0;
	margin: 0;
}
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details ul.variation li > *:first-child {
	font-family: var(--label-font);
	font-size: 11px;
	letter-spacing: .10em;
	text-transform: uppercase;
	color: var(--text-muted);
}
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details ul.variation li > *:last-child {
	font-family: var(--price-font);
	font-size: 12.5px;
	color: var(--text);
}
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details ul.variation li p {
	margin: 0;
	padding: 0;
}

/* The discount plugin's "Free" tag. Outline, not a fill — the kit reserves
   filled chips for strain type. */
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details .awdr_free_product_text {
	display: inline-block;
	background: transparent;
	color: var(--trust);
	border: 1px solid var(--trust);
	border-radius: var(--kc-radius-sm);
	font-family: var(--label-font);
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: .10em;
	text-transform: uppercase;
	line-height: 1;
	padding: var(--kc-space-1) var(--kc-space-2);
}

/* Line money: mono, ink, heavier than the name. */
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-quantity-pricing .product-subtotal,
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-quantity-pricing .product-subtotal .amount,
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-quantity-pricing .product-subtotal bdi {
	font-family: var(--price-font);
	font-size: 17px;
	font-weight: 700;
	line-height: 1;
	color: var(--text);
	white-space: nowrap;
}

/* Quantity stepper. Savoy strips the wrap's border and squeezes the input to
   20px (17px under 400) — a two-digit quantity does not fit in it. Rebuild the
   control so every child is a real --btn-min-height (48px) touch target: the
   .quantity element carries the box (48 content + 2 border = 50px) so the "Qty"
   label can stay outside it and keep its association.
   The buttons are sized 48 and not 46: the 2px of border belongs to the parent
   and is not tappable area, so a 46px button inside a 48px box is a 46px
   target — under the kit's floor on the control a customer uses most. */
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .nm-quantity-wrap {
	display: flex;
	align-items: center;
	gap: var(--kc-space-2);
	width: auto;
	border: 0 none;
	margin: 0;
}
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .nm-quantity-wrap label {
	font-family: var(--label-font);
	font-size: 11px;
	letter-spacing: .10em;
	text-transform: uppercase;
	color: var(--text-muted);
	margin: 0;
}
/* Owner 2026-08-25 evening: compact hairline pill (32px), transparent ground —
   the 48px white box read as oversized and abrupt. Coarse pointers bump to
   38px below. Same recut as the side cart's §30.8. */
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .nm-quantity-wrap .quantity {
	display: inline-flex;
	align-items: stretch;
	margin: 0;
	background: transparent;
	border: 1px solid var(--border);
	border-radius: 999px;
	overflow: hidden;
	/* the template separates the three controls with literal &nbsp; text nodes;
	   zeroing the inherited size collapses them without touching the markup.
	   Each child re-declares its own font-size below. */
	font-size: 0;
}
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-minus.nm-font.nm-font-media-play,
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-plus.nm-font.nm-font-media-play {
	/* beats child/style.css .nm-qty-minus...{width:50px!important;height:54px!important} */
	display: flex !important;
	align-items: center;
	justify-content: center;
	width: 30px !important;
	height: 32px !important;
	font-size: 11px !important;
	line-height: 1;
	color: var(--text);
	background: transparent;
	cursor: pointer;
	user-select: none;
	/* the stepper is the one control on this page designed to be tapped twice
	   inside the double-tap window (1→2→3), which is exactly iOS Safari's
	   double-tap-zoom gesture — manipulation opts out of it (pan and pinch
	   stay intact). Tap highlight is suppressed to match the identical control
	   in the side cart (30-side-cart.css). */
	touch-action: manipulation;
	-webkit-tap-highlight-color: transparent;
}
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-minus:hover,
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-plus:hover {
	background: var(--surface-sunken);
	color: var(--text) !important; /* beats child rgba(0,0,0,.5) !important */
}

/* The marks. Savoy's font glyphs here are media-play triangles (◀ ▶ via
   .flip) — a previous/next pair, not less/more. These are the PDP's
   finalized masked-SVG marks (kc-redesign.css §27.5, ledger Batch 18d):
   geometry-centred by flex, no font metrics involved. content:"" replaces
   the icon-font character and neutralizes the .flip transform in one move. */
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-minus::before,
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-plus::before {
	content: "" !important;
	display: block;
	width: 11px;
	height: 11px;
	background: currentColor;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: contain;
	mask-size: contain;
	transform: none;
}
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-minus::before {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='7.15' width='12' height='1.7' rx='.85'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='7.15' width='12' height='1.7' rx='.85'/%3E%3C/svg%3E");
}
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-plus::before {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='7.15' width='12' height='1.7' rx='.85'/%3E%3Crect x='7.15' y='2' width='1.7' height='12' rx='.85'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='7.15' width='12' height='1.7' rx='.85'/%3E%3Crect x='7.15' y='2' width='1.7' height='12' rx='.85'/%3E%3C/svg%3E");
}

/* Digit ink correction, this surface's own number: pixel-scan of the rendered
   control at DPR2 put the marks' optical centres at 65.5/65.5 and the digit's
   at 63.0 — the digit rides 1.25 css px high (Batch 18d's phenomenon; NOT
   Batch 18d's 2.5px value, which belongs to the PDP's 17px/line-height=46px
   box). Alignment acceptance here is the ink scan, never getBoundingClientRect. */
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity input.qty {
	transform: translateY(1.25px);
}
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity input.qty {
	width: 36px;
	height: 32px;
	padding: 0;
	border: 0 none;
	background: transparent;
	font-family: var(--price-font);
	font-size: 16px;
	font-weight: 600;
	line-height: 32px;
	text-align: center;
	color: var(--text);
	-moz-appearance: textfield;
	appearance: textfield;
}
@media (pointer: coarse) {
	body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-minus.nm-font.nm-font-media-play,
	body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity .nm-qty-plus.nm-font.nm-font-media-play {
		width: 34px !important;
		height: 38px !important;
	}
	body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity input.qty {
		height: 38px;
		line-height: 38px;
	}
}
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity input.qty::-webkit-outer-spin-button,
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity input.qty::-webkit-inner-spin-button {
	-webkit-appearance: none;
	margin: 0;
}
/* Free-gift rows print a bare "1" here with no stepper — give it the same
   optical weight as the real steppers so the row does not look broken. */
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-quantity-pricing .product-quantity {
	font-family: var(--price-font);
	font-size: 16px;
	color: var(--text);
}

/* Remove: was a 16x16 target painted #acacac. Both are wrong — it is 48x48 and
   it is ink. Grey is not available for a control the customer has to hit, and
   48 is the kit's floor (--btn-min-height): this is a standalone, destructive,
   one-tap control on an 86.5%-mobile store, which is the last place to round
   a touch target down. */
body.kc-rd.woocommerce-cart .shop_table.cart td.product-remove {
	width: var(--btn-min-height);
	top: var(--kc-space-2);
	right: 0;
}
body.kc-rd.woocommerce-cart .shop_table.cart td.product-remove a.remove {
	display: flex;
	align-items: center;
	justify-content: center;
	width: var(--btn-min-height);
	height: var(--btn-min-height);
	border: 1px solid transparent;
	border-radius: var(--kc-radius-md);
	font-size: 15px;
	color: var(--text) !important; /* beats main.css #acacac !important */
	/* 48px rounded box: the UA tap highlight would paint a grey rounded
	   rectangle over the one control the comment above says must never read
	   grey — the tomato hover/focus state is the deliberate press feedback */
	-webkit-tap-highlight-color: transparent;
	transition: border-color .15s ease, color .15s ease, background-color .15s ease;
}
body.kc-rd.woocommerce-cart .shop_table.cart td.product-remove a.remove:before {
	line-height: 1;
}
body.kc-rd.woocommerce-cart .shop_table.cart td.product-remove a.remove:hover,
body.kc-rd.woocommerce-cart .shop_table.cart td.product-remove a.remove:focus-visible {
	color: var(--urgent) !important; /* beats main.css #AA2934 !important */
	border-color: var(--urgent);
	background: var(--surface);
}


/* --------------------------------------------------------------------------
   60.3  Stacked cards below 700px
   86.5% of this store's traffic is mobile. A row that keeps a thumbnail, a
   name, a stepper, money and a delete on one nowrap line at 375px is not a
   layout, it is a collision.
   -------------------------------------------------------------------------- */

@media (max-width: 700px) {

	/* Take the table out of table layout so the rows can carry margins. Every
	   row has to come with it — a leftover display:table-row inside a block
	   tbody builds an anonymous table around itself and shrink-wraps, which is
	   what strands the actions row at 137px instead of the full width. */
	body.kc-rd.woocommerce-cart form.woocommerce-cart-form table.shop_table.cart,
	body.kc-rd.woocommerce-cart form.woocommerce-cart-form table.shop_table.cart > tbody,
	body.kc-rd.woocommerce-cart form.woocommerce-cart-form table.shop_table.cart > tbody > tr:not(.cart_item) {
		display: block;
		width: 100%;
	}
	body.kc-rd.woocommerce-cart form.woocommerce-cart-form table.shop_table.cart > tbody > tr > td.actions {
		display: flex;
		width: 100%;
	}

	/* The card is a two-column grid, and the money line spans both so a wide
	   figure ("$1,240.00") never has to share 213px with a 134px stepper.
	   Reaching the money line means promoting the children of
	   td.nm-product-details into the row's grid — display:contents does that
	   without a template override. The td carries no visual style and the
	   theme already destroyed this table's semantics by making tr a flexbox,
	   so nothing is lost by removing one more box from the tree. */
	body.kc-rd.woocommerce-cart .shop_table.cart tr.cart_item {
		display: grid;
		grid-template-columns: 64px minmax(0, 1fr);
		grid-template-areas:
			"shot  name"
			"shot  meta"
			"meter meter";
		align-items: start;
		column-gap: var(--kc-space-3);
		row-gap: 0;
		margin: 0 0 var(--kc-space-3);
		padding: var(--kc-space-3);
		background: var(--card-bg);
		border: 1px solid var(--card-border);
		border-radius: var(--card-radius);
	}
	/* the last-child exemptions Savoy and the child theme apply to the old
	   divider model would otherwise strip this card's edges */
	body.kc-rd.woocommerce-cart table.shop_table.cart > tbody > tr.cart_item:last-child,
	body.kc-rd.woocommerce-cart .shop_table.cart tr.cart_item {
		border-bottom: 1px solid var(--card-border);
	}
	body.kc-rd.woocommerce-cart .shop_table.cart tr.cart_item > td,
	body.kc-rd.woocommerce-cart .shop_table.cart tr.cart_item:first-child > td,
	body.kc-rd.woocommerce-cart .shop_table.cart tr.cart_item:last-child > td {
		padding: 0;
		border: 0 none;
	}

	/* main.css pins the thumbnail to width:100% !important under 479 and the
	   child pins it to 75px !important under 768 — both fight the grid track. */
	body.kc-rd.woocommerce-cart .shop_table.cart .woocommerce-cart-form__cart-item .product-thumbnail {
		width: 64px !important; /* beats two competing !important width rules */
		grid-area: shot;
		padding: 0;
	}
	body.kc-rd.woocommerce-cart .shop_table.cart .product-thumbnail > a > img,
	body.kc-rd.woocommerce-cart .shop_table.cart .product-thumbnail > img {
		height: 64px;
	}

	body.kc-rd.woocommerce-cart .shop_table.cart tr.cart_item > td.nm-product-details {
		display: contents;
	}
	/* keep the name and the meta line clear of the remove control parked in the
	   card's top-right corner */
	body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details > a {
		grid-area: name;
		display: block;
		width: auto !important; /* beats @<=1200 width:100%!important / @<=1199 calc() */
		min-width: 0;
		padding-right: var(--kc-cart-remove-gutter);
		font-size: 15px !important;
	}
	body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details > ul.variation {
		grid-area: meta;
		width: auto;
		padding-right: var(--kc-cart-remove-gutter);
		margin-top: var(--kc-space-2);
	}

	body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-quantity-pricing {
		grid-area: meter;
		display: flex;
		flex-direction: row;
		flex-wrap: wrap;
		align-items: center;
		justify-content: space-between;
		gap: var(--kc-space-3);
		width: auto !important; /* beats child width:100%!important + padding-right:0!important */
		padding: 0 !important;
		/* the theme hangs a -16px right margin on this block; left in place it
		   pushes the line subtotal 16px past the card's right edge */
		margin: var(--kc-space-3) 0 0;
		line-height: 1;
	}
	body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-quantity-pricing .product-quantity {
		width: auto;
		flex: 0 0 auto;
	}
	body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-quantity-pricing .product-subtotal {
		width: auto;
		flex: 0 0 auto;
		margin-left: auto;
		padding-top: 0;
		font-size: 17px !important; /* beats @<=400 {font-size:inherit} and the 18px mobile rule */
		text-align: right;
	}
	/* under 400 Savoy hides the abbreviated label and shrinks the field; the
	   rebuilt control keeps its size, so put the label back */
	body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .nm-quantity-wrap label.nm-qty-label-abbrev {
		display: inline-block;
	}

	/* the control grew from 44 to 48, so the inset drops from 8 to 4 and its
	   inner edge lands exactly where it did before — the gutter reservation on
	   the name and the meta line is unchanged by the resize. */
	body.kc-rd.woocommerce-cart .shop_table.cart td.product-remove {
		top: var(--kc-space-2);
		right: var(--kc-space-1);
	}
}

/* REMOVED — a @media (max-width:359px) block that hid .nm-quantity-wrap label,
   recorded so nobody re-adds it. It never fired: it computed to (0,6,2) while
   this file's own rule above, label.nm-qty-label-abbrev {display:inline-block},
   is (0,7,2) and its @media (max-width:700px) is equally in effect at 359px.
   Media queries add no specificity, so the abbreviated label stayed visible and
   the block's only reachable target was the non-abbreviated label, which is
   already hidden. Deleted rather than made to bite, for two reasons: the 320px
   measurement shows the stepper and the subtotal fit with the label present, so
   nothing needed hiding; and both labels going display:none would strip the
   quantity input of its accessible name at the narrowest widths. */


/* --------------------------------------------------------------------------
   60.4  Actions row — Continue Shopping only
   "Update cart" is retired from view, not from the DOM: the wooajaxcart
   plugin auto-submits it on every quantity change (measured: stepper click →
   totals updated, no button press), so a visible button is a control that
   asks the customer to do work the page already does. The plugin fires it
   with a programmatic .trigger('click'), which works on a display:none
   element — the button must stay in the markup, merely unseen. Its "busy"
   affordance survives: during the update WC blocks the form (blockUI) and
   wooajaxcart swaps the checkout button label to "Updating...".
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-cart .shop_table.cart td.actions {
	display: flex;
	flex-direction: row;
	align-items: center;
	justify-content: flex-start;
	gap: var(--kc-space-3);
	padding-top: var(--kc-space-5);
	border: 0 none;
}

body.kc-rd.woocommerce-cart .shop_table.cart td.actions > button[name="update_cart"] {
	display: none;
}

body.kc-rd.woocommerce-cart .shop_table.cart td.actions > a#nm-cart-continue-button {
	order: 0;
	width: auto !important;   /* beats child width:47% !important */
	min-height: var(--btn-min-height);
	display: inline-flex;
	align-items: center;
	padding: 0;
	background: transparent;
	color: var(--text);
	border: 0 none;
	border-radius: 0;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	text-decoration: underline;
	text-underline-offset: 4px;
	filter: none; /* .nm-cart-continue-button gets contrast(.3) from the child */
}
body.kc-rd.woocommerce-cart .shop_table.cart td.actions > a#nm-cart-continue-button:hover {
	background: transparent;
	color: var(--text);
	text-decoration-thickness: 2px;
}

@media (max-width: 700px) {
	body.kc-rd.woocommerce-cart .shop_table.cart td.actions {
		flex-direction: column;
		align-items: stretch;
		gap: var(--kc-space-3);
		padding-top: var(--kc-space-4);
	}
	body.kc-rd.woocommerce-cart .shop_table.cart td.actions > a#nm-cart-continue-button {
		justify-content: center;
	}
}


/* --------------------------------------------------------------------------
   60.5  Coupon — a quiet disclosure BELOW the money panel
   In the served DOM the coupon block sits at the very top of the right
   column, above CART TOTALS — the single most prominent slot on the page,
   spent on the 2% case. The column is already a flex context candidate:
   ordering totals first and the coupon after costs no markup. Card chrome
   removed with the demotion — a bordered box at full column width still
   reads as "a second panel"; a text disclosure reads as the aside it is.
   Proceed to Checkout stays the page's only amber.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-cart .cart-collaterals {
	display: flex;
	flex-direction: column;
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals { order: 1; }
body.kc-rd.woocommerce-cart .cart-collaterals .nm-coupon-wrap { order: 2; }

body.kc-rd.woocommerce-cart .cart-collaterals .nm-coupon-wrap {
	background: transparent;
	border: 0 none;
	border-radius: 0;
	margin: var(--kc-space-3) 0 0;
}
body.kc-rd.woocommerce-cart .cart-collaterals .nm-coupon-wrap .nm-coupon-inner {
	background: transparent !important; /* beats main.css background-color:transparent!important chain */
	padding: 0 var(--kc-space-2) !important; /* beats the child's 20px !important pair */
}

body.kc-rd.woocommerce-cart .nm-coupon-wrap .nm-coupon-inner #nm-coupon-btn {
	display: flex;
	align-items: center;
	min-height: var(--btn-min-height); /* quiet, but still a 48px target */
	padding-left: 0;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 500;
	color: var(--text-muted);
	text-decoration: underline;
	text-underline-offset: 3px;
}
body.kc-rd.woocommerce-cart .nm-coupon-wrap .nm-coupon-inner #nm-coupon-btn:hover {
	color: var(--text);
}
/* The child theme hangs a remote SVG coupon icon off :after. It is decoration
   inside a cart panel (the kit's never-list) and it is a cross-origin request
   to the production domain from staging. */
body.kc-rd.woocommerce-cart .nm-coupon-wrap .nm-coupon-inner #nm-coupon-btn:after {
	content: none;
}
/* Savoy pins the chevron at top:3px; the disclosure is now 48px tall, so it was
   floating above the label. Centre it without disturbing the child theme's
   rotate() — margin, not transform. */
body.kc-rd.woocommerce-cart .nm-coupon-wrap .nm-coupon-inner #nm-coupon-btn:before {
	border-color: currentColor; /* follows the demoted muted ink, darkens on hover with it */
	top: 50%;
	right: 2px;
	margin-top: -7px;
}

body.kc-rd.woocommerce-cart .nm-coupon-inner #nm-coupon-code {
	width: 100%;
	height: var(--field-height);
	padding: 0 var(--kc-space-3) !important; /* beats main.css padding:0 15px !important */
	background: var(--field-bg);
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
	font-family: var(--price-font);
	/* 16px is a hard floor on every focusable text input, not a type choice:
	   iOS Safari zooms the viewport when a focused field computes under 16px,
	   and the customer has to pinch back out mid-cart. This is the only
	   free-text field on the page. The tracking is pulled back from .04em to
	   compensate for the extra width — it is uppercase mono, so it still reads
	   as a code field. */
	font-size: 16px;
	letter-spacing: .02em;
	text-transform: uppercase;
	color: var(--text);
}

/* Applying a coupon is a secondary action. It was crimson, then near-black —
   both read as "the button on this page". */
body.kc-rd.woocommerce-cart .nm-coupon-inner #nm-apply-coupon-btn {
	width: 100% !important; /* beats main.css width:100% !important */
	height: var(--btn-min-height);
	margin: var(--kc-space-3) 0 0;
	padding: 0;
	background: transparent;
	color: var(--text);
	border: 2px solid var(--text);
	border-radius: var(--kc-radius-md);
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	text-transform: none;
	transition: background-color .15s ease;
}
body.kc-rd.woocommerce-cart .nm-coupon-inner #nm-apply-coupon-btn:hover {
	background: var(--surface-sunken);
	color: var(--text) !important; /* beats main.css color:#fff !important on hover */
}


/* --------------------------------------------------------------------------
   60.6  Cart totals — the money panel
   Stone panel against the paper ground. Every figure is mono and ink; nothing
   here is generated, restated or recomputed by CSS.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table {
	background: var(--surface-sunken);
	border: 1px solid var(--border);
	border-radius: var(--card-radius);
	overflow: hidden;
}
/* The child theme draws the panel edge per-row (top radius on :first-child,
   bottom radius on tr.order-total, sides on :not(:last-child)) — which falls
   apart the moment a fee or discount row appears, and leaves a stray rounded
   corner on a hidden row. The panel owns its own edges now. */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr {
	border: 0 none;
	border-radius: 0;
	border-bottom: 1px solid var(--border);
	padding: 0;
	align-items: baseline;
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr:last-child {
	border-bottom: 0 none;
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr > th,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr > td {
	padding: var(--kc-space-4);
	background: transparent;
	border: 0 none;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 500;
	color: var(--text);
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr > td {
	text-align: right;
}

/* Every money figure in the panel: mono, ink, never lighter than the label. */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table .amount,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table .amount bdi,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table .woocommerce-Price-currencySymbol {
	font-family: var(--price-font);
	font-weight: 700;
	color: var(--text);
}

/* Subtotal is the loudest number the customer can actually see on this page —
   the theme hides tr.order-total (see the header note). */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.cart-subtotal > td,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.cart-subtotal > td .amount,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.cart-subtotal > td bdi {
	font-size: 20px;
	line-height: 1.1;
}
/* And if the store ever reveals the total row, it takes the crown without a
   second edit. This does not touch its display property. */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.order-total > th {
	font-weight: 600;
	font-size: 16px;
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.order-total > td,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.order-total > td strong,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.order-total > td .amount,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.order-total > td bdi {
	font-size: 24px;
	font-weight: 700;
	line-height: 1.05;
	color: var(--text);
}

/* Fee row (the conditional-fee plugin renders the e-transfer line here when it
   applies) and coupon-discount row. Styled to match the rest of the panel and
   nothing more — the label and the figure are whatever the store printed. */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.fee > th,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.cart-discount > th {
	font-weight: 500;
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.fee > td,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.fee > td .amount,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.cart-discount > td,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.cart-discount > td .amount {
	font-size: 16px;
	color: var(--text);
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.cart-discount td a {
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	font-weight: 600;
	color: var(--trust);
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* Shipping row. The theme hides the <th> and the method list and leaves one
   label plus one sentence occupying 145px — compact it and keep both readable
   at full ink (delivery availability is decision information). */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.woocommerce-shipping-totals > td {
	width: 100%;
	text-align: left;
	padding-top: var(--kc-space-4);
	padding-bottom: var(--kc-space-4);
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody .nm-shipping-th-title {
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 500;
	letter-spacing: .10em;
	text-transform: uppercase;
	color: var(--text-muted);
	padding: 0 0 var(--kc-space-2);
	margin: 0;
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .woocommerce-shipping-destination {
	padding: 0;
	margin: 0;
	font-size: 13.5px;
	line-height: 1.5;
	color: var(--text);
}
/* If the store ever un-hides #shipping_method, the options are already sized. */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals #shipping_method li label {
	display: inline-flex;
	align-items: center;
	min-height: var(--btn-min-height);
	font-size: 15px;
	color: var(--text);
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals #shipping_method .amount {
	font-family: var(--price-font);
	font-weight: 700;
	color: var(--text);
	margin-left: var(--kc-space-2);
}

/* Minimum-order error row (injected by the delivery-city script). Tomato is the
   error role, but it is spent on the border only — see the colour note below.
   The row is never grey and never a filled block of colour.

   ⚠️ CONTRAST — the sentence is --text, not --urgent, and that is deliberate.
   --urgent (via --kc-tomato-600) on the --surface ground this rule sets is
   4.14:1 — under the 4.5:1 floor, and the 14px below is not "large text"
   (large = 18.66px bold / 24px) so 4.5 is the threshold that applies.
   The theme got away with #EB5757 because it sat on a 15% tomato wash; this
   file changed the ground to cream, which is what broke it. --text on cream is
   16.69:1. Enlarging the sentence to clear the large-text exemption is not the
   fix — it would make the error louder than the subtotal.
   A darker --urgent-text role would also work, but tokens live in
   kc-redesign.css and this partial may not define :root, so it is not this
   file's call to make. Flagged for the lead.

   ⚠️ SELECTOR REACH — the declarations are repeated on a descendant p below.
   main.css carries `.error.min-order p {font-size:18px; font-weight:400;
   color:#EB5757}`, which matches the message paragraph *directly*; a directly
   matched rule beats an inherited value no matter how specific the ancestor
   that set it, so styling the td alone leaves every typographic declaration
   here dead. The row is not on the live cart to inspect, so both are declared:
   whichever box the plugin puts the sentence in is covered, and the pair is
   harmless if there is no p. */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.error.min-order > td,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.error.min-order > td p {
	color: var(--text);
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	line-height: 1.45;
	text-align: center !important;
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.error.min-order > td {
	background: var(--surface) !important;  /* beats main.css rgba(235,87,87,.15) !important */
	border: 1px solid var(--urgent) !important; /* beats main.css #EB5757 !important */
	border-radius: var(--kc-radius-md);
	padding: var(--kc-space-3) var(--kc-space-4);
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.error.min-order > td p {
	margin: 0;
	padding: 0;
}
/* the threshold figure inside the message is part of the message, not a
   separate money element — it takes the sentence's colour */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.error.min-order > td .amount,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.error.min-order > td .amount bdi,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.error.min-order > td .woocommerce-Price-currencySymbol {
	color: var(--text);
}

/* Proceed to Checkout — the one primary action on this view. The amber fill,
   ink border and hard shadow come from kc-redesign.css §19.7 (lead-owned);
   this only fixes what that rule does not reach: the child theme's 10px radius
   !important, its 18px/500 type, the touch floor, and the missing focus ring. */
/* The child theme nudges this row with padding-left/right:4px !important inside
   its <=768 query, which knocks the CTA off the panel's gutter. */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .wc-proceed-to-checkout {
	padding: var(--kc-space-4) !important; /* beats two 4px !important nudges */
	border-bottom: 0 none;
}
/* carries .shop_table tbody so it outweighs this file's own generic
   "every cell in the panel gets 16px" rule above */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .shop_table tbody > tr.wc-proceed-to-checkout > td {
	padding: 0;
	width: 100%;
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .wc-proceed-to-checkout .checkout-button {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	min-height: var(--btn-min-height);
	height: auto;
	padding: var(--kc-space-3) var(--kc-space-5);
	border-radius: var(--kc-radius-md) !important; /* beats child radius:10px !important */
	font-family: var(--kc-font-body);
	font-size: 16px;
	font-weight: 700;
	line-height: 1.25;
	text-transform: none;
}
/* Disabled checkout (under the city minimum): quiet, but still legible. */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .wc-proceed-to-checkout .checkout-button.disabled,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .wc-proceed-to-checkout .checkout-button[disabled] {
	background: var(--surface-sunken) !important; /* beats §19.7's amber !important */
	color: var(--text-muted) !important;
	border-color: var(--border-strong) !important;
	box-shadow: none;
	cursor: default;
}


/* --------------------------------------------------------------------------
   60.7  Empty cart
   Layout and the return CTA only — .nm-shop-notice chrome belongs to the
   notices surface and is left untouched here on purpose.
   -------------------------------------------------------------------------- */

/* Template order is: stale error notice, icon, Return-to-shop, notices wrapper,
   "your cart is empty" message. Display order has to be message then button —
   flex `order` does that without touching the markup. */
body.kc-rd.woocommerce-cart .nm-cart-empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	margin: var(--kc-space-7) 0;
	color: var(--text);
}
/* Reading order: the sentence, a way to search, the primary way out, one
   shelf entrance. The X glyph goes entirely — an error icon over a neutral
   state ("your cart is empty" is not a failure) was the walkthrough's S7
   dead-end reading in one image. */
body.kc-rd.woocommerce-cart .nm-cart-empty > p.icon { display: none; }
body.kc-rd.woocommerce-cart .nm-cart-empty > .wc-empty-cart-message { order: 1; }
body.kc-rd.woocommerce-cart .nm-cart-empty > .kc-cart-empty-search { order: 2; }
body.kc-rd.woocommerce-cart .nm-cart-empty > p.return-to-shop { order: 3; }
body.kc-rd.woocommerce-cart .nm-cart-empty > .kc-cart-empty-links { order: 4; }
body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop {
	display: block;
	margin-left: 0;
}
/* Savoy blanket-hides every .nm-shop-notice inside .nm-cart-empty, which takes
   "Your cart is currently empty." with it — the page currently renders a
   heading, a missing icon glyph and a button, with no sentence anywhere.
   Revealed here for the empty-cart message only; the stale minimum-order
   notice that also sits in this container stays hidden, and the notice's own
   chrome (colour, icon, padding) is left to the notices surface. */
body.kc-rd.woocommerce-cart .nm-cart-empty .wc-empty-cart-message .nm-shop-notice {
	display: block;
	margin: 0 0 var(--kc-space-5);
	padding: 0;
	font-size: 16px;
	line-height: 1.5;
}
body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop {
	margin-top: var(--kc-space-5);
}
body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop:hover {
	filter: none; /* the child theme dims the only way out of this page */
}
body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop a.button.wc-backward {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--btn-min-height);
	padding: var(--kc-space-3) var(--kc-space-6);
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border: 2px solid var(--btn-primary-border);
	border-radius: var(--kc-radius-md);
	box-shadow: var(--btn-primary-shadow);
	font-family: var(--kc-font-body);
	font-size: 16px;
	font-weight: 700;
	text-transform: none;
	transition: transform .12s ease, box-shadow .12s ease;
}
body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop a.button.wc-backward:hover {
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	transform: translate(1px, 1px);
	box-shadow: 2px 2px 0 var(--text);
}
body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop a.button.wc-backward:active {
	transform: translate(3px, 3px);
	box-shadow: 0 0 0 var(--text);
}


/* --------------------------------------------------------------------------
   60.8  Focus — visible on everything on this page, per the kit's state table
   (3px --trust ring, offset 3). Savoy ships no focus style at all here.
   -------------------------------------------------------------------------- */

/* the two per-line-item links (thumbnail, product name) match none of the
   scoped focus lists in the kit and the theme's `* { outline: 0 none }` kills
   the UA ring, so without these a keyboard user tabs the line items blind.
   The tr.cart-discount coupon-removal link is deliberately NOT here —
   20-buttons.css already carries .woocommerce-remove-coupon:focus-visible. */
body.kc-rd.woocommerce-cart .shop_table.cart .product-thumbnail > a:focus-visible,
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details > a:focus-visible,
body.kc-rd.woocommerce-cart .shop_table.cart td.product-remove a.remove:focus-visible,
body.kc-rd.woocommerce-cart .shop_table.cart td.actions > a#nm-cart-continue-button:focus-visible,
body.kc-rd.woocommerce-cart .shop_table.cart td.actions > button:focus-visible,
body.kc-rd.woocommerce-cart .nm-coupon-wrap .nm-coupon-inner #nm-coupon-btn:focus-visible,
body.kc-rd.woocommerce-cart .nm-coupon-inner #nm-coupon-code:focus-visible,
body.kc-rd.woocommerce-cart .nm-coupon-inner #nm-apply-coupon-btn:focus-visible,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals #shipping_method input:focus-visible + label,
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .wc-proceed-to-checkout .checkout-button:focus-visible,
body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop a.button.wc-backward:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}
/* The quantity field is the one exception to the list above, and it has to be:
   input.qty CANNOT carry the ring. Its parent .quantity is overflow:hidden (it
   has to be — that is what clips the three children to --field-radius), and an
   ancestor's overflow:hidden clips descendant painting, outlines included.
   The .quantity padding box is exactly the input's height, so a ring drawn 3px
   outside the input's border box is clipped away top and bottom completely and
   the left/right segments survive as two stray bars over the +/- buttons.
   Ring goes on .quantity, which is also the box the customer perceives as the
   field. :focus-within, not :focus-visible — the state belongs to the group,
   and a text input matches :focus-visible on pointer focus anyway, so nothing
   is lost. Verify this one by tabbing to it, not by reading a computed style
   off a different field that has no clipping ancestor. */
body.kc-rd.woocommerce-cart .shop_table.cart .product-quantity .quantity:focus-within {
	border-color: var(--text);
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}


/* --------------------------------------------------------------------------
   60.9  Reduced motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	body.kc-rd.woocommerce-cart .shop_table.cart td.product-remove a.remove,
	body.kc-rd.woocommerce-cart .shop_table.cart td.actions > a#nm-cart-continue-button,
	body.kc-rd.woocommerce-cart .shop_table.cart td.actions > button,
	body.kc-rd.woocommerce-cart .nm-coupon-inner #nm-apply-coupon-btn,
	body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .wc-proceed-to-checkout .checkout-button,
	body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop a.button.wc-backward {
		transition: none !important; /* beats the theme's 0.3s ease !important transitions */
	}
	body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop a.button.wc-backward:hover,
	body.kc-rd.woocommerce-cart .nm-cart-empty p.return-to-shop a.button.wc-backward:active {
		transform: none;
	}
}


/* --------------------------------------------------------------------------
   60.10  Page rhythm — the dead band under "Cart"
   Measured at 375px: title bottom → first line item was ~200px of nothing,
   three separate causes, each killed at its own root:

   1) .gift_block_custom-wrapper — previous developer's "Choose a Free Gift!"
      picker shell (savoy-child/page.php:11, is_cart||is_checkout). When the
      picker renders nothing (its usual state: the discount-rules gift is
      auto-added), the wrapper stays as an EMPTY div carrying margin-bottom:
      60px (style.css:5199). Collapsed only while :empty — if the picker's
      AJAX ever fills it, the margin comes back with the content.
   2) .woocommerce-notices-wrapper — 80-notices.css §? reserves a 107px
      notice slot on every cart load to absorb AJAX notices without layout
      shift. On this page that is a permanent 107px blank at the top for a
      notice that appears on a minority of interactions; the trade is
      reversed (dispatch P1: the dead space costs more than the shift).
      Beaten at (0,5,1) vs the reservation's (0,4,1) — cascade order is
      irrelevant at unequal specificity, no !important. Flagged in the
      report for the orchestrator to resolve with the notices surface.
   3) The VC "Cart" heading's 35px stock margin, trimmed to the token gap.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-cart .gift_block_custom-wrapper:empty {
	margin-bottom: 0;
	padding: 0 !important; /* beats the wrapper's own 19%/15px !important pads */
}

body.kc-rd.woocommerce-cart.woocommerce-page .woocommerce > .woocommerce-notices-wrapper {
	min-height: 0;
}

body.kc-rd.woocommerce-cart h2.vc_custom_heading {
	margin-bottom: var(--kc-space-3);
}


/* --------------------------------------------------------------------------
   60.11  Money panel header: minimum-order warning + free-delivery bar
   Both render server-side at woocommerce_before_cart_totals — inside the
   .cart_totals node WooCommerce cart.js replaces on every quantity/coupon
   change, so both refresh with the money they describe.
   -------------------------------------------------------------------------- */

/* Gift-tier milestone ladder (.kc-ladder) — same builder as the side cart
   (inc-cart-panel.php); here it sits as its own soft panel directly above the
   totals table. CONTAINER BOX ONLY: every inner element (head, track, rungs,
   animation) is styled once in 66-track.css §66.5 — the shared component
   layer — and a page-scoped copy here would silently outrank it. The old
   unscoped .kc-threshold base in kc-redesign.css §6 no longer matches any
   markup and is on the orchestrator delete list. */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .kc-ladder {
	margin: 0 0 var(--kc-space-3);
	padding: var(--kc-space-4);
	background: var(--surface-sunken);
	border: 1px solid var(--border);
	border-radius: var(--card-radius);
}

/* Value tally right under the ladder (shared component, 66-track.css §66.5). */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .kc-gift-tally {
	margin: 0 0 var(--kc-space-3);
}

/* Below-minimum warning. Urgent-accented but quiet: it is information the
   customer needs before checkout refuses them, not an alarm. Renders nothing
   until the minimum-order engine actually carries a minimum (see
   inc-cart-page.php — no invented numbers). */
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .kc-minorder {
	margin: 0 0 var(--kc-space-3);
	padding: var(--kc-space-3) var(--kc-space-4);
	background: var(--surface);
	border: 1px solid var(--urgent);
	border-left-width: 4px;
	border-radius: var(--card-radius);
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 500;
	line-height: 1.4;
	color: var(--text);
}
body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .kc-minorder .amount {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
}


/* --------------------------------------------------------------------------
   60.12  Line-item facts: engine unit price + the free-gift tag
   -------------------------------------------------------------------------- */

/* "2 × $40.00" (inc-cart-page.php; qty ≥ 2 only — at qty 1 it would restate
   the row's subtotal, and a price appears once per place). */
body.kc-rd.woocommerce-cart .shop_table.cart .nm-product-details .kc-line-unit {
	margin: 2px 0 0;
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-size: 12px;
	font-weight: 500;
	line-height: 1.4;
	color: var(--text-muted);
}

/* woo-discount-rules' free-gift tag ("DISCOUNT: FREE"). The plugin ships it
   as a filled chip on its own blue — a sixth hue on this page. Same decision
   as the side cart (30-side-cart.css §30.6): plain pine text, weight for
   emphasis. Pine is the trust role, and "FREE" is a confirmation, not a
   control — no border, no fill, nothing that reads as tappable. */
body.kc-rd.woocommerce-cart .shop_table.cart .variation .awdr_free_product_text {
	display: inline;
	padding: 0;
	font-weight: 700;
	color: var(--trust);
	background: none;
	border: 0 none;
	border-radius: 0;
}


/* --------------------------------------------------------------------------
   60.13  Empty cart: search + one shelf entrance (walkthrough S7)
   The FiboSearch form ships its own skin (70-search.css owns that); this
   only sizes and places the block.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-cart .nm-cart-empty .kc-cart-empty-search {
	width: min(420px, 100%);
	margin: 0 0 var(--kc-space-2);
}

/* Desktop measured the FiboSearch form collapsed to a 73px box inside this
   context (form 73px, .dgwt-wcas-sf-wrapp 0px, input 59px — the header
   instance's icon-mode sizing leaking into a bare shortcode render; the
   phone layout was unaffected). Pin the whole chain to the wrapper's width.
   !important: the plugin writes its layout widths with !important. */
body.kc-rd.woocommerce-cart .nm-cart-empty .kc-cart-empty-search .dgwt-wcas-search-wrapp,
body.kc-rd.woocommerce-cart .nm-cart-empty .kc-cart-empty-search form.dgwt-wcas-search-form,
body.kc-rd.woocommerce-cart .nm-cart-empty .kc-cart-empty-search .dgwt-wcas-sf-wrapp {
	width: 100% !important;
	max-width: none !important;
}
body.kc-rd.woocommerce-cart .nm-cart-empty .kc-cart-empty-search .dgwt-wcas-search-input {
	width: 100% !important;
	min-height: var(--btn-min-height);
}

/* Plain-form fallback when FiboSearch is unavailable (inc-cart-page.php). */
body.kc-rd.woocommerce-cart .nm-cart-empty .kc-cart-empty-search-fallback {
	display: flex;
	gap: var(--kc-space-2);
}
body.kc-rd.woocommerce-cart .nm-cart-empty .kc-cart-empty-search-fallback input[type="search"] {
	flex: 1 1 auto;
	height: var(--field-height);
	padding: 0 var(--kc-space-3);
	background: var(--field-bg);
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
	font-family: var(--kc-font-body);
	font-size: 16px; /* the iOS zoom floor, same as the coupon field */
	color: var(--text);
}
body.kc-rd.woocommerce-cart .nm-cart-empty .kc-cart-empty-search-fallback button {
	flex: 0 0 auto;
	min-height: var(--btn-min-height);
	padding: 0 var(--kc-space-4);
	background: transparent;
	color: var(--text);
	border: 2px solid var(--text);
	border-radius: var(--kc-radius-md);
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	cursor: pointer;
}

body.kc-rd.woocommerce-cart .nm-cart-empty .kc-cart-empty-links {
	margin: var(--kc-space-4) 0 0;
}
body.kc-rd.woocommerce-cart .nm-cart-empty a.kc-cart-empty-link {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 4px;
}
body.kc-rd.woocommerce-cart .nm-cart-empty a.kc-cart-empty-link:hover {
	text-decoration-thickness: 2px;
}

/* --------------------------------------------------------------------------
   60.13  Ladder, phone form
   Container padding only — the v3 component (66-track.css §66.5) is sized
   for a 375px frame already (the approved mockup's own width), so the phone
   branch must not shrink track/dot geometry out from under it.
   -------------------------------------------------------------------------- */
@media (max-width: 700px) {
	body.kc-rd.woocommerce-cart .cart-collaterals .cart_totals .kc-ladder {
		padding-top: 10px;
		padding-bottom: 10px;
	}
}


/* --------------------------------------------------------------------------
   60.x  Gift row (cart page) — same lockdown face as the panel (30.19)
   wdr already renders the quantity as plain text here; the FREE badge lands
   in the Subtotal column via woocommerce_cart_item_subtotal, the unit-price
   cell is emptied server-side, and the redundant "Discount: Free" chip
   hides. One money statement per row.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-cart .woocommerce-cart-form tr.kc-gift-row .variation {
	display: none;
}

/* On this surface the name filter's output lands AFTER the </a> (cart.php
   pipes the whole anchor through woocommerce_cart_item_name), so the note is
   a direct flex item of td.nm-product-details — full-basis makes it take its
   own row under the name instead of sitting beside it. */
body.kc-rd.woocommerce-cart .woocommerce-cart-form tr.kc-gift-row .kc-gift-note {
	flex: 0 0 100%;
}

body.kc-rd.woocommerce-cart .woocommerce-cart-form tr.kc-gift-row .kc-gift-free {
	display: inline-block;
	padding: 2px 8px;
	font-family: var(--kc-font-body);
	font-weight: 700;
	font-size: 12.5px;
	letter-spacing: .05em;
	line-height: 1.4;
	color: var(--trust);
	background: var(--surface-sunken);
	border: 1px solid var(--trust);
	border-radius: 999px;
}


/* --------------------------------------------------------------------------
   60.14  Ghost row — cart-page shell for the shared component
   The locked next-tier package renders as one <td colspan=3> row at the end
   of the items table (woocommerce_cart_contents). Inner markup
   (.kc-ghost-inner) is built in inc-cart-panel.php and styled once in
   66-track.css §66.5; this section owns only how the shell sits in the
   table's rhythm.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-cart .woocommerce-cart-form tr.kc-ghost-row > td {
	padding: var(--kc-space-3) 0;
	border-bottom: 1px solid var(--border);
}

/* ===== 65-checkout.css ===== */
/* ==========================================================================
   65-checkout.css — SURFACE: checkout page (Group A, money path)
   --------------------------------------------------------------------------
   Owns checkout-page LAYOUT and checkout-only components: the two-column
   sticky order rail, form group headings, the login/coupon demotion, the
   payment-card selected state, the minimum-order gate panel, and the
   checkout error presentation. The form CONTROL vocabulary (inputs, labels,
   invalid borders, focus rings, required markers) belongs to 10-forms.css
   and is not restated here. Buttons stay in kc-redesign.css §2/§19.7.

   DOM VERIFIED (staging /checkout/, 2026-08-23, live fetch not assumption):
     form.checkout
       > ul.nm-checkout-login-coupon > li > .woocommerce-info   (login/coupon)
       > .checkout-main-form-wrapper-custom          (child-theme wrapper)
           > #customer_details.col2-set > .col-1 (billing) + .col-2 (shipping
             toggle + order notes)
           > #order_review  (h3#order_review_heading + review table +
             #payment via woocommerce_checkout_order_review)
     Payment: ul.wc_payment_methods > li.wc_payment_method > input + label
     (kc-redesign.css §7 already carded them; this file upgrades the states).
     Gate branch: div.error-message (savoy/woocommerce/checkout/cart-errors.php)
     containing two <p> (generic text + Return-to-cart button) around our
     .kc-gate rendered on woocommerce_cart_has_errors.

   STICKY PREREQUISITE (ledger Batch 18, same trap): .nm-page-wrap ships
   overflow:hidden, which makes it the sticky viewport and sticky never
   engages. Open it on checkout only, swallow the theme's 15px offscreen-menu
   overhang with overflow-x:clip (clip does NOT create a scroll container;
   hidden kills sticky).
   ========================================================================== */

/* --------------------------------------------------------------------------
   §65.1 Two-column layout with sticky order rail (desktop only)
   -------------------------------------------------------------------------- */
@media (min-width: 1025px) {
	html:has(body.kc-rd.woocommerce-checkout) { overflow-x: clip; }
	body.kc-rd.woocommerce-checkout { overflow-x: clip; }
	body.kc-rd.woocommerce-checkout .nm-page-wrap { overflow: visible; }

	/* Three grid children (inc-checkout.php §8 relocates payment out of the
	   rail): #customer_details -> col 1 row 1, #kc-payment-col -> col 1
	   row 2, #order_review -> col 2 spanning both = the summary rail. The
	   rail is short (summary only), so it needs no internal scrolling. */
	body.kc-rd.woocommerce-checkout form.checkout .checkout-main-form-wrapper-custom {
		display: grid;
		grid-template-columns: minmax(0, 1fr) 420px;
		grid-template-rows: auto 1fr;
		column-gap: var(--kc-space-7);
		align-items: start;
	}
	body.kc-rd.woocommerce-checkout form.checkout #customer_details { grid-column: 1; grid-row: 1; }
	body.kc-rd.woocommerce-checkout form.checkout #kc-payment-col { grid-column: 1; grid-row: 2; }
	body.kc-rd.woocommerce-checkout form.checkout #order_review {
		grid-column: 2;
		grid-row: 1 / span 2;
		position: sticky;
		/* v5 shell: the site header is gone; the 56px mini header
		   (69-checkout-shell.css --kc-co-head-h) + a 16px seam. The fallback
		   120 is the pre-shell value (header-on-scroll bottomed at 110). */
		top: calc(var(--kc-co-head-h, 104px) + 16px);
	}

	/* Savoy floats billing (.col-1) and shipping (.col-2) side by side.
	   Inside a 1fr column that produces two cramped half-columns — stack
	   them full width instead. */
	body.kc-rd.woocommerce-checkout #customer_details .col-1,
	body.kc-rd.woocommerce-checkout #customer_details .col-2 {
		float: none;
		width: 100%;
	}
}

/* The order rail reads as one quiet card at every width. */
body.kc-rd.woocommerce-checkout #order_review {
	background: var(--surface-raised);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-lg);
	padding: var(--kc-space-5);
}
body.kc-rd.woocommerce-checkout #order_review_heading {
	margin: 0 0 var(--kc-space-4);
	font-family: var(--kc-font-body);
	font-size: 18px;
	font-weight: 700;
	color: var(--text);
}

/* --------------------------------------------------------------------------
   §65.2 Form group headings (Contact / Delivery address / Delivery
   preferences — rendered by inc-checkout.php's kc_heading fields)
   -------------------------------------------------------------------------- */
body.kc-rd.woocommerce-checkout .kc-form-heading {
	width: 100%;
	clear: both;
	margin: var(--kc-space-6) 0 var(--kc-space-2);
	padding: 0;
}
body.kc-rd.woocommerce-checkout #billing_kc_h_contact_field { margin-top: 0; }
body.kc-rd.woocommerce-checkout .kc-form-heading h3 {
	margin: 0;
	font-family: var(--kc-font-body);
	font-size: 18px;
	font-weight: 700;
	color: var(--text);
}
/* (v5) The legacy "Local Delivery - …" fields and their "Delivery
   preferences" heading no longer render (inc-checkout §1 unsets them); the
   structured options block in inc-checkout-fields.php carries the heading. */

/* --------------------------------------------------------------------------
   §65.3 Login / coupon rows: red shouting -> one quiet grey line each.
   Red is the error colour and a coupon prompt is a discount hunt invitation;
   the toggles stay (WC core .showlogin/.showcoupon still bound).
   -------------------------------------------------------------------------- */
body.kc-rd.woocommerce-checkout .nm-checkout-login-coupon {
	margin: 0 0 var(--kc-space-4);
	padding: 0;
	list-style: none;
}
body.kc-rd.woocommerce-checkout .nm-checkout-login-coupon li { margin: 0; }
body.kc-rd.woocommerce-checkout .nm-checkout-login-coupon .woocommerce-info {
	background: none !important;
	border: 0 !important;
	color: var(--text-faint) !important;
	font-size: 12.5px !important;
	padding: 2px 0 !important;
	margin: 0 !important;
	text-align: left;
}
body.kc-rd.woocommerce-checkout .nm-checkout-login-coupon .woocommerce-info::before {
	display: none !important; /* the theme's alert icon */
}
body.kc-rd.woocommerce-checkout .nm-checkout-login-coupon .woocommerce-info a {
	color: var(--text-muted) !important;
	text-decoration: underline;
	text-underline-offset: 2px;
}
body.kc-rd.woocommerce-checkout .nm-checkout-login-coupon .woocommerce-info a:hover {
	color: var(--text) !important;
}

/* --------------------------------------------------------------------------
   §65.4 Payment cards: selected = pine (customer's own choice), idle = quiet
   ink. Whole card is clickable (the label fills the card; input stays for
   a11y). Loads after kc-redesign.css §7, same-or-higher specificity wins.
   -------------------------------------------------------------------------- */
body.kc-rd .wc_payment_methods { gap: var(--kc-space-3); }
body.kc-rd li.wc_payment_method {
	position: relative;
	border: 2px solid var(--border-strong);
	background: var(--surface-raised);
	padding: 0 !important; /* padding moves onto the label = full-card hit area */
}
body.kc-rd li.wc_payment_method > input[type="radio"] {
	position: absolute;
	opacity: 0;
	pointer-events: none;
}
body.kc-rd li.wc_payment_method > label {
	display: block;
	padding: 14px 48px 14px 16px;
	margin: 0;
	min-height: 48px;
}
/* #payment in the selector on purpose: Savoy's theme-options inline CSS ships
   `#payment .payment_methods li { border-color:#eeeeee }` at (1,1,1), which
   beats every classes-only rule here regardless of order (verified 2026-08-23:
   the selected card rendered a grey border, carried only by the chip+shadow).
   Matching its ID weight is the fix; !important is not. */
body.kc-rd #payment li.wc_payment_method:has(input:checked),
body.kc-rd li.wc_payment_method:has(input:checked) {
	border-color: var(--trust);
	background: var(--surface-raised);
	box-shadow: 2px 2px 0 var(--trust);
}
/* The theme paints a fake radio on BOTH label pseudos — main.css
   `#payment .wc_payment_method label:before` at (1,1,2) plus style.css
   `body.woocommerce-checkout [type=radio]:checked + label:after` at (0,3,3),
   with an ID rule nudging `top`. Fighting them property-by-property is how
   the chip ended up as a 10px dot in the card's top-left. Cede BOTH label
   pseudos (ID-weight selector so the (1,x,x) rules lose), and hang the ✓
   chip on the LI instead — no theme rule touches li::after. */
body.kc-rd.woocommerce-checkout #payment .wc_payment_method > label::before,
body.kc-rd.woocommerce-checkout #payment .wc_payment_method > label::after {
	content: none !important;
	display: none !important;
}
/* main.css's ID rule also writes padding-left:25px on the label */
body.kc-rd.woocommerce-checkout #payment .wc_payment_method > label {
	display: block;
	padding: 14px 48px 14px 16px;
	margin: 0;
	min-height: 48px;
}
/* the ✓ chip — customers re-click cards they are not sure they selected */
body.kc-rd li.wc_payment_method::after {
	content: "";
	position: absolute;
	top: 14px;
	right: 14px;
	width: 22px;
	height: 22px;
	border: 2px solid var(--border-strong);
	border-radius: 50%;
	background: var(--surface-raised);
	pointer-events: none;
}
body.kc-rd li.wc_payment_method:has(input:checked)::after {
	background-color: var(--trust);
	border-color: var(--trust);
	/* geometry-drawn check (SVG data URI): no font metrics involved */
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 9'%3E%3Cpath d='M1 4.5 4.5 8 11 1' fill='none' stroke='white' stroke-width='2'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: center;
	background-size: 12px 9px;
}
body.kc-rd .wc_payment_method .payment_box {
	padding: 0 16px 14px !important;
	margin: 0 !important;
	background: none !important;
}
body.kc-rd .kc-pay-note {
	margin-top: var(--kc-space-2);
	font-size: 12px;
	color: var(--text-muted);
}
/* §7 of the base file painted the fee line tomato — but red is the ERROR
   colour and a disclosed fee is a fact, not a fault. Quiet ink, bold rate. */
body.kc-rd .kc-pay-fee { color: var(--text); font-weight: 400; }
body.kc-rd .kc-pay-fee strong { font-weight: 700; }
/* a theme li rule sets list-style:none at the LI level, which beats the
   base file's ol-level decimal — the 4 steps rendered numberless */
body.kc-rd .kc-pay-explain ol > li { list-style: decimal !important; }
.kc-pay-heading {
	margin: var(--kc-space-5) 0 var(--kc-space-3);
	font-family: var(--kc-font-body);
	font-size: 18px;
	font-weight: 700;
	color: var(--text);
}

/* --------------------------------------------------------------------------
   §65.5 Minimum-order gate (renders instead of the form, inside Savoy's
   cart-errors template). The template's own generic paragraph and duplicate
   Return-to-cart button say nothing our panel doesn't — hide them, keep the
   wrapper for WC's own semantics.
   -------------------------------------------------------------------------- */
body.kc-rd.woocommerce-checkout .error-message > p { display: none; }
/* 80-notices.css (loads after this file — 80 > 65) cards .error-message as a
   generic "Error" note; with the gate panel inside, that is a frame around a
   frame plus a duplicate label. :has(.kc-gate) both scopes the neutralisation
   to exactly the gate case and lifts specificity to (0,5,1) over its (0,4,1),
   so load order stops mattering. */
body.kc-rd.woocommerce-checkout .woocommerce > .error-message:has(.kc-gate) {
	background: none;
	border: 0;
	padding: 0;
	margin: 0;
	box-shadow: none;
}
body.kc-rd.woocommerce-checkout .woocommerce > .error-message:has(.kc-gate)::before {
	content: none;
}
.kc-gate {
	max-width: 560px;
	margin: var(--kc-space-6) auto var(--kc-space-8);
	padding: var(--kc-space-6);
	background: var(--surface-raised);
	border: 1px solid var(--border);
	border-left: 4px solid var(--action);
	border-radius: var(--kc-radius-lg);
	text-align: left;
}
.kc-gate-title {
	margin: 0 0 var(--kc-space-3);
	font-family: var(--kc-font-body);
	font-size: 24px;
	font-weight: 700;
	color: var(--text);
	font-variant-numeric: tabular-nums;
}
.kc-gate-body {
	margin: 0 0 var(--kc-space-4);
	font-size: 15px;
	line-height: 1.6;
	color: var(--text-muted);
	font-variant-numeric: tabular-nums;
}
.kc-gate-body strong { color: var(--text); }
.kc-gate-issues {
	margin: 0 0 var(--kc-space-4) 18px;
	padding: 0;
	list-style: disc;
	font-size: 14px;
	line-height: 1.6;
	color: var(--text-muted);
}
.kc-gate-issues li { margin-bottom: 4px; }
.kc-gate-actions {
	display: flex;
	align-items: center;
	gap: var(--kc-space-5);
	flex-wrap: wrap;
}
.kc-gate-btn,
.kc-gate-btn:visited {
	display: inline-block;
	min-height: var(--btn-min-height);
	line-height: calc(var(--btn-min-height) - 4px);
	padding: 0 var(--kc-space-5);
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border: 2px solid var(--btn-primary-border);
	border-radius: var(--kc-radius-md);
	box-shadow: var(--btn-primary-shadow);
	font-weight: 700;
	font-size: 14px;
	text-decoration: none;
	transition: transform .12s ease, box-shadow .12s ease;
}
.kc-gate-btn:hover {
	color: var(--btn-primary-text);
	transform: translate(3px, 3px);
	box-shadow: 0 0 0 var(--text);
}
.kc-gate-link,
.kc-gate-link:visited {
	color: var(--text-muted);
	font-size: 14px;
	text-decoration: underline;
	text-underline-offset: 2px;
}
.kc-gate-link:hover { color: var(--text); }

/* The branded minimum notice on the CART page (kc-min-note span inside the
   theme's error notice) — make its links legible on the notice ground. */
.kc-min-note a { text-decoration: underline; text-underline-offset: 2px; }

/* --------------------------------------------------------------------------
   §65.6 Checkout submit errors: branded presentation + a scroll target that
   is not hidden behind the fixed header.
   -------------------------------------------------------------------------- */
body.kc-rd.woocommerce-checkout form.checkout .woocommerce-NoticeGroup-checkoutErrors {
	scroll-margin-top: calc(var(--kc-co-head-h, 104px) + 16px);
}
body.kc-rd.woocommerce-checkout form.checkout .woocommerce-error {
	background: var(--surface-raised) !important;
	border: 1px solid var(--border) !important;
	border-left: 4px solid var(--urgent) !important;
	border-radius: var(--kc-radius-md);
	color: var(--text) !important;
	font-size: 14px;
	padding: var(--kc-space-4) var(--kc-space-5) !important;
}
body.kc-rd.woocommerce-checkout .form-row { scroll-margin-top: calc(var(--kc-co-head-h, 104px) + 16px); }

/* --------------------------------------------------------------------------
   §65.7 Terms row: kill the ORPHAN box. savoy-child/style.css paints a fake
   checkbox on EVERY span inside label.checkbox (`.checkbox span:before`,
   (0,3,3)); the terms label contains the text span AND the required-star
   span, so a second empty box floated at the row's right edge (walkthrough
   K2). The real terms checkbox (WC core compliance control) is untouched.
   Deletion candidate reported for orchestrator; neutralised here at (0,4,3).
   -------------------------------------------------------------------------- */
body.kc-rd.woocommerce-checkout .checkbox span.required::before {
	content: none !important;
	display: none !important;
}
/* the terms link rendered in the theme's amber link colour — on cream that
   is the page's weakest contrast; the compliance link reads in ink */
body.kc-rd.woocommerce-checkout .woocommerce-terms-and-conditions-checkbox-text a {
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 2px;
}

/* --------------------------------------------------------------------------
   §65.7b Free-gift line in the summary: woo-discount-rules ships a blue
   "Free" pill (.awdr_free_product_text) — foreign colour, button-shaped
   (brand rule: nothing that is not Add-to-cart may look like a buy control).
   Flatten it to quiet pine text.
   -------------------------------------------------------------------------- */
body.kc-rd #order_review .awdr_free_product_text {
	background: none !important;
	color: var(--trust) !important;
	font-weight: 600;
	font-size: 12px;
	padding: 0 !important;
	border-radius: 0;
}
body.kc-rd #order_review .variation-Discount {
	display: inline;
	font-size: 12px;
	color: var(--text-muted);
}
body.kc-rd #order_review .variation-Discount p { display: inline; margin: 0; }

/* --------------------------------------------------------------------------
   §65.8 Mobile / narrow (the desktop grid never applies below 1025px).
   DOM order is form -> payment -> summary. v5 (owner point 2): the summary
   goes FIRST — a collapsible "Order summary · $total" bar above the form
   (69-checkout-shell.css §69.4) — so the customer never scrolls to the
   bottom to learn the total. Payment stays last. No sticky here.
   -------------------------------------------------------------------------- */
@media (max-width: 1024px) {
	body.kc-rd.woocommerce-checkout form.checkout .checkout-main-form-wrapper-custom {
		display: flex;
		flex-direction: column;
	}
	body.kc-rd.woocommerce-checkout form.checkout #order_review    { order: 0; }
	body.kc-rd.woocommerce-checkout form.checkout #customer_details { order: 1; }
	body.kc-rd.woocommerce-checkout form.checkout #kc-payment-col  { order: 3; }

	body.kc-rd.woocommerce-checkout #order_review {
		padding: var(--kc-space-4);
		margin-top: var(--kc-space-5);
	}
	body.kc-rd.woocommerce-checkout #kc-payment-col { margin-top: var(--kc-space-5); }
	body.kc-rd.woocommerce-checkout .kc-form-heading { margin-top: var(--kc-space-5); }
}

/* --------------------------------------------------------------------------
   §65.9 Details that read as noise otherwise
   -------------------------------------------------------------------------- */
/* wc_price spans pick up stray margin from theme .amount rules — the gate
   title read "is  $60.00" */
.kc-gate .woocommerce-Price-amount { margin: 0; }

/* Savoy's own "Shipping details" h3 (ship-to-different toggle + notes) joins
   the group-heading voice instead of shouting in its own size */
body.kc-rd.woocommerce-checkout #customer_details .col-2 h3 {
	font-family: var(--kc-font-body);
	font-size: 18px;
	font-weight: 700;
	color: var(--text);
}

/* --------------------------------------------------------------------------
   §65.13 e-Transfer card typography (owner-dictated block, 2026-09-12).

   The bacs card is: one BOLD assurance heading, then the red bar carrying all
   four owner-dictated lines, EACH SENTENCE COMPLETING ON ITS OWN LINE — the
   owner split "Send the exact amount shown / leave the transfer message
   empty." across two lines precisely so no line wraps on a phone. So the
   mobile rule below sizes the bar for the longest line ("First-time customers
   must verify their ID (19+).") to stay single-line at 375px. If a copy edit
   ever lengthens a line, shrink the COPY, not the font below 11px.

   COD's policy list (.kc-pay-explain > ul) stays flush-left/quiet from the
   09-12 fix: the base file asks for disc markers at the UL level
   (kc-redesign.css:444 !important) but Savoy kills list-style at the LI
   level — the same trap as the §65.2 ol-decimal fix — so markers never
   painted and only a naked indent survived. The !important on
   list-style/padding out-argues those theme li rules.

   Scoped to .payment_box so the thank-you card and the view-order card (same
   classes, styled by 68-thankyou.css) are untouched. #c62828 and family are
   sanctioned FUNCTIONAL EMT colours, not brand hexes — none restated here.
   -------------------------------------------------------------------------- */
body.kc-rd .payment_box .kc-pay-assure { font-weight: 700; }
body.kc-rd .payment_box .kc-emt-warn {
	padding: 12px 14px;
	font-size: 13px;
}
@media (max-width: 480px) {
	body.kc-rd .payment_box .kc-emt-warn {
		padding: 10px;
		/* sized for the longest line ("First-time customers must verify their
		   ID (19+).") to stay on ONE line at 375px — measured: needs 263px,
		   the box gives 253 at 12px-side padding, so 11px + 2px reclaimed +
		   -0.02em tracking closes it. If a copy edit ever lengthens a line,
		   shrink the COPY, not the font below 11px. */
		font-size: 11px;
		letter-spacing: -0.02em;
	}
}
body.kc-rd .payment_box .kc-pay-explain ul {
	margin: var(--kc-space-2) 0 0;
	padding: 0;
	list-style: none !important;
}
body.kc-rd .payment_box .kc-pay-explain ul > li {
	list-style: none !important;
	margin: 0 0 4px;
	padding: 0 !important;
	font-size: 12.5px;
	line-height: 1.55;
	color: var(--text-muted);
}

/* ===== 66-track.css ===== */
/* ==========================================================================
   66-track.css — SURFACE: fulfilment track (checkout banner · side-cart
   estimator · checkout-rail milestone ladder)
   --------------------------------------------------------------------------
   Partner files: inc-track.php (banner + estimator builders, wc-ajax
   endpoint), inc-cart-panel.php (places the estimator; owns the ladder
   builder), inc-checkout.php §10 (places the ladder on the checkout rail),
   parts-js/66-track.js (posts the postal code, asks for a fragment refresh).

   Design language: the banner and estimator are SYSTEM information, not the
   customer's choice — quiet ink on sunken surface, no pine, no amber (green =
   the customer's own selection; amber = the one money CTA per surface). The
   ladder reuses the exact vocabulary of 30-side-cart.css §30.3 scoped to the
   checkout rail; pine appears there only in its earned/confirmation role.
   No dollar figures live in this file's components except the ladder's own
   labels — thresholds belong to the ladder, the fee rate to the gateway card.
   ========================================================================== */

/* --------------------------------------------------------------------------
   §66.1 Checkout track banner (above form.checkout, refreshed as a fragment)
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-checkout .kc-track-banner {
	margin: 0 0 var(--kc-space-5);
	padding: var(--kc-space-3) var(--kc-space-4);
	background: var(--surface-sunken);
	border: 1px solid var(--border);
	border-left: 3px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	font-family: var(--kc-font-body);
	font-size: 14px;
	line-height: 1.5;
	color: var(--text);
}

body.kc-rd.woocommerce-checkout .kc-track-banner strong {
	font-weight: 600;
}

/* Mail orders must not ask local-delivery questions (owner 2026-08-25:
   a mail order saying "Local Delivery - …" is a category error). The two
   legacy jwa fields and their "Delivery preferences" heading hide whenever
   the SHIPPING banner is on the page. The banner is the track's live DOM
   witness — update_order_review replaces it on every address edit — so the
   rows follow the address in BOTH directions with zero JS. Presentation
   only: the fields stay optional server-side, and on the delivery track
   nothing changes. (Same :has() idiom as 65-checkout.css §65.2.) */
body.kc-rd.woocommerce-checkout:has(.kc-track-banner-shipping) #billing_delivery_time_field,
body.kc-rd.woocommerce-checkout:has(.kc-track-banner-shipping) #shipping_type_field,
body.kc-rd.woocommerce-checkout:has(.kc-track-banner-shipping) #billing_kc_h_prefs_field {
	display: none;
}

/* --------------------------------------------------------------------------
   §66.2 Side-cart estimator row (inside the panel footer AND the empty state)
   -------------------------------------------------------------------------- */

body.kc-rd #nm-widget-panel .kc-track-est {
	width: 100%;
	margin: 0;
	font-family: var(--kc-font-body);
}

/* Resolved state: a <details> row — summary line left, quiet Change right.
   Native disclosure handles the toggle; no JS owns any state here. */
body.kc-rd #nm-widget-panel details.kc-track-est > summary.kc-track-summary {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--kc-space-3);
	min-height: 28px;
	cursor: pointer;
	list-style: none;
}
body.kc-rd #nm-widget-panel details.kc-track-est > summary.kc-track-summary::-webkit-details-marker {
	display: none;
}

body.kc-rd #nm-widget-panel .kc-track-line {
	font-size: 14px;
	font-weight: 500;
	line-height: 1.4;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .kc-track-change {
	flex: 0 0 auto;
	font-size: 12px;
	font-weight: 500;
	color: var(--text-muted);
	text-decoration: underline;
	text-underline-offset: 2px;
}

/* Ask state: label + form in one row. */
body.kc-rd #nm-widget-panel .kc-track-est-ask .kc-track-ask {
	display: block;
	margin: 0 0 var(--kc-space-1);
	font-size: 14px;
	font-weight: 500;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .kc-track-form {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--kc-space-2);
	margin: 0;
}
body.kc-rd #nm-widget-panel details.kc-track-est[open] .kc-track-form {
	margin-top: var(--kc-space-2);
}

body.kc-rd #nm-widget-panel .kc-track-input {
	flex: 1 1 120px;
	min-width: 0;
	height: 44px;
	padding: 0 var(--kc-space-3);
	background: var(--field-bg);
	border: 1px solid var(--field-border);
	border-radius: var(--kc-radius-md);
	font-family: var(--price-font);
	font-size: 14px;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--text);
}

body.kc-rd #nm-widget-panel .kc-track-check {
	flex: 0 0 auto;
	min-height: 44px;
	padding: 0 var(--kc-space-4);
	background: var(--surface-raised);
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	color: var(--text);
	cursor: pointer;
}
body.kc-rd #nm-widget-panel .kc-track-check:hover {
	background: var(--surface-sunken);
}

body.kc-rd #nm-widget-panel .kc-track-msg {
	flex: 1 1 100%;
	font-size: 12px;
	line-height: 1.3;
	color: var(--urgent);
}
body.kc-rd #nm-widget-panel .kc-track-msg:empty {
	display: none;
}

/* Inside the empty-state block the estimator follows two notes — give it air. */
body.kc-rd #nm-widget-panel .kc-cart-empty .kc-track-est {
	margin-top: var(--kc-space-4);
}

/* --------------------------------------------------------------------------
   §66.3 Milestone ladder on the checkout rail (same component, third surface;
   builder + markup identical to the side cart's — inc-cart-panel.php)
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-checkout .kc-checkout-ladder:empty,
body.kc-rd.woocommerce-checkout .kc-checkout-ladder:not(:has(.kc-ladder)) {
	display: none;
}

/* Geometry only — the component itself (head, track, rungs, animation) lives
   in §66.5 below, shared verbatim by all three surfaces. */
body.kc-rd.woocommerce-checkout .kc-checkout-ladder .kc-ladder {
	margin: 0 0 var(--kc-space-4);
	padding: var(--kc-space-4) var(--kc-space-4) var(--kc-space-3);
	background: var(--surface-sunken);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
}

/* Ghost rows + tally inside the review-order table (shells only; the inner
   markup is styled once in §66.5). The table's own cells carry the checkout
   surface's padding rhythm; these rows are quieter and un-bordered. */
body.kc-rd.woocommerce-checkout .woocommerce-checkout-review-order-table tr.kc-ghost-row > td {
	padding: 6px 0;
	border: 0;
}

body.kc-rd.woocommerce-checkout .woocommerce-checkout-review-order-table tr.kc-gift-tally-row > td {
	padding: 6px 0 12px;
	border: 0;
}

/* Gift rows on the checkout summary: the FREE badge (subtotal filter) is the
   row's one money statement — the wdr "Discount: Free" chip would say it
   twice. Same rule family as 30-side-cart.css §30.19 / 60-cart-page.css
   §60.x, third surface. */
body.kc-rd.woocommerce-checkout .woocommerce-checkout-review-order-table tr.kc-gift-row .variation {
	display: none;
}

/* --------------------------------------------------------------------------
   §66.5 Ladder v3 — the owner-approved mockup, as the shared component
   (docs/redesign/ladder-mockup.html — its source is the spec)
   --------------------------------------------------------------------------
   COMPONENT-level rules, deliberately scoped to body.kc-rd only: the ladder
   renders identically on the side cart, the cart page and the checkout rail,
   and one source here cannot drift three ways. Surface files own ONLY the
   container box (padding/background/border) — every inner element is styled
   here and nowhere else.

   Anatomy (markup built in inc-cart-panel.php):
     .kc-ladder-headwrap   one nowrap head line + perk subline, with the
                           celebration overlay (.kc-ladder-cel) stacked on top
     .kc-ladder-track      8px rail; .kc-ladder-fill = pine gradient +
                           barber-pole stripes, width transitions 1.2s
     .kc-ladder-ms         equal flex columns; .kc-ladder-m = 14px marker ON
                           the rail + package lines (.kc-ladder-nmline, one
                           gift per line) + amount. Done = pine disc + check;
                           chased = pine ring + breathe; later = grey ring.
   v4 (2026-08-27): the v3 34px icon discs covered 136px of a 318px rail and
   hid the fill's tip for the first/last ~$7 of every band, so between $60
   and $100 the bar looked frozen and then "lit up" at $100 (owner report,
   measured). Markers are now 14px — the fill is visible across the whole
   rail and grows with every dollar; the gift itself is named in the label
   lines, not drawn in the disc. Head wraps as units (never mid-phrase) so a
   long package name is never ellipsised on a 375px panel.
     .kc-ghost-inner       locked next-tier gift row (each surface wraps it
                           in its own li/tr shell)
     .kc-gift-tally        "$X of free stuff in this order"
   Pine = earned/confirmation stays true: the chased rung gets a pine RING
   and a glow — the disc fills pine only once the engine has granted it.
   rgba(30, 91, 60, …) literals below are var(--trust) with alpha — same
   idiom as the ink rgba() shades in the token file; no new hex.
   -------------------------------------------------------------------------- */

body.kc-rd .kc-ladder-headwrap {
	position: relative;
}

/* Celebration overlay — 60-cart.js adds .kc-cel-on for 1.6s when the served
   tier (data-kc-tier) rises above the sessionStorage'd one. */
body.kc-rd .kc-ladder-cel {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	margin: 0;
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	font-weight: 700;
	color: var(--trust);
	opacity: 0;
	pointer-events: none;
	transition: opacity .3s;
}

body.kc-rd .kc-ladder-head,
body.kc-rd .kc-ladder-perkline {
	transition: opacity .3s;
}

body.kc-rd .kc-cel-on .kc-ladder-cel { opacity: 1; }
/* The hide is INSTANT (transition:none): the theme can re-apply the fragment
   mid-celebration, and a fresh head restarting a 0.3s fade under the overlay
   reads as two sentences mushed together. Only the return fades. */
body.kc-rd .kc-cel-on .kc-ladder-head,
body.kc-rd .kc-cel-on .kc-ladder-perkline { opacity: 0; transition: none; }
body.kc-rd .kc-cel-on .kc-ladder-fill { filter: brightness(1.6); }

/* Head: wraps as UNITS (the value capsule drops to a second line before the
   package name is ever ellipsised). Each child keeps nowrap; v3's nowrap on
   the whole line truncated "FREE 2 Joints + Gu…" on a 375px panel. */
body.kc-rd .kc-ladder-head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 2px 5px;
	white-space: nowrap;
	margin: 0 0 3px;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 400;
	line-height: 1.4;
	color: var(--text);
}

body.kc-rd .kc-ladder-head b,
body.kc-rd .kc-ladder-head b .amount {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-size: 15.5px;
	font-weight: 700;
	font-style: normal;
	color: var(--text);
}

body.kc-rd .kc-ladder-gname {
	flex: 0 1 auto;
	min-width: 0;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	font-weight: 600;
}

body.kc-rd .kc-ladder-worth {
	flex: 0 0 auto;
	padding: 1px 7px;
	border-radius: 999px;
	background: rgba(30, 91, 60, .09); /* --trust @ 9% */
	font-size: 10.5px;
	font-weight: 700;
	color: var(--trust);
}

body.kc-rd .kc-ladder-perkline {
	margin: 0;
	font-family: var(--kc-font-body);
	font-size: 11px;
	font-weight: 400;
	line-height: 1.4;
	color: var(--text-muted);
}

/* Track + fill. The width transition is what makes an add-to-cart visibly
   pour money into the bar; 60-cart.js replays it across fragment swaps. */
body.kc-rd .kc-ladder-track {
	position: relative;
	height: 8px;
	margin: 10px 0 4px;
	background: var(--border);
	border-radius: 999px;
	/* no overflow:hidden — the rung discs ride over the rail */
}

body.kc-rd .kc-ladder-fill {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	border-radius: 999px;
	background: linear-gradient(90deg, var(--kc-pine-300), var(--trust));
	overflow: hidden;
	transition: width 1.2s cubic-bezier(.4, 0, .2, 1), filter .3s;
}

/* Barber-pole: the moving stripes keep the bar feeling live between edits. */
body.kc-rd .kc-ladder-fill::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
	background: repeating-linear-gradient(-45deg, rgba(255, 255, 255, .22) 0 8px, transparent 8px 16px);
	background-size: 30px 30px;
	animation: kc-ladder-stripe 1.6s linear infinite;
}

@keyframes kc-ladder-stripe {
	to { background-position: 30px 0; }
}

/* Rungs: N equal columns; the -15px pull (4px rail margin + 8px rail + 3px)
   centres the 14px markers on the 8px rail. */
body.kc-rd .kc-ladder-ms {
	position: relative;
	display: flex;
	margin-top: -15px;
}

body.kc-rd .kc-ladder-m {
	flex: 1 1 0;
	min-width: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 3px;
}

body.kc-rd .kc-ladder-dot {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 14px;
	height: 14px;
	border-radius: 50%;
	background: var(--surface-raised);
	border: 2px solid var(--border-strong);
	color: var(--text-muted);
	box-sizing: border-box;
	transition: background .3s, border-color .3s, color .3s;
}

body.kc-rd .kc-ladder-dot .kc-ic {
	display: block;
}

body.kc-rd .kc-m-done .kc-ladder-dot {
	background: var(--trust);
	border-color: var(--trust);
	color: var(--on-trust);
}

/* The rung being chased: pine ring + breathe glow (mockup 2s pulse). */
body.kc-rd .kc-m-next .kc-ladder-dot {
	border-color: var(--trust);
	color: var(--text);
	animation: kc-ladder-breathe 2s ease-in-out infinite;
}

@keyframes kc-ladder-breathe {
	0%, 100% { box-shadow: 0 0 0 0 rgba(30, 91, 60, .45); }  /* --trust @ 45% */
	50%      { box-shadow: 0 0 0 5px rgba(30, 91, 60, 0); }
}

body.kc-rd .kc-ladder-nm {
	margin-top: 2px;
	font-family: var(--kc-font-body);
	font-size: 10px;
	font-weight: 600;
	line-height: 1.2;
	text-align: center;
	color: var(--text-muted);
}

/* One gift per line ("1 Gummy" / "+ 2 Joints"): a package never breaks
   mid-phrase inside an ~80px column. The chased rung reads in ink. */
body.kc-rd .kc-ladder-nmline {
	display: block;
	white-space: nowrap;
}

body.kc-rd .kc-m-next .kc-ladder-nm {
	color: var(--text);
}

body.kc-rd .kc-m-done .kc-ladder-nm {
	color: var(--trust);
}

body.kc-rd .kc-ladder-amt {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-size: 9px;
	font-weight: 600;
	color: var(--text-muted);
}

/* — Ghost row: the next tier's package, locked in place in the cart list — */
body.kc-rd .kc-ghost-inner {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	opacity: .5;
}

body.kc-rd .kc-ghost-thumb {
	flex: 0 0 38px;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 38px;
	height: 38px;
	border-radius: var(--kc-radius-md);
	background: var(--surface-sunken);
	color: var(--text);
}

body.kc-rd .kc-ghost-main {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 1px;
}

body.kc-rd .kc-ghost-name {
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	font-weight: 600;
	line-height: 1.3;
	color: var(--text-muted);
}

body.kc-rd .kc-ghost-x {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-size: 11px;
}

body.kc-rd .kc-ghost-note {
	font-family: var(--kc-font-body);
	font-size: 10.5px;
	font-weight: 500;
	line-height: 1.3;
	color: var(--text-muted);
}

body.kc-rd .kc-ghost-price,
body.kc-rd .kc-ghost-price .amount {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-size: 12.5px;
	font-weight: 700;
	color: var(--text-muted);
}

body.kc-rd .kc-ghost-price del {
	text-decoration: line-through;
}

/* — Value tally — pine = earned value; the zero-state nudge stays quiet ink
   (pine is confirmation, and nothing is confirmed yet). */
body.kc-rd .kc-gift-tally {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 9px 12px;
	border-radius: var(--kc-radius-md);
	background: var(--surface-sunken);
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	font-weight: 700;
	color: var(--trust);
}

body.kc-rd .kc-gift-tally .kc-ic {
	flex: 0 0 auto;
}

body.kc-rd .kc-gift-tally-zero {
	color: var(--text-muted);
	font-weight: 600;
}

/* — Gift-row subline ("yours — added automatically") — */
body.kc-rd .kc-gift-note {
	display: block;
	font-family: var(--kc-font-body);
	font-size: 10.5px;
	font-weight: 500;
	line-height: 1.3;
	color: var(--text-muted);
	text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
	body.kc-rd .kc-ladder-fill { transition: none; }
	body.kc-rd .kc-ladder-fill::after { animation: none; background: none; }
	body.kc-rd .kc-m-next .kc-ladder-dot { animation: none; box-shadow: 0 0 0 2px rgba(30, 91, 60, .25); } /* static ring keeps the "chased" state legible */
	body.kc-rd .kc-ladder-dot,
	body.kc-rd .kc-ladder-cel,
	body.kc-rd .kc-ladder-head,
	body.kc-rd .kc-ladder-perkline { transition: none; }
}

/* --------------------------------------------------------------------------
   §66.4 Narrow panel / phone
   -------------------------------------------------------------------------- */

@media (max-width: 550px) {
	body.kc-rd #nm-widget-panel .kc-track-line,
	body.kc-rd #nm-widget-panel .kc-track-est-ask .kc-track-ask {
		font-size: 13px;
	}
}

@media (max-width: 700px) {
	body.kc-rd.woocommerce-checkout .kc-track-banner {
		font-size: 13px;
	}
}

/* ===== 67-fulfillment.css ===== */
/* ==========================================================================
   67 · FULFILLMENT — the Delivery/Shipping fork as big cards (checkout)
   Owner: checkout agent. Partner files: inc-checkout.php §11-12 (builder),
   inc-zone.php (track decision), inc-express.php (Express + ship dates),
   parts-js/67-fulfillment.js (highlight sync only).
   --------------------------------------------------------------------------
   Visual language deliberately mirrors the payment cards (65-checkout.css
   §65.4): 2px border-strong idle, pine border + offset shadow + ✓ chip when
   chosen (green = the customer's own choice — brand law). Selected state is
   painted twice on purpose: server-side class on render, :has() live in the
   browser, and 67-fulfillment.js keeps both honest across fragment paints
   (the check can sit on the rail's twin radio — see inc-checkout.php §11
   header — so every selector here also accepts the .kc-ful-sel class).
   ========================================================================== */

/* --------------------------------------------------------------------------
   §67.1 (v5b) Placement. #kc-co-ship-wrap is no longer a grid child: it
   renders INSIDE the billing field flow (pseudo-field @110, below the
   complete address block — inc-checkout §1 + inc-checkout-fields.php). The
   earlier grid re-slot (ship row 2 / payment row 3) is gone with it;
   65-checkout.css §65.1's three-child grid is exact again. The extra top
   margin makes the fork read as its own step after the address, the same
   rank the group headings carry (65-checkout.css §65.2).
   -------------------------------------------------------------------------- */
body.kc-rd.woocommerce-checkout .kc-form-ful-row { margin: var(--kc-space-4) 0 var(--kc-space-2); }

/* --------------------------------------------------------------------------
   §67.2 Heading + block rhythm (same voice as the form group headings)
   -------------------------------------------------------------------------- */
body.kc-rd.woocommerce-checkout #kc-co-ship-wrap { margin-top: var(--kc-space-3); }
body.kc-rd.woocommerce-checkout .kc-ful-heading {
	margin: 0 0 var(--kc-space-3);
	font-family: var(--kc-font-body);
	font-size: 18px;
	font-weight: 700;
	color: var(--text);
}
body.kc-rd #kc-co-ship {
	display: flex;
	flex-direction: column;
	gap: var(--kc-space-3);
}

/* --------------------------------------------------------------------------
   §67.3 The cards
   -------------------------------------------------------------------------- */
body.kc-rd .kc-ful-card {
	position: relative;
	display: flex;
	align-items: center;
	gap: var(--kc-space-3);
	padding: 14px 48px 14px 16px;
	min-height: 56px;
	margin: 0;
	border: 2px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	background: var(--surface-raised);
	cursor: pointer;
	transition: border-color 120ms ease, box-shadow 120ms ease;
}
body.kc-rd .kc-ful-card:hover { border-color: var(--text-muted); }

/* the real WC radio stays for a11y, visually absorbed by the card */
body.kc-rd .kc-ful-card input.shipping_method {
	position: absolute;
	opacity: 0;
	pointer-events: none;
}
body.kc-rd .kc-ful-card:focus-within {
	outline: 2px solid var(--trust);
	outline-offset: 2px;
}

body.kc-rd .kc-ful-ic { flex: 0 0 auto; color: var(--text-muted); display: inline-flex; }
body.kc-rd .kc-ful-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
body.kc-rd .kc-ful-title {
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 700;
	color: var(--text);
}
body.kc-rd .kc-ful-meta { font-size: 12.5px; color: var(--text-muted); }
body.kc-rd .kc-ful-threshold { font-size: 11px; color: var(--text-muted); }
body.kc-rd .kc-ful-price {
	margin-left: auto;
	font-size: 14px;
	font-weight: 700;
	color: var(--text);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}
body.kc-rd .kc-ful-price .woocommerce-Price-amount { margin: 0; }
body.kc-rd .kc-ful-free { color: var(--trust); }

/* selected = pine, the payment-card language exactly. v5: CLASS ONLY. A
   checked radio is the engine's default until the customer clicks (intent,
   inc-checkout-fields.php §2) — painting :checked pine would colour a system
   guess green. Server renders .kc-ful-sel only with intent; 67-fulfillment.js
   mirrors the same rule live. */
body.kc-rd .kc-ful-card.kc-ful-sel {
	border-color: var(--trust);
	box-shadow: 2px 2px 0 var(--trust);
}
body.kc-rd .kc-ful-card.kc-ful-sel .kc-ful-ic { color: var(--trust); }

/* the ✓ chip, same geometry as §65.4 (customers re-click cards they are not
   sure they selected) */
body.kc-rd .kc-ful-card::after {
	content: "";
	position: absolute;
	top: 14px;
	right: 14px;
	width: 22px;
	height: 22px;
	border: 2px solid var(--border-strong);
	border-radius: 50%;
	background: var(--surface-raised);
	pointer-events: none;
}
body.kc-rd .kc-ful-card.kc-ful-sel::after {
	background-color: var(--trust);
	border-color: var(--trust);
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 9'%3E%3Cpath d='M1 4.5 4.5 8 11 1' fill='none' stroke='white' stroke-width='2'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: center;
	background-size: 12px 9px;
}

/* --------------------------------------------------------------------------
   §67.4 Disabled Delivery card (out of the delivery area): visible, honest,
   inert. No hover, no chip — it is information, not an option.
   -------------------------------------------------------------------------- */
body.kc-rd .kc-ful-card.kc-ful-off {
	cursor: default;
	background: var(--surface);
	border-style: dashed;
	border-color: var(--border);
}
body.kc-rd .kc-ful-card.kc-ful-off:hover { border-color: var(--border); }
body.kc-rd .kc-ful-card.kc-ful-off::after { content: none; }
body.kc-rd .kc-ful-card.kc-ful-off .kc-ful-title,
body.kc-rd .kc-ful-card.kc-ful-off .kc-ful-meta,
body.kc-rd .kc-ful-card.kc-ful-off .kc-ful-ic { opacity: 0.55; }
body.kc-rd .kc-ful-reason {
	margin-top: 2px;
	font-size: 12px;
	color: var(--text);
}

/* --------------------------------------------------------------------------
   §67.5 Ground / Express lanes inside the Shipping card. The card becomes a
   small column; the head row is static (no price — prices live on the
   lanes), lanes are compact selectable rows.
   -------------------------------------------------------------------------- */
body.kc-rd .kc-ful-card.kc-ful-shipping:has(.kc-ful-subs) {
	flex-direction: column;
	align-items: stretch;
	gap: var(--kc-space-2);
}
body.kc-rd .kc-ful-head-static {
	display: flex;
	align-items: center;
	gap: var(--kc-space-3);
}
body.kc-rd .kc-ful-subs {
	display: flex;
	flex-direction: column;
	gap: var(--kc-space-2);
	margin-left: 34px; /* clears the head icon column */
	padding-top: var(--kc-space-2);
	border-top: 1px solid var(--border);
}
body.kc-rd .kc-ful-sub {
	display: flex;
	align-items: center;
	gap: var(--kc-space-3);
	min-height: 44px;
	margin: 0;
	padding: 8px 12px;
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	background: var(--surface-raised);
	cursor: pointer;
	transition: border-color 120ms ease;
}
body.kc-rd .kc-ful-sub:hover { border-color: var(--text-muted); }
body.kc-rd .kc-ful-sub input.shipping_method {
	position: absolute;
	opacity: 0;
	pointer-events: none;
}
body.kc-rd .kc-ful-sub-name {
	font-size: 13.5px;
	font-weight: 600;
	color: var(--text);
}
body.kc-rd .kc-ful-sub .kc-ful-price { font-size: 13.5px; }
body.kc-rd .kc-ful-sub.kc-ful-sub-sel {
	border-color: var(--trust);
	box-shadow: inset 0 0 0 1px var(--trust);
}
body.kc-rd .kc-ful-shipnote,
body.kc-rd .kc-ful-foot {
	font-size: 11.5px;
	color: var(--text-muted);
}
body.kc-rd .kc-ful-card:has(.kc-ful-subs) .kc-ful-shipnote { margin-left: 34px; }
/* single-lane card: the footnote becomes a full-width flow row under the head
   (absolute positioning clipped into the card border — seen on staging) */
body.kc-rd label.kc-ful-card.kc-ful-shipping { flex-wrap: wrap; }
body.kc-rd label.kc-ful-card .kc-ful-foot {
	flex: 1 0 100%;
	margin: 4px 0 0 34px; /* aligns under the head text (icon 22 + gap 12) */
}

/* --------------------------------------------------------------------------
   §67.6 The right-rail chooser collapses to the chosen line only (NN: the
   rail selector nobody sees). The radios are the rail's twin group members —
   hidden AND inert; choosing happens on the cards. JS marks .kc-chosen by
   value (belt for the :has()-less), :only-child covers the single-rate and
   zone-0 placeholder rows.
   -------------------------------------------------------------------------- */
body.kc-rd.woocommerce-checkout #order_review ul#shipping_method > li { display: none; margin: 0; }
body.kc-rd.woocommerce-checkout #order_review ul#shipping_method > li.kc-chosen,
body.kc-rd.woocommerce-checkout #order_review ul#shipping_method > li:has(input:checked),
body.kc-rd.woocommerce-checkout #order_review ul#shipping_method > li:only-child { display: block; }
body.kc-rd.woocommerce-checkout #order_review ul#shipping_method input.shipping_method { display: none; }
body.kc-rd.woocommerce-checkout #order_review ul#shipping_method label {
	margin: 0;
	cursor: default;
	pointer-events: none;
	padding-left: 0;
}
/* Savoy paints a fake radio on the label pseudos (same trap 65-checkout.css
   §65.4 documents for #payment) — the input is hidden, so the ghost circle
   must go too. ID weight beats the theme's (1,x,x) rules. */
body.kc-rd.woocommerce-checkout #order_review ul#shipping_method label::before,
body.kc-rd.woocommerce-checkout #order_review ul#shipping_method label::after {
	content: none !important;
	display: none !important;
}

/* --------------------------------------------------------------------------
   §67.7 Empty state (no usable address yet): a dashed prompt, never a blank.
   -------------------------------------------------------------------------- */
body.kc-rd .kc-ful-empty {
	padding: var(--kc-space-4);
	border: 2px dashed var(--border-strong);
	border-radius: var(--kc-radius-md);
	font-size: 13.5px;
	color: var(--text-muted);
	background: var(--surface);
}

/* --------------------------------------------------------------------------
   §67.8 COD explainer (rendered by inc-zone.php when COD is absent)
   -------------------------------------------------------------------------- */
body.kc-rd .kc-cod-note {
	margin: 0 0 var(--kc-space-3);
	font-size: 12px;
	color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   §67.9 Quiet under reduced motion
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
	body.kc-rd .kc-ful-card,
	body.kc-rd .kc-ful-sub { transition: none; }
}

/* ===== 68-thankyou.css ===== */
/* ==========================================================================
   68 — EMT thank-you (pack step 6) + checkout EMT warning bar + CC card.
   Owner: checkout session (pack-step67). Loads after 65/67 — later number
   wins ties, no !important needed against sibling partials.

   §68.1 red warning bar (checkout payment box + thank-you card + view-order)
   §68.2 amber pending hero (never a green check)
   §68.3 four-row copy card
   §68.4 legacy suppression while the card is live (body.kc-ty-emt)
   §68.5 mobile
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   §68.1 The red bar. NN-proven: #c62828 solid / white / 12.5px / 700 — red
   is spent ONLY on "don't get this wrong"; recipient details stay in brand
   colours. (#c62828 / #b7791f / #fdf3e0 are functional EMT colours, not
   brand hexes — build_kc_css.py --check allows them by design.)
   -------------------------------------------------------------------------- */
body.kc-rd .kc-emt-warn {
	display: block;
	margin-top: var(--kc-space-2);
	padding: 10px 12px;
	background: #c62828;
	color: #ffffff;
	border-radius: 8px;
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	font-weight: 700;
	line-height: 1.5;
}
body.kc-rd .kc-emt-warn > span { display: block; padding: 2px 0; }

/* the assurance line above it, inside the expanded e-Transfer card */
body.kc-rd .kc-pay-assure {
	margin: 0 0 var(--kc-space-2);
	font-size: 13.5px;
	line-height: 1.55;
	color: var(--text);
}

/* --------------------------------------------------------------------------
   §68.2 Amber pending hero. Clock, not check: the one remaining task stays
   front and center. Gazella earns its place (>=28px brand face).
   -------------------------------------------------------------------------- */
body.kc-rd .kc-ty-hero {
	max-width: 640px;
	margin: 0 auto;
	padding: var(--kc-space-6) var(--kc-space-4) var(--kc-space-4);
	text-align: center;
}
body.kc-rd .kc-ty-clock { display: inline-block; line-height: 0; }
body.kc-rd .kc-ty-title {
	margin: var(--kc-space-3) 0 var(--kc-space-2);
	font-family: var(--kc-font-display);
	font-size: clamp(28px, 4.5vw, 38px);
	line-height: 1.15;
	color: var(--text);
}
body.kc-rd .kc-ty-sub {
	margin: 0;
	font-family: var(--kc-font-body);
	font-size: 15px;
	line-height: 1.6;
	color: var(--text-muted);
}
body.kc-rd .kc-ty-sub strong { color: var(--text); font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   §68.3 The copy card: amber head, white body, four rows
   (96px uppercase label + value + Copy).
   -------------------------------------------------------------------------- */
body.kc-rd .kc-ty-pay {
	max-width: 640px;
	margin: 0 auto var(--kc-space-6);
	border: 2px solid #b7791f;
	border-radius: var(--kc-radius-lg);
	background: var(--surface-raised);
	overflow: hidden;
}
body.kc-rd .kc-ty-pay-head {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 12px 16px;
	background: #b7791f;
	color: #ffffff;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 700;
	line-height: 1.35;
}
body.kc-rd .kc-ty-pay-head svg { flex: 0 0 auto; }
body.kc-rd .kc-ty-pay-body { padding: 4px 16px 16px; }

body.kc-rd .kc-ty-row {
	display: grid;
	grid-template-columns: 96px 1fr auto;
	gap: 4px 12px;
	align-items: center;
	padding: 10px 0;
	border-bottom: 1px solid var(--border);
}
body.kc-rd .kc-ty-lbl {
	font-family: var(--kc-font-body);
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: .07em;
	text-transform: uppercase;
	color: var(--text-faint);
	line-height: 1.35;
}
body.kc-rd .kc-ty-valwrap { min-width: 0; }
body.kc-rd .kc-ty-val {
	display: block;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	color: var(--text);
	line-height: 1.4;
	overflow-wrap: anywhere;
	font-variant-numeric: tabular-nums;
}
body.kc-rd .kc-ty-hint {
	display: block;
	margin-top: 2px;
	font-size: 12px;
	font-weight: 400;
	color: var(--text-muted);
	line-height: 1.45;
}
body.kc-rd .kc-ty-copy {
	min-height: 34px;
	padding: 6px 14px;
	border: 1.5px solid var(--border-strong);
	border-radius: 6px;
	background: var(--surface-raised);
	font-family: var(--kc-font-body);
	font-size: 12px;
	font-weight: 700;
	color: var(--text);
	cursor: pointer;
}
body.kc-rd .kc-ty-copy:hover { border-color: var(--text); }
body.kc-rd .kc-ty-copy.is-done {
	border-color: var(--trust);
	color: var(--trust);
}
@media (pointer: coarse) {
	body.kc-rd .kc-ty-copy { min-height: 44px; padding: 10px 16px; }
}

body.kc-rd .kc-ty-next { margin: var(--kc-space-4) 0 0; }
body.kc-rd .kc-ty-next-h {
	display: block;
	margin-bottom: 4px;
	font-family: var(--kc-font-body);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: .07em;
	text-transform: uppercase;
	color: var(--text-faint);
}
body.kc-rd .kc-ty-next ol {
	margin: 0;
	padding-left: 20px;
	font-size: 13.5px;
	line-height: 1.75;
	color: var(--text);
}
body.kc-rd .kc-ty-next ol > li { list-style: decimal !important; }

body.kc-rd .kc-ty-help {
	margin: var(--kc-space-3) 0 0;
	font-size: 13px;
	line-height: 1.6;
	color: var(--text-muted);
}
body.kc-rd .kc-ty-help a { color: var(--trust); font-weight: 600; text-decoration: underline; }
body.kc-rd .kc-ty-urgency {
	margin: var(--kc-space-3) 0 0;
	padding: 10px 12px;
	background: #fdf3e0;
	border-radius: 8px;
	font-size: 13px;
	line-height: 1.6;
	color: var(--text);
}
body.kc-rd .kc-ty-foot {
	margin: var(--kc-space-3) 0 0;
	font-size: 12px;
	line-height: 1.5;
	color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   §68.4 While the card is live: Savoy's green-check "has been received"
   notice contradicts the pending state — hidden for EMT-awaiting orders
   only (body class set server-side; §14 belt already empties the text).
   -------------------------------------------------------------------------- */
body.kc-rd.kc-ty-emt .woocommerce-thankyou-order-received { display: none; }

/* --------------------------------------------------------------------------
   §68.5 Mobile (390px must not overflow; labels keep their column).
   -------------------------------------------------------------------------- */
@media (max-width: 480px) {
	body.kc-rd .kc-ty-hero { padding-top: var(--kc-space-5); }
	body.kc-rd .kc-ty-row { grid-template-columns: 86px 1fr auto; gap: 4px 10px; }
	body.kc-rd .kc-ty-pay-body { padding: 2px 12px 14px; }
}

/* ===== 69-checkout-shell.css ===== */
/* ==========================================================================
   69-checkout-shell.css — SURFACE: checkout-v5 distraction-free shell
   Owner: checkout agent. Partner files: inc-checkout-shell.php (mini header,
   summary head, register slot), inc-checkout-fields.php (fulfilment intent,
   local-delivery options, COD cash), parts-js/69-checkout-shell.js.
   --------------------------------------------------------------------------
   Everything keys off body.kc-co-shell (editable checkout only — the thank-you
   page never gets it). This file is the LAST checkout partial (69 > 67 > 66 >
   65), so same-specificity ties resolve here without !important. The
   !important declarations below (4, in 3 rules — §69.1 ×2, §69.7 ×2) are all
   against INLINE styles (the WhatsApp plugin writes position/z-index inline)
   or the theme's own !important (#nm-header; the Savoy fake-radio pseudo).
   None presses an old kc layer. (v5b dropped §69.5's one: the address rows
   are no longer folded, and #kc-payment-col / .col-2 carry no inline display
   — checked in the live DOM.)

   Chrome this file hides is owned elsewhere (inc-nav / inc-topbar / inc-shell /
   40-42-45 CSS) and is not edited — hiding is a checkout-scoped override.
   Delete candidate for the orchestrator: kc-redesign.css §7 lines 399-412
   (body.kc-enclosed-checkout …) is superseded by §69.1 below.
   ========================================================================== */

/* --------------------------------------------------------------------------
   §69.1 Site chrome off. The header height token is the one number the rest
   of the page reads (body offset, sticky rail top, scroll margins).
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell {
	--kc-co-head-h: 56px;
	padding-top: var(--kc-co-head-h);
	background: var(--surface);
}
body.kc-rd.kc-co-shell #nm-header,
body.kc-rd.kc-co-shell #nm-header-placeholder,
body.kc-rd.kc-co-shell .kc-marquee,
body.kc-rd.kc-co-shell .top-bar-delivery,
body.kc-rd.kc-co-shell #nm-mobile-menu,
body.kc-rd.kc-co-shell .nm-mobile-menu-overlay,
body.kc-rd.kc-co-shell #nm-footer,
body.kc-rd.kc-co-shell #nm-shop-bar-wrap,
body.kc-rd.kc-co-shell #nm-header-search,
body.kc-rd.kc-co-shell #nm-login-popup-wrap {
	display: none !important; /* theme + kc-redesign.css both pin #nm-header with !important */
}
/* Click-to-Chat writes position:fixed inline; display is the only lever. */
body.kc-rd.kc-co-shell #ht-ctc-chat,
body.kc-rd.kc-co-shell .ht-ctc-chat { display: none !important; }
/* The kc mobile drawer (header.php:98 div.mobile-container-nav-menu, styled
   by 40-nav-menu.css §40.4) is parked off-screen at top:-100% — invisible but
   its 45 links/buttons stayed in the Tab order ahead of the form (verify F1).
   Its hamburger lives in the hidden #nm-header, so on the shell it can never
   open: unrender it. No !important needed — nothing sets its display. */
body.kc-rd.kc-co-shell .mobile-container-nav-menu { display: none; }

/* --------------------------------------------------------------------------
   §69.2 Mini header: logo · Secure checkout · Back to cart. Fixed (the theme
   never fires wp_body_open, so it is printed at wp_footer — see PHP).
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell .kc-co-head {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 11112; /* the marquee's slot, now empty */
	height: var(--kc-co-head-h);
	background: var(--surface-raised);
	border-bottom: 1px solid var(--border);
}
body.kc-rd.kc-co-shell .kc-co-head-in {
	max-width: 1280px;
	height: 100%;
	margin: 0 auto;
	padding: 0 var(--gutter);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--kc-space-4);
}
body.kc-rd.kc-co-shell .kc-co-logo { display: inline-flex; align-items: center; line-height: 0; }
body.kc-rd.kc-co-shell .kc-co-logo-img { height: 30px; width: auto; display: block; }
body.kc-rd.kc-co-shell .kc-co-logo-text {
	font-family: var(--kc-font-body);
	font-size: 18px;
	font-weight: 700;
	color: var(--text);
}
body.kc-rd.kc-co-shell .kc-co-secure {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	font-weight: 600;
	letter-spacing: .02em;
	color: var(--text-muted);
}
body.kc-rd.kc-co-shell .kc-co-secure svg { color: var(--trust); }
body.kc-rd.kc-co-shell .kc-co-back,
body.kc-rd.kc-co-shell .kc-co-back:visited {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	min-height: 44px;
	padding: 0 6px;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	color: var(--text);
	text-decoration: none;
}
body.kc-rd.kc-co-shell .kc-co-back:hover { color: var(--text-muted); }
body.kc-rd.kc-co-shell .kc-co-back-short { display: none; }
@media (max-width: 480px) {
	body.kc-rd.kc-co-shell .kc-co-secure span { display: none; } /* lock icon alone */
	body.kc-rd.kc-co-shell .kc-co-back-long { display: none; }
	body.kc-rd.kc-co-shell .kc-co-back-short { display: inline; }
}

/* --------------------------------------------------------------------------
   §69.3 Page title: the WPBakery "Checkout" heading stays (it is the page's
   own copy) but drops from a 50px hero to a compact 28px line — the shell's
   "Secure checkout" already says where we are. Gazella at ≥28px only.
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell .wpb-content-wrapper h2.vc_custom_heading {
	/* (0,4,2) over savoy-child/style.css:2750 (0,3,2) — effective since the
	   orchestrator dropped that rule's !important on 2026-08-27 (verify F2). */
	font-family: var(--kc-font-display);
	font-size: 28px;
	line-height: 1.1;
	margin: var(--kc-space-4) 0 var(--kc-space-4);
}
body.kc-rd.kc-co-shell .nm-page-wrap-inner > .nm-row { padding-top: 0; }

/* --------------------------------------------------------------------------
   §69.4 Order-summary head (inc-checkout-shell.php §3). Desktop: first thing
   in the sticky rail — the total is visible before anything else. Phone: the
   rail moves ABOVE the form (65-checkout.css §65.8 order:0) and becomes a
   collapsible bar whose head always shows "Order summary · $total".
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell #order_review_heading { display: none; } /* "Your order" — the head replaces it */
body.kc-rd.kc-co-shell .kc-co-sum-head {
	display: flex;
	align-items: center;
	gap: var(--kc-space-3);
	margin: 0 0 var(--kc-space-4);
	padding: 0 0 var(--kc-space-3);
	border-bottom: 1px solid var(--border);
}
body.kc-rd.kc-co-shell .kc-co-sum-title {
	flex: 1 1 auto;
	font-family: var(--kc-font-body);
	font-size: 16px;
	font-weight: 700;
	color: var(--text);
}
body.kc-rd.kc-co-shell .kc-co-sum-total,
body.kc-rd.kc-co-shell .kc-co-sum-total .amount {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-size: 20px;
	font-weight: 700;
	color: var(--text);
	white-space: nowrap;
}
body.kc-rd.kc-co-shell .kc-co-sum-total .woocommerce-Price-amount { margin: 0; }
body.kc-rd.kc-co-shell .kc-co-sum-toggle {
	display: none; /* desktop: nothing to fold */
	flex: 0 0 auto;
	width: 44px;
	height: 44px;
	margin: -10px -10px -10px 0;
	padding: 0;
	border: 0;
	background: none;
	color: var(--text);
	cursor: pointer;
	align-items: center;
	justify-content: center;
}
body.kc-rd.kc-co-shell .kc-co-sum-toggle svg { transition: transform 160ms ease; }
body.kc-rd.kc-co-shell .kc-co-sum-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
/* the review table's Product / Subtotal header row is noise under a head */
body.kc-rd.kc-co-shell .woocommerce-checkout-review-order-table thead { display: none; }

/* Phone / tablet (verify F2, owner: "置顶"): the bar is PINNED directly under
   the mini header — above the page title, the login/coupon lines and the
   track banner — not merely first inside the form. The head is fixed; the
   body opens as a sheet hanging from the bar (max-height, scrolls inside).
   Both stay in the form's DOM (the review table carries the shipping-method
   twins and is a WC fragment target), only their boxes leave the flow; the
   in-flow #order_review collapses to nothing. Ancestors carry no transform /
   filter (checked), so fixed = viewport. A scrim (69-checkout-shell.js) sits
   between the sheet and the page so a tap outside closes it. */
@media (max-width: 1024px) {
	body.kc-rd.kc-co-shell {
		--kc-co-sum-h: 48px;
		padding-top: calc(var(--kc-co-head-h) + var(--kc-co-sum-h));
	}
	body.kc-rd.kc-co-shell #order_review {
		margin: 0;
		padding: 0;
		border: 0;
		border-radius: 0;
		background: none;
	}
	body.kc-rd.kc-co-shell .kc-co-sum-head {
		position: fixed;
		top: var(--kc-co-head-h);
		left: 0;
		right: 0;
		z-index: 11111; /* under the mini header (11112), over the sheet */
		height: var(--kc-co-sum-h);
		margin: 0;
		padding: 0 var(--gutter);
		background: var(--surface-raised);
		border-bottom: 1px solid var(--border);
		cursor: pointer;
	}
	body.kc-rd.kc-co-shell .kc-co-sum-toggle { display: inline-flex; margin: 0 -10px 0 0; }
	body.kc-rd.kc-co-shell .kc-co-sum-body { display: none; }
	body.kc-rd.kc-co-shell .kc-co-sum-body.is-open {
		display: block;
		position: fixed;
		top: calc(var(--kc-co-head-h) + var(--kc-co-sum-h));
		left: 0;
		right: 0;
		z-index: 11110;
		/* leave a band of page (scrim) under the sheet so it reads as a sheet
		   and a tap outside has somewhere to land; long summaries scroll */
		max-height: calc(100vh - var(--kc-co-head-h) - var(--kc-co-sum-h) - 72px);
		max-height: calc(100dvh - var(--kc-co-head-h) - var(--kc-co-sum-h) - 72px);
		overflow-y: auto;
		overscroll-behavior: contain;
		padding: var(--kc-space-4) var(--gutter) var(--kc-space-5);
		background: var(--surface-raised);
		border-bottom: 1px solid var(--border);
		box-shadow: var(--panel-shadow);
	}
	body.kc-rd.kc-co-shell .kc-co-sum-scrim {
		position: fixed;
		inset: 0;
		z-index: 11109;
		background: var(--text);
		opacity: .28;
	}
	/* 2026-08-28: 这里原本还有一份 shell 的 scroll-margin-top 规则(选择器与
	   89-mobile-fix.css §89.5 逐字相同、值也相同)。89 后加载、同特异性,
	   永远赢 —— 也就是说**改这里的公式不会有任何效果**,是个静默陷阱。
	   已删除:锚点偏移的唯一权威是 89-mobile-fix.css(它用 var() 兜底,
	   把桌面档一并算对;本文件只在 ≤1024 声明 --kc-co-head-h / --kc-co-sum-h,
	   那两个变量仍然是这里的责任)。改偏移请去 89。 */
}

/* --------------------------------------------------------------------------
   §69.5 Progressive disclosure (v5b — owner 2026-08-27 pm: the WHOLE address
   first, then the fork). The address block is never folded. Until the
   customer clicks a Fulfillment card (data-kc-intent="" on #kc-co-ship-wrap,
   mirrored by JS on click) only what the fork unlocks waits: delivery
   preferences, ship-to-different + notes, and the whole payment column. The
   summary rail stays — the customer always sees what they are paying.
   No !important here any more: the folded targets carry no inline display
   (WC core's wc-address-i18n.js only writes inline display on the locale
   address rows, which are no longer in this list).
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell form.checkout:has(#kc-co-ship-wrap[data-kc-intent=""]) #billing_kc_delivery_opts_field,
body.kc-rd.kc-co-shell form.checkout:has(#kc-co-ship-wrap[data-kc-intent=""]) #customer_details .col-2,
body.kc-rd.kc-co-shell form.checkout:has(#kc-co-ship-wrap[data-kc-intent=""]) #kc-payment-col {
	display: none;
}
/* Delivery preferences belong to the delivery track only (chosen rate's
   track, refreshed with the #kc-co-ship fragment): a Canada-wide shipping
   pick never sees them and the server never validates them
   (inc-checkout-fields.php §4). Together with the intent rule above the
   block appears exactly when the customer has PICKED Same-day delivery. */
body.kc-rd.kc-co-shell form.checkout:has(#kc-co-ship:not([data-kc-track="delivery"])) #billing_kc_delivery_opts_field {
	display: none;
}
/* Delivery track: WC's "Order notes" duplicates "Delivery instructions" (the
   driver line the dispatch bot reads). Hidden, not removed — the mail track
   keeps it, and the server never validates it (verify O3). */
body.kc-rd.kc-co-shell form.checkout:has(#kc-co-ship[data-kc-track="delivery"]) #order_comments_field {
	display: none;
}
body.kc-rd.kc-co-shell .kc-ful-next {
	margin: 2px 0 0;
	font-size: 12.5px;
	color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   §69.6 Delivery-preferences block (inc-checkout-fields.php §3).
   Pills: idle = quiet ink outline; the DEFAULT checked pill is ink-filled
   (system default, not the customer's choice); once the group has been
   touched the checked pill turns pine (brand law: green = the customer's
   own choice).
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell .kc-form-opts-row { margin-top: var(--kc-space-2); }
body.kc-rd.kc-co-shell .kc-co-opts-h {
	margin: var(--kc-space-5) 0 var(--kc-space-3);
	font-family: var(--kc-font-body);
	font-size: 18px;
	font-weight: 700;
	color: var(--text);
}
body.kc-rd.kc-co-shell .kc-co-fs {
	margin: 0 0 var(--kc-space-4);
	padding: 0;
	border: 0;
	min-width: 0;
}
body.kc-rd.kc-co-shell .kc-co-legend {
	display: block;
	margin: 0 0 var(--kc-space-2);
	padding: 0;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	color: var(--text);
}
body.kc-rd.kc-co-shell .kc-co-seg {
	display: flex;
	flex-wrap: wrap;
	gap: var(--kc-space-2);
}
body.kc-rd.kc-co-shell .kc-co-pill {
	position: relative;
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	margin: 0;
	padding: 0 14px;
	border: 1.5px solid var(--border-strong);
	border-radius: 999px;
	background: var(--surface-raised);
	cursor: pointer;
	transition: border-color 120ms ease, background 120ms ease, color 120ms ease;
}
body.kc-rd.kc-co-shell .kc-co-pill input[type="radio"] {
	position: absolute;
	opacity: 0;
	width: 1px;
	height: 1px;
	margin: 0;
	pointer-events: none;
}
body.kc-rd.kc-co-shell .kc-co-pill-t {
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	font-weight: 500;
	line-height: 1.2;
	color: var(--text);
	white-space: nowrap;
}
body.kc-rd.kc-co-shell .kc-co-pill:hover { border-color: var(--text-muted); }
body.kc-rd.kc-co-shell .kc-co-pill:focus-within { outline: 2px solid var(--trust); outline-offset: 2px; }
/* system default: ink */
body.kc-rd.kc-co-shell .kc-co-pill:has(input:checked) {
	border-color: var(--text);
	background: var(--text);
}
body.kc-rd.kc-co-shell .kc-co-pill:has(input:checked) .kc-co-pill-t { color: var(--surface-raised); }
/* the customer's own pick: pine */
body.kc-rd.kc-co-shell .kc-co-seg.is-touched .kc-co-pill:has(input:checked) {
	border-color: var(--trust);
	background: var(--trust);
}
body.kc-rd.kc-co-shell .kc-co-seg.is-touched .kc-co-pill:has(input:checked) .kc-co-pill-t { color: var(--on-trust); }

body.kc-rd.kc-co-shell .kc-co-time {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--kc-space-2);
	margin-top: var(--kc-space-3);
}
body.kc-rd.kc-co-shell .kc-co-time[hidden],
body.kc-rd.kc-co-shell .kc-co-unit[hidden] { display: none; }
body.kc-rd.kc-co-shell .kc-co-sel {
	height: 44px;
	padding: 0 36px 0 12px;
	border: 1px solid var(--field-border);
	border-radius: var(--field-radius);
	background: var(--field-bg);
	font-family: var(--kc-font-body);
	font-size: 14px;
	color: var(--text);
}
body.kc-rd.kc-co-shell .kc-co-sel-rel { flex: 0 0 auto; min-width: 110px; }
body.kc-rd.kc-co-shell .kc-co-sel-time { flex: 1 1 160px; min-width: 0; }
body.kc-rd.kc-co-shell .kc-co-vh {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}
body.kc-rd.kc-co-shell .kc-co-hint {
	flex: 1 1 100%;
	display: block;
	margin-top: 4px;
	font-size: 12px;
	line-height: 1.4;
	color: var(--text-muted);
}
body.kc-rd.kc-co-shell .kc-co-unit { margin-top: var(--kc-space-3); max-width: 240px; }
body.kc-rd.kc-co-shell .kc-co-unit label,
body.kc-rd.kc-co-shell .kc-co-note label,
body.kc-rd.kc-co-shell .kc-cod-cash label {
	display: block;
	margin: 0 0 6px;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	color: var(--text);
}
body.kc-rd.kc-co-shell .kc-co-note-in { min-height: 64px; }

/* --------------------------------------------------------------------------
   §69.7 COD cash field (inside the COD payment box, so it exists only while
   Cash on Delivery is the selected method — WC shows/hides the box).
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell .kc-cod-cash { margin-top: var(--kc-space-3); }
/* Savoy paints its fake radio on EVERY label under #payment (main.css
   `#payment .wc_payment_method label:before`, (1,1,2)); 65-checkout.css §65.4
   cedes only the card's own > label. This nested label needs the same
   ID-weight opt-out, same idiom, same reason. */
body.kc-rd.woocommerce-checkout #payment .kc-cod-cash label::before,
body.kc-rd.woocommerce-checkout #payment .kc-cod-cash label::after {
	content: none !important;
	display: none !important;
}
body.kc-rd.woocommerce-checkout #payment .kc-cod-cash label { padding-left: 0; }
body.kc-rd.kc-co-shell .kc-cod-cash-wrap {
	position: relative;
	display: block;
	max-width: 200px;
}
body.kc-rd.kc-co-shell .kc-cod-cash-cur {
	position: absolute;
	left: 14px;
	top: 50%;
	transform: translateY(-50%);
	font-family: var(--price-font);
	font-size: 14px;
	color: var(--text-muted);
	pointer-events: none;
}
body.kc-rd.kc-co-shell .kc-cod-cash-in {
	width: 100%;
	padding-left: 28px;
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   §69.8 Register-card slot: the card is the points session's; the slot only
   gives it the form's rhythm (full width, one gap below email). Empty slot
   (logged in / registration off) takes no space.
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell .kc-form-slot-row { margin: 0; }
body.kc-rd.kc-co-shell .kc-co-register-slot:empty { display: none; }
body.kc-rd.kc-co-shell .kc-co-register-slot > * { margin-top: var(--kc-space-2); }

/* the inline "Welcome back" login card the points session prepends to the
   form — same card language: it already uses the shared card tokens; only
   clear the top rhythm now that the site header is gone */
body.kc-rd.kc-co-shell form.checkout > .kc-pl-card { margin-top: var(--kc-space-2); }

/* --------------------------------------------------------------------------
   §69.10 (verify O1/O2) Account box + ship-to-different line.
   The template prints WC's "Create an account?" box after the whole billing
   block; the register card that refers to it ("Untick Create an account")
   sits under Email. 69-checkout-shell.js moves the checkbox row
   (p.create-account) to directly under the register slot (DOM move,
   id/handlers intact — inc-login.php's guest-fallback still reads
   #createaccount); the empty .woocommerce-account-fields shell stays in
   col-1 at zero height. Savoy's
   "Shipping details" h3 over the "Ship to a different address?" box is a
   second heading for a checkbox: the h3 goes, the box becomes one quiet
   line. Both tracks keep the box (owner question in the report).
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell .woocommerce-billing-fields__field-wrapper > .form-row.create-account {
	clear: both;
	margin: 0 0 var(--kc-space-2);
}
body.kc-rd.kc-co-shell .form-row.create-account label.checkbox {
	font-size: 13px;
	color: var(--text);
}
body.kc-rd.kc-co-shell #ship-to-different-address > h3 { display: none; }
body.kc-rd.kc-co-shell #ship-to-different-address {
	margin: var(--kc-space-4) 0 0;
	padding: 0;
}
body.kc-rd.kc-co-shell #ship-to-different-address label.checkbox {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	margin: 0;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 500;
	color: var(--text-muted);
}
body.kc-rd.kc-co-shell #customer_details .col-2 { margin-top: 0; }

/* --------------------------------------------------------------------------
   §69.9 Footline + motion
   -------------------------------------------------------------------------- */
body.kc-rd.kc-co-shell .kc-checkout-footline { padding: var(--kc-space-6) 0 var(--kc-space-7); }
@media (prefers-reduced-motion: reduce) {
	body.kc-rd.kc-co-shell .kc-co-sum-toggle svg,
	body.kc-rd.kc-co-shell .kc-co-pill { transition: none; }
}

/* ===== 70-search.css ===== */
/* ==========================================================================
   70-search.css — SURFACE: SEARCH   (Group A · not an SEO surface)
   --------------------------------------------------------------------------
   Owns two things and nothing else:
     (a) the FiboSearch live dropdown  — the panel + rows, desktop and the
         full-screen mobile overlay.
     (b) the search results page       — /?s=<q>&post_type=product : the query
         chip and the empty state. The product GRID on that page is already
         kc-redesign.css §1/§13/§15 and is deliberately untouched here.
   The header search FIELD is kc-redesign.css §14/§22 and is left alone. The
   one exception is the field's *overlay* instance: on mobile FiboSearch moves
   .dgwt-wcas-search-wrapp out of .kc-fibo-header into .dgwt-wcas-om-bar, so
   every §22 rule stops matching there. That instance is dropdown chrome and
   is styled below, scoped to .dgwt-wcas-overlay-mobile so it can never reach
   the header field.

   DOM VERIFIED ON STAGING (2026-08-15, not recalled):
     · GET /                       -> wrapper is
         .dgwt-wcas-search-wrapp.dgwt-wcas-no-submit.dgwt-wcas-style-solaris
         .js-dgwt-wcas-layout-classic.js-dgwt-wcas-mobile-overlay-enabled
       inside #nm-header-search-form.kc-fibo-header. No submit button exists.
     · Inline config `var dgwt_wcas` : show_images 1 · show_price 1 ·
       show_headings 1 · show_details_panel "" (OFF) · show_desc "" ·
       min_chars 2 · mobile_overlay_breakpoint 992 · mobile_overlay_wrapper
       "body" · labels.no_results "\"No results\"".
     · GET /?wc-ajax=dgwt_wcas_ajax_search&s=kush -> 9 suggestions, keys are
       post_id/value/url/type/thumb_html/price only. NO stock field. See §70.6.
     · search.min.js 1.34.0 createProductSuggestion() renders exactly:
         a.dgwt-wcas-suggestion.dgwt-wcas-suggestion-product[data-post-id]
           > span.dgwt-wcas-si > img
           > div.dgwt-wcas-content-wrapp
               > div.dgwt-wcas-st > span.dgwt-wcas-st-title (+<strong> hits)
               > div.dgwt-wcas-meta > span.dgwt-wcas-sp (get_price_html)
       "more_products" -> a.dgwt-wcas-suggestion.dgwt-wcas-suggestion-more
                            > span.dgwt-wcas-st-more > span.dgwt-wcas-st-more-total
       "headline"      -> a.dgwt-wcas-suggestion.dgwt-wcas-suggestion-headline
       "no-results"    -> bare div.dgwt-wcas-suggestion-nores (NO anchor)
     · showMobileOverlay() builds
         div.dgwt-wcas-overlay-mobile > div.dgwt-wcas-om-bar
           > button.dgwt-wcas-om-return
           > .dgwt-wcas-search-wrapp (moved) + .dgwt-wcas-suggestions-wrapp (moved)
       and the panel is appended to <body>, so `body.kc-rd …` reaches it.
     · GET /?s=kush&post_type=product  -> body.kc-rd.search.search-results,
         div.nm-shop-results-bar.is-search > ul > li > a#nm-shop-search-taxonomy-reset
         then ul.nm-products (grid, not mine).
     · GET /?s=zzqqxxvv&post_type=product -> same bar, then
         div.nm-shop-no-products > h3.woocommerce-info. That is the whole
         empty state.

   COMPETING SELECTORS BEATEN (measured, not assumed):
     · kc-redesign.css §14 `.dgwt-wcas-suggestions-wrapp` (0,1,0)!important
       box-shadow:var(--kc-shadow) — a 3px hard button shadow on a floating
       panel. Beaten at (0,2,1)+!important with --panel-shadow.
     · kc-redesign.css §14 `.dgwt-wcas-suggestion:hover` AND
       `.dgwt-wcas-suggestion.dgwt-wcas-suggestion-selected` (0,2,0)!important
       paint BOTH states var(--kc-paper). --kc-paper aliases --surface, which
       is also --panel-bg, so today hover is literally invisible and keyboard
       nav is indistinguishable from hover. Both beaten at (0,4,2)/(0,5,2)
       +!important — !important is unavoidable here, it is the only way past
       an existing !important.
     · ajax-search 1.34.0 `.dgwt-wcas-st` (0,1,0) colour #444 — grey product
       names, against the kit's "names are never grey". Beaten at (0,3,1).
     · ajax-search `.dgwt-wcas-suggestions-wrapp a:focus{outline:0}` (0,2,1)
       removes the focus ring. Beaten at (0,4,2).
     · ajax-search `.dgwt-wcas-has-headings .dgwt-wcas-suggestion` (0,2,0)
       kills every row separator (has-headings is on, show_headings=1).
       Beaten at (0,3,1).
     · ajax-search mobile overlay: `.dgwt-wcas-om-bar button.dgwt-wcas-om-return`
       (0,2,1) 45x45 #ccc — under --btn-min-height and a grey block. Beaten at
       (0,4,2). Its `:focus{outline:0}` beaten at (0,5,2).
     · ajax-search `.dgwt-wcas-overlay-mobile-on .dgwt-wcas-suggestions-wrapp`
       (0,2,0)!important top/height:45px. Beaten at (0,3,1)+!important, and the
       iOS auto-height rule (0,3,0)!important is re-asserted at (0,4,2) so the
       iOS fix is not collateral damage.
     · savoy shop.css `.nm-shop-results-bar a:before` (0,1,2) top:6px and
       savoy-child main.css same at top:8px (twice). Beaten with the theme's
       own id at (1,4,2).
     · savoy shop.css `.nm-shop-no-products h3` (0,1,1) + customizer inline
       `.woocommerce-info` (0,1,0) 16-18px #282828. Beaten at (0,4,2).
   Everything is scoped `body.kc-rd …` so it leaves with the kill switch.
   ========================================================================== */


/* --------------------------------------------------------------------------
   70.1  Dropdown panel
   The panel keeps §14's 2px ink border and 4px radius on purpose — it hangs
   off a field with the same border and radius, and a floating 12px-radius
   card under a 4px field reads detached. Only the shadow changes: a hard
   offset shadow is the *button* affordance, and the kit gives panels their
   own token.
   -------------------------------------------------------------------------- */

body.kc-rd .dgwt-wcas-suggestions-wrapp {
	background: var(--panel-bg);
	padding: var(--kc-space-1) 0;
	/* !important: beats kc-redesign.css §14's own !important. */
	box-shadow: var(--panel-shadow) !important;
	overscroll-behavior: contain;
}

/* Desktop only: the plugin sets no max-height, and 9 rows under a fixed
   header runs off the bottom of the viewport. The overlay (<=992px) manages
   its own height and must not get this. */
@media (min-width: 993px) {
	body.kc-rd .dgwt-wcas-suggestions-wrapp {
		max-height: min(70vh, 560px);
	}
}


/* --------------------------------------------------------------------------
   70.2  Suggestion rows — thumbnail | name | price
   -------------------------------------------------------------------------- */

body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion {
	padding: var(--kc-space-2) var(--kc-space-3);
	/* has-headings strips every separator; a price list needs rules. */
	border-bottom: 1px solid var(--border);
	color: var(--text);
	font-size: 15px;
	line-height: 1.35;
	align-items: center;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion:last-child {
	border-bottom: 0;
}

body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion.dgwt-wcas-suggestion-product {
	min-height: var(--btn-min-height);
	gap: var(--kc-space-3);
}

/* Thumbnail: square, on the kit's image bed, never cropped. */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-si {
	flex: 0 0 44px;
	width: 44px;
	height: 44px;
	background: var(--card-shot-bg);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-sm);
	overflow: hidden;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-si img {
	width: 100%;
	height: 100%;
	max-width: none;
	max-height: none;
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: none;
	object-fit: contain;
}

/* Stack name over price. The desktop panel is only as wide as the header
   field (FiboSearch sets width inline from form.outerWidth; §14 pins that
   field to 230px), so the plugin's side-by-side name/price leaves ~160px for
   both. Stacked works at 230px and at 375px alike, and it puts the money on
   its own line. */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-content-wrapp {
	flex-direction: column;
	align-items: flex-start;
	justify-content: center;
	gap: var(--kc-space-1);
	padding-left: 0;
	min-width: 0;
}


/* --------------------------------------------------------------------------
   70.3  Name and price
   -------------------------------------------------------------------------- */

body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-st {
	color: var(--text);
	font-size: 15px;
	line-height: 1.3;
	font-weight: 500;
	width: 100%;
	overflow: hidden;
}
/* `text-overflow` is the belt for the one-line brace, and it belongs HERE and
   not in §70.13's desktop block where it was first written: -webkit-line-clamp
   already draws the ellipsis in Blink/WebKit, but on a UA that drops
   -webkit-box the element falls back to a single overflow:hidden line and the
   name is cut with NO ellipsis at all — a silent truncation, which is the
   exact failure mode F01 exists to remove. That fallback is not a desktop
   condition, so the belt is not either. (Measured 2026-08-29: the phone
   overlay's title box computed `text-overflow: clip` while the 993+ panel
   computed `ellipsis` — the belt was on one end only.) */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-st .dgwt-wcas-st-title {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
	text-overflow: ellipsis;
	overflow-wrap: anywhere;
	vertical-align: baseline;
}
/* The query hit. Base weight is 500 precisely so the plugin's <strong> 700
   reads as a match signal without needing a colour — a second colour inside
   a shopping row is decoration, and the kit forbids it. */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-st strong {
	font-weight: 700;
	color: var(--text);
}

/* Money: mono, tabular, left-aligned under the name. Colour is already
   var(--kc-ink)!important from §14 — correct, and left alone. */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-sp {
	font-family: var(--price-font);
	font-size: 14px;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	line-height: 1.2;
	text-align: left;
	padding-left: 0;
	white-space: nowrap;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-sp * {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
}
/* Sale pairs, if a sale price ever reaches the dropdown. The kit puts sale
   prices in --urgent and the struck original quiet. §14's !important is on
   .dgwt-wcas-sp itself, so a rule on the descendants wins normally.
   The struck original is --text-muted, not --text-faint: this is money, and
   the kit's rule is that money is never grey. --text-muted is the house
   treatment for a struck price everywhere else (kc-redesign.css
   `li.product .price del` and `.nm-product-summary .price del`, both
   --kc-ink-60, which aliases --text-muted) and it clears AA at 6.2:1 on
   --panel-bg; --text-faint lands at 2.8:1 and fails.
   The size stays below the live price on purpose — 12/14 here mirrors the
   grid's own 14/17 struck-to-live ratio. */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-sp ins {
	color: var(--urgent);
	text-decoration: none;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-sp del {
	color: var(--text-muted);
	opacity: 1;
	font-size: 12px;
}


/* --------------------------------------------------------------------------
   70.4  States — hover, keyboard, focus
   The brief asks for a keyboard highlight that is clearly not hover. It also
   has to survive §14 painting both states the same colour, in !important.
     hover            : stone ground + the name underlines (a link affordance)
     keyboard-selected: stone ground + a pine rail down the left edge
   The rail only ever appears under arrow-key navigation, so the two states
   are never ambiguous — including for someone using both a mouse and keys.
   -------------------------------------------------------------------------- */

body.kc-rd .dgwt-wcas-suggestions-wrapp a.dgwt-wcas-suggestion:hover {
	/* !important: §14 sets background on (0,2,0)!important. */
	background: var(--surface-sunken) !important;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp a.dgwt-wcas-suggestion:hover .dgwt-wcas-st-title {
	text-decoration: underline;
	text-underline-offset: 2px;
}

body.kc-rd .dgwt-wcas-suggestions-wrapp a.dgwt-wcas-suggestion.dgwt-wcas-suggestion-selected {
	/* !important: same §14 declaration, same reason. */
	background: var(--surface-sunken) !important;
	box-shadow: inset 4px 0 0 0 var(--trust);
}

/* The plugin explicitly removes the focus ring on every link in the panel.
   Put it back, to the kit's state-table spec, inset so it is not clipped by
   the panel's own overflow. */
body.kc-rd .dgwt-wcas-suggestions-wrapp a.dgwt-wcas-suggestion:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: -3px;
}


/* --------------------------------------------------------------------------
   70.5  Group headings and the "see all" escape hatch
   -------------------------------------------------------------------------- */

/* show_headings = 1, so a "Products" divider row renders above the group. */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion-headline {
	min-height: 0;
	padding-bottom: 0;
	border-bottom: 0;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion-headline .dgwt-wcas-st {
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: .10em;
	text-transform: uppercase;
	color: var(--text-faint);
	border-bottom: 1px solid var(--border);
	margin-top: var(--kc-space-3);
	padding-bottom: var(--kc-space-2);
}

/* The last row: "See all products… (38)". Not the primary action of this
   view — picking a product is — so it is quiet but unmistakable. Pine, not
   amber, for exactly that reason. */
body.kc-rd .dgwt-wcas-suggestions-wrapp a.dgwt-wcas-suggestion.dgwt-wcas-suggestion-more {
	min-height: var(--btn-min-height);
	justify-content: center;
	color: var(--trust);
	text-transform: none;
	border-bottom: 0;
	border-top: 1px solid var(--border);
}
body.kc-rd .dgwt-wcas-suggestions-wrapp a.dgwt-wcas-suggestion.dgwt-wcas-suggestion-more .dgwt-wcas-st-more {
	font-size: 14px;
	font-weight: 700;
	color: var(--trust);
	text-align: center;
	padding: 0 var(--kc-space-3);
}
body.kc-rd .dgwt-wcas-suggestions-wrapp a.dgwt-wcas-suggestion.dgwt-wcas-suggestion-more .dgwt-wcas-st-more-total {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-weight: 400;
	color: var(--text-muted);
}


/* --------------------------------------------------------------------------
   70.6  Stock status in the dropdown
   --------------------------------------------------------------------------
   HONEST STATE: this is inert today and I am not pretending otherwise.
   The AJAX payload carries post_id/value/url/type/thumb_html/price and
   nothing else — verified against s=kush, where post 33711 "LA Kush Pop"
   comes back as a normal suggestion while the same post renders as
   li.product.outofstock on the results page. 82% of the catalogue is flagged
   outofstock (CLAUDE.md §3.6), so the dropdown is silently offering sold-out
   product on most queries and CSS alone cannot see it.
   The rules below are the receiving end of the one-line server change in my
   needs_lead note: FiboSearch's own renderer already injects
   suggestion.meta_after as raw HTML inside .dgwt-wcas-meta
   (apply3rdPartyPlaceholder("meta_after", …) in search.min.js), and
   .dgwt-wcas-stock.out-of-stock is the plugin's own documented class. The
   moment that field is populated this styles itself. Nothing is hidden.
   -------------------------------------------------------------------------- */

body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-stock {
	display: inline-block;
	margin-top: var(--kc-space-1);
	font-family: var(--label-font);
	/* 12/.10em caps is the kit's Label role exactly. It was 11/.08em, which is
	   off the type scale and under the floor for text a customer has to read
	   to decide — and stock is the decision here, with 82% of the catalogue
	   flagged outofstock (CLAUDE.md §3.6). Mono caps is the least forgiving
	   combination at small sizes, so it does not get to sit below the role. */
	font-size: 12px;
	line-height: 1.4;
	letter-spacing: .10em;
	text-transform: uppercase;
	text-align: left;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-stock.in-stock {
	color: var(--trust);
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-stock.out-of-stock {
	color: var(--text);
	background: var(--surface-sunken);
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-sm);
	padding: var(--kc-space-1) var(--kc-space-2);
}
/* Sold-out rows stay legible and stay clickable — dimmed, never hidden. */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion:has(.dgwt-wcas-stock.out-of-stock) .dgwt-wcas-si {
	opacity: .55;
}


/* --------------------------------------------------------------------------
   70.7  Dropdown: no results
   --------------------------------------------------------------------------
   The plugin renders a bare <div class="dgwt-wcas-suggestion-nores"> holding
   the literal string "No results" — no link, no anchor, nothing to press.
   CSS cannot add a control, and a ::after that *looks* like a link but is not
   focusable would be worse than the dead end it replaces. So: guidance the
   customer can act on in the field they are already typing in, and the panel
   is pre-wired for a real anchor the moment the plugin's "no results" label
   carries one (it is injected with innerHTML, so an <a> works — see
   needs_lead). :not(:has(a)) means the generated line removes itself the
   instant that lands, with no second edit here. Browsers without :has() drop
   the rule and get today's behaviour, which is a safe degradation.
   -------------------------------------------------------------------------- */

body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion-nores {
	padding: var(--kc-space-5) var(--kc-space-4);
	color: var(--text);
	font-size: 16px;
	font-weight: 600;
	line-height: 1.4;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion-nores:not(:has(a))::after {
	content: "Try a shorter phrase, a strain name, or a brand.";
	display: block;
	margin-top: var(--kc-space-2);
	color: var(--text-muted);
	font-size: 14px;
	font-weight: 400;
	line-height: 1.55;
}
/* Pre-styled for the label change: any anchor placed in the no-results
   message becomes the kit's primary control, at full touch size. */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion-nores a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--btn-min-height);
	margin-top: var(--kc-space-3);
	padding: var(--kc-space-3) var(--kc-space-5);
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border: 2px solid var(--btn-primary-border);
	border-radius: var(--kc-radius-md);
	box-shadow: var(--btn-primary-shadow);
	font-size: 15px;
	font-weight: 700;
	text-decoration: none;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion-nores a:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}


/* --------------------------------------------------------------------------
   70.8  Mobile overlay  (<=992px — 86.5% of this store's search traffic)
   --------------------------------------------------------------------------
   FiboSearch replaces the dropdown with a full-screen overlay and moves the
   field into a 45px bar with a 45px grey back button. 45 is under
   --btn-min-height, so the bar goes to 56 and the three plugin numbers that
   depend on it (button width, field width, panel offset) move with it —
   including a re-assert of the plugin's iOS auto-height rule so raising the
   specificity here does not silently disable it.
   -------------------------------------------------------------------------- */

body.kc-rd .dgwt-wcas-overlay-mobile {
	background: var(--panel-bg);
}

body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar {
	height: 56px;
	background: var(--panel-bg);
	border-bottom: 1px solid var(--border);
	align-items: stretch;
}

/* Back. Was a 45px #ccc block with its icon absolutely positioned at 12/12. */
body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar button.dgwt-wcas-om-return {
	width: 56px;
	height: 56px;
	flex: 0 0 56px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: transparent;
	border-right: 1px solid var(--border);
	border-radius: 0;
	color: var(--text);
}
body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar .dgwt-wcas-om-return svg {
	position: static;
	left: auto;
	top: auto;
	width: 22px;
	height: 22px;
	fill: var(--text);
}
/* The plugin's :focus rule sets outline:0 on this button. */
body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar button.dgwt-wcas-om-return:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: -3px;
}

/* The field, only in its overlay incarnation. Reaching it here is safe:
   .kc-fibo-header is not an ancestor once FiboSearch has moved the wrapper,
   and .dgwt-wcas-overlay-mobile never exists around the header field. */
body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-search-wrapp-mobile {
	width: calc(100% - 56px);
}
body.kc-rd .dgwt-wcas-overlay-mobile input[type="search"].dgwt-wcas-search-input {
	/* !important on all three: the plugin pins height/font-size !important and
	   kc-redesign §14 pins border/radius !important. */
	height: 56px !important;
	font-size: 16px !important;
	border: 0 !important;
	border-radius: 0 !important;
	padding: 0 var(--kc-space-4) !important;
	background: var(--field-bg) !important;
	color: var(--text) !important;
}
/* One magnifier is enough, and the back arrow already occupies the left of
   the bar. Dropping it returns ~25px of name width on a 375px screen. */
body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-ico-magnifier {
	display: none;
}
/* WIG: the border-0 restyle above erased the UA focus ring; keyboard users got
   nothing. An inset ring, since the bar is edge-to-edge. (Re-filed from the
   30-side-cart audit, which had this finding mis-keyed.) */
body.kc-rd .dgwt-wcas-overlay-mobile input[type="search"].dgwt-wcas-search-input:focus-visible {
	outline: 2px solid var(--trust);
	outline-offset: -2px;
}

/* The panel fills the rest of the screen: no border, no radius, no shadow. */
body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-suggestions-wrapp {
	top: 56px !important;
	height: calc(100% - 56px) !important;
	max-height: none;
	background: var(--panel-bg);
	border: 0 !important;
	border-radius: 0 !important;
	box-shadow: none !important;
	padding: 0;
	/* Kept separate from the shorthand on purpose: a UA that does not know
	   env() invalidates the whole declaration, and folded into `padding` that
	   would drop us back to the plugin's padding (same caveat 40-nav-menu.css
	   documents). Covers both height branches — the iOS re-assert below only
	   overrides height/max-height and inherits this. */
	padding-bottom: max(0px, env(safe-area-inset-bottom, 0px));
}
/* Re-assert the plugin's iOS behaviour. Its rule is (0,3,0)!important and the
   one above is (0,3,1)!important, so without this the iOS fix would lose. */
html.dgwt-wcas-is-ios body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-suggestions-wrapp {
	height: auto !important;
	max-height: none !important;
}

/* 48px rows, every row type, at the smallest screen we ship to. */
@media (max-width: 992px) {
	body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-suggestion {
		min-height: 56px;
		padding: var(--kc-space-2) var(--kc-space-4);
	}
	/* The headline is itself a .dgwt-wcas-suggestion (search.min.js appends the
	   headline classes to a.classes.suggestion — see the DOM notes above), so
	   the rule right above matches it too. Both it and §70.5's headline rule
	   are (0,3,1) and a media query adds no specificity, so without this the
	   later one wins and the "Products" divider becomes a 56px block with 8px
	   of bottom padding — a whole suggestion row of a 375px screen spent on a
	   label the plugin marks pointer-events:none. (0,4,1) puts §70.5 back,
	   keeping the overlay's 16px gutter so the divider aligns with the rows. */
	body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-suggestion.dgwt-wcas-suggestion-headline {
		min-height: 0;
		padding: var(--kc-space-2) var(--kc-space-4) 0;
	}
	body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-si {
		flex-basis: 48px;
		width: 48px;
		height: 48px;
	}
	body.kc-rd .dgwt-wcas-overlay-mobile a.dgwt-wcas-suggestion.dgwt-wcas-suggestion-more {
		min-height: 56px;
	}
	body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-suggestion-nores {
		padding: var(--kc-space-5) var(--kc-space-4);
	}
}


/* ==========================================================================
   70.9  Results page — the query chip
   --------------------------------------------------------------------------
   Everything below is scoped body.kc-rd.search so it cannot collide with the
   category surface, which shares .nm-shop-results-bar via .is-category.
   The chip is savoy's own "remove this filter" control: an x glyph plus
   "Search results for <q>", and nm-shop.min.js sends it to data-shop-url.
   That reading is correct, so it stays a chip — it is made legible, given a
   real touch target, and left secondary.
   ========================================================================== */

body.kc-rd.search .nm-shop-results-bar.is-search {
	margin-bottom: var(--kc-space-5);
	overflow: visible;
}
body.kc-rd.search .nm-shop-results-bar.is-search ul {
	margin: 0;
}
body.kc-rd.search .nm-shop-results-bar.is-search ul li {
	display: block;
	margin: 0;
}

body.kc-rd.search .nm-shop-results-bar.is-search a#nm-shop-search-taxonomy-reset {
	display: inline-flex;
	align-items: center;
	/* savoy-child leaves flex-wrap:wrap on this anchor. With the query span
	   held at its full width (see below) a long query would break onto its own
	   line inside the chip before it ever truncated. */
	flex-wrap: nowrap;
	min-height: var(--btn-min-height);
	max-width: 100%;
	padding: var(--kc-space-2) var(--kc-space-4) var(--kc-space-2) 38px;
	background: var(--surface-sunken);
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	color: var(--text-muted);
	font-size: 14px;
	line-height: 1.3;
}
/* savoy pins the x glyph to top:6px, savoy-child to top:8px in two places —
   all of them assume a short pill. Centre it against a 48px control. */
body.kc-rd.search .nm-shop-results-bar.is-search a#nm-shop-search-taxonomy-reset:before {
	top: 50%;
	left: 14px;
	transform: translateY(-50%);
	font-size: 14px;
	color: var(--text-muted);
}
/* The query itself is the one thing here worth reading. min-width:0 is what
   makes the ellipsis actually fire: the chip is inline-flex, so this span is a
   flex item with the default min-width:auto, and savoy's inherited
   white-space:nowrap makes its min-content width the whole query. It can then
   never shrink, so it never overflows its own box, so text-overflow has no
   overflow to act on — the clip happens on the anchor instead and the word is
   cut mid-glyph with no ellipsis. Same idiom as .dgwt-wcas-content-wrapp in
   §70.2. nowrap is restated here rather than inherited so the truncation does
   not depend on a theme rule we do not own. */
body.kc-rd.search .nm-shop-results-bar.is-search a#nm-shop-search-taxonomy-reset span {
	color: var(--text);
	font-weight: 600;
	margin-left: var(--kc-space-1);
	min-width: 0;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
body.kc-rd.search .nm-shop-results-bar.is-search a#nm-shop-search-taxonomy-reset:hover {
	color: var(--text);
	border-color: var(--text);
}
body.kc-rd.search .nm-shop-results-bar.is-search a#nm-shop-search-taxonomy-reset:hover:before {
	color: var(--text);
}
body.kc-rd.search .nm-shop-results-bar.is-search a#nm-shop-search-taxonomy-reset:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}


/* --------------------------------------------------------------------------
   70.10  Results page — empty state
   --------------------------------------------------------------------------
   Shipped markup is one line: div.nm-shop-no-products > h3.woocommerce-info
   reading "No products were found matching your selection." — 16px grey, no
   exit.

   SPECIFICITY, MEASURED (w3-search, 2026-08-29, /?s=$99 at 1280 and 375):
   a product search page carries BOTH body classes — `body.archive.search` —
   so 23-catalog-page.css §23.4 `body.kc-rd.archive #nm-shop
   .nm-shop-no-products` (1,3,1) matches here too and outranked this block's
   (0,3,1). Computed on the live page before this edit:
       padding: 32px 16px 88px   (23-catalog's, not this file's)
       text-align: center        (23-catalog's; nothing here ever set it)
   Two consequences the screenshots show: 88px of dead space under the last
   line, and a CENTRED box sitting directly under the LEFT-aligned jump card
   of §70.14 — two stacked boxes on one page disagreeing about their axis.
   The box selector below now carries `#nm-shop` as well, so it is (1,3,1)
   against (1,3,1) and wins on order (parts load 23 → 70; verified in
   document.styleSheets on the live page). No !important, and the category
   archives 23-catalog actually aims at are untouched: every rule here is
   scoped to `.search`, which a category archive never has.

   The h3 rule this block used to carry (20px/600/--text) is deleted rather
   than re-specified. It never won — 23-catalog's (1,3,2) h3 rule renders it
   15px muted — and 15px muted is now the RIGHT answer: since §70.14 the jump
   card above carries the headline of this state, and a second 20px sentence
   under it would compete for the same job. One dead declaration removed, one
   live one left where it is.
   -------------------------------------------------------------------------- */

body.kc-rd.search #nm-shop .nm-shop-no-products {
	display: block;
	max-width: var(--measure);
	margin: var(--kc-space-4) 0 var(--kc-space-7);
	padding: var(--kc-space-6) var(--kc-space-5);
	background: var(--surface-sunken);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-lg);
	text-align: left;
}
/* The guidance line. It used to end "…pick a category from the Shop by
   category list", which named a component that DOES NOT RENDER below 992px:
   .kc-catnav has no offsetParent on a 375px viewport (measured on
   /?s=$99&post_type=product, both viewports — desktop "visible", phone
   "hidden"). On the device that is 86.5% of this store's traffic the empty
   state pointed at an exit the reader could not see. The replacement names
   no component, is true in both layouts, and states the actual rule the
   customer just ran into — which is also what the jump card above is there
   to soften. Copy is listed for the owner in docs/redesign/w3-search.md. */
body.kc-rd.search .nm-shop-no-products::after {
	content: "Try a shorter phrase, a strain name, or a brand — search matches product names, not category names.";
	display: block;
	max-width: 46ch;
	margin-top: var(--kc-space-3);
	color: var(--text-muted);
	font-size: 15px;
	line-height: 1.6;
}

/* In the empty state the chip is the only control on screen, so give it the
   panel ground to sit on rather than the same stone as the empty-state card.
   :has(+ …) targets exactly the case where .nm-shop-no-products is the next
   sibling — which is the shipped DOM. Unsupported browsers drop the rule and
   keep the default chip. */
body.kc-rd.search .nm-shop-results-bar.is-search:has(+ .nm-shop-no-products) a#nm-shop-search-taxonomy-reset {
	background: var(--surface-raised);
}


/* --------------------------------------------------------------------------
   70.11  Motion
   Only the back button and the chip animate, and both are colour-only, but
   the kill switch should never be the thing that honours a system setting.
   The !important here is the one in this file that does NOT name a measured
   competitor, and that is deliberate: a reduced-motion reset has to outrank
   declarations that do not exist yet (customizer inline CSS, a plugin bump,
   RUCSS re-serialising the sheet), and specificity loses to any future
   !important. Today nothing competes — savoy's `a{transition:color .2s}` is
   (0,0,1) — so this costs nothing and is not re-audited on each update.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar button.dgwt-wcas-om-return,
	body.kc-rd.search .nm-shop-results-bar.is-search a#nm-shop-search-taxonomy-reset {
		transition: none !important;
	}
}


/* ==========================================================================
   70.10  Category rows  (search-0827)
   --------------------------------------------------------------------------
   inc-search.php turns FiboSearch's category group on (show_product_tax_
   product_cat) and pushes the mapped category first, so the panel now opens
   with a "Categories" group above "Products". search.js renders each as
     a.dgwt-wcas-suggestion.dgwt-wcas-suggestion-tax.dgwt-wcas-suggestion-cat
       > div.dgwt-wcas-st > span.dgwt-wcas-st-title
                          + span.dgwt-wcas-st-breadcrumbs ("in Weed")
   Measured before this block (375, inline panel): a category row without a
   breadcrumb was 37px tall — under the 44px touch floor that every other row
   in this panel already meets. The breadcrumb was the plugin's grey 13px.
   ========================================================================== */

body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion.dgwt-wcas-suggestion-tax {
	min-height: var(--btn-min-height);
	align-items: center;
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion-tax .dgwt-wcas-st {
	font-weight: 600;
}
/* "in Weed": the kit's label role, quiet, on its own line under the name. */
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion-tax .dgwt-wcas-st-breadcrumbs {
	display: block;
	margin-top: 2px;
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: .02em;
	line-height: 1.3;
	color: var(--text-muted);
}
body.kc-rd .dgwt-wcas-suggestions-wrapp .dgwt-wcas-suggestion-tax .dgwt-wcas-st-breadcrumbs .dgwt-wcas-st-label-in {
	color: var(--text-faint);
}
/* Same hover affordance as a product row: the name underlines. */
body.kc-rd .dgwt-wcas-suggestions-wrapp a.dgwt-wcas-suggestion-tax:hover .dgwt-wcas-st-title {
	text-decoration: underline;
	text-underline-offset: 2px;
}

@media (max-width: 992px) {
	body.kc-rd .dgwt-wcas-overlay-mobile .dgwt-wcas-suggestion.dgwt-wcas-suggestion-tax {
		min-height: 56px;
	}
}


/* ==========================================================================
   70.11  Mobile overlay entry point  (search-0827)
   --------------------------------------------------------------------------
   Under 992px FiboSearch prepends an invisible <a class="js-dgwt-wcas-enable-
   mobile-form"> over the header field; a tap on it opens the full-screen
   overlay that §70.8 styles. The plugin positions it
     .dgwt-wcas-enable-mobile-form { position:absolute; inset:0; height:100% }
   at (0,1,0). Savoy-child's header rule `#nm-header li.menu-item a
   { position:relative }` (1,1,2) wins on every anchor inside the header <li>,
   and savoy's `.nm-menu li a { display:inline-block }` (0,2,2) follows —
   measured on staging 2026-08-27: trigger rect [15,113,341,0], computed
   position:relative height:0, and elementFromPoint() over the field returns
   the INPUT. Net effect: no thumb ever reaches the trigger, the overlay never
   opens, and phones get the desktop dropdown pinned under a 40px field.
   Same family as shell-v3 §6 #1 (theme header rules hijacking FiboSearch's
   own elements). Restored here at (1,3,2), scoped to the overlay breakpoint.
   ========================================================================== */

@media (max-width: 992px) {
	body.kc-rd #nm-header-search-form.kc-fibo-header a.js-dgwt-wcas-enable-mobile-form {
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		display: block;
		padding: 0;
		margin: 0;
		z-index: 100;
	}
}


/* ==========================================================================
   §70.12  THE HEADER SEARCH FIELD BECOMES A 44px ICON BELOW 992  (nav-0827)
   --------------------------------------------------------------------------
   POSITION ONLY — this section does not touch the engine, the suggestion
   panel, the overlay's interior, or any of §70.1–§70.11. It changes where the
   header's search control sits in the new one-row mobile header (45-header-fix
   §45.4) and nothing else. inc-search.php is untouched.

   The old mobile header gave the field a whole second row (341x40 at y=98).
   That row is what made #nm-header 112px tall. Collapsed to a 44x44 cell the
   header is 64px — 48px of a 812px phone screen given back above the fold.

   WHY THIS DOES NOT COST THE CUSTOMER A SEARCH BOX. Below 992 FiboSearch does
   not use this field at all: it lays a transparent <a
   class="js-dgwt-wcas-enable-mobile-form"> over the form and opens a
   FULL-SCREEN overlay with its own, larger input (measured in §2.4: 319x56
   field, 56x56 back button, 56px rows — every one of them bigger than the
   40px strip this replaces). §70.11 is what made that trigger reachable in
   the first place. So the inline field below 992 was always a button that
   looked like a text box; this makes it look like the button it is.
   ⚠️ It IS a discoverability trade — nativenic's own header file records the
   opposite call ("iHerb/Chewy never hide search on mobile; searchers are the
   highest-intent visitors"). Put to the owner in docs/redesign/nav-0827.md;
   the fallback is one declaration (restore `flex: 0 0 100%` on the li in
   §45.4.2 and this block's width) and is written up there.

   The input is kept in the DOM at full size-zero rather than display:none:
   the plugin binds to it, reads it, and clones its value into the overlay.
   -------------------------------------------------------------------------- */
@media (max-width: 991px) {

	body.kc-rd #nm-header-search-form.kc-fibo-header {
		width: 44px;
		height: 44px;
		min-width: 0;
		margin: 0 !important;           /* [beats kc-redesign.css §19 mobile !important] */
		flex: 0 0 44px;
	}
	body.kc-rd #nm-header-search-form.kc-fibo-header .dgwt-wcas-search-wrapp {
		width: 44px;
		min-width: 0;
		max-width: 44px;
		height: 44px;
		position: relative;
	}
	body.kc-rd #nm-header-search-form.kc-fibo-header .dgwt-wcas-search-form,
	body.kc-rd #nm-header-search-form.kc-fibo-header .dgwt-wcas-sf-wrapp {
		width: 44px;
		height: 44px;
	}

	/* The field: present, focusable by the plugin, invisible. No border, no
	   ground, no glyph of its own — the magnifier below is the only ink. */
	body.kc-rd #nm-header-search-form.kc-fibo-header .dgwt-wcas-search-input {
		width: 44px !important;          /* [beats kc-redesign.css §19 !important] */
		height: 44px !important;         /* [beats kc-redesign.css §19 !important] */
		padding: 0 !important;           /* [beats kc-redesign.css §19 !important] */
		font-size: 0 !important;         /* [beats kc-redesign.css §19 !important] */
		border: 0 !important;            /* [beats kc-redesign.css §14 !important] */
		background: transparent !important;
		box-shadow: none !important;
		color: transparent;
	}
	body.kc-rd #nm-header-search-form.kc-fibo-header .dgwt-wcas-preloader,
	body.kc-rd #nm-header-search-form.kc-fibo-header .dgwt-wcas-voice-search { display: none; }

	/* The magnifier is the plugin's own SVG, re-parked in the middle of the
	   cell. currentColor keeps it on the header's ink token like its two
	   neighbours (the account glyph and the bag). */
	body.kc-rd #nm-header-search-form.kc-fibo-header .dgwt-wcas-ico-magnifier {
		position: absolute;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
		width: 20px;
		height: 20px;
		margin: 0;
		opacity: 1;
		fill: var(--text);
	}

	/* §70.11 already gives this trigger inset:0 of its wrapper; that wrapper is
	   now 44x44, so the tap target is 44x44. Restated as a floor, not a
	   duplicate: if §70.11 is ever consolidated away this still holds. */
	body.kc-rd #nm-header-search-form.kc-fibo-header a.js-dgwt-wcas-enable-mobile-form {
		min-width: 44px;
		min-height: 44px;
	}
}


/* ==========================================================================
   70.13  THE DESKTOP DROPDOWN IS NOT THE FIELD  (fix2-search-nav, F01)
   --------------------------------------------------------------------------
   MEASURED DEFECT (1280x900, typing "gummies", Playwright):
     wrapper inline style = "position:absolute; max-height:600px;
       z-index:999999999; width:230px; top:92.5px; left:952px"
     six product rows, every title box 134px wide and clamped to 2 lines:
       "200mg THC Gummies - Lullab…"  "200mg THC Gummies - Prepp…"
       "200mg THC Gummies - …"        "500mg THC Gummies - Tropic…"
     The clamp lands on the flavour — i.e. on the only part of the name that
     tells these six rows apart. Three consecutive rows read identically.

   ROOT CAUSE (plugin source, read, not guessed):
     ajax-search-for-woocommerce 1.34.0 assets/js/search.js:2675
       adjustContainerWidth() { …baseWidth = $baseElement.outerWidth();
                                $suggestions.css('width', baseWidth+'px'); }
     $baseElement is getForm() — the HEADER FORM. So the panel is not sized by
     the panel's own needs, it is a mirror of a 230px input. It is re-applied
     inline on every open and on every resize, so there is nothing to "fix
     once" in the DOM.
     The plugin does read a `sug_width` option (includes/Helpers.php:1519) but
     it only feeds `options.width`, and adjustContainerWidth overwrites the
     inline width unconditionally afterwards — that setting is a dead end in
     this version. Checked before writing CSS.

   WHY min-width AND NOT width/!important:
     `min-width` is a different property from `width`, and the used width is
     clamp(min-width, width, max-width). A stylesheet min-width therefore beats
     an inline width WITHOUT !important and without fighting the plugin's JS,
     which keeps writing the same 230px happily underneath.

   WHY THE FIELD IS NOT WIDENED INSTEAD (the obvious alternative, measured and
   rejected): at 1280 the main menu ends at x=857 and the icon cluster starts
   at x=897 — 40px of slack. At 1024 the same two numbers are 651 and 652:
   ONE pixel. 45-header-fix.css §B already lets the search li shrink to 150px
   to protect the menu's ink. There is no room to widen the field on the
   narrowest desktop the panel ever renders at, so the field stays and only the
   panel grows.

   WHY IT GROWS LEFTWARD:
     The plugin pins the panel's `left` to the form's left edge, so a wider
     panel grows to the RIGHT, off the viewport (952 + 400 = 1352 > 1280 =
     horizontal overflow, which this project treats as a hard failure). The
     negative margin re-hangs the panel from the field's RIGHT edge instead,
     into the header's empty middle. It is arithmetic, not taste:
       panel.left = form.left + (--kc-sug-anchor-w − --kc-sug-w)
       panel.right = form.left + --kc-sug-anchor-w = the field's own right edge
     Both custom properties are declared here rather than guessed elsewhere;
     --kc-sug-anchor-w is the field's rendered width, set by kc-redesign.css
     §14 and re-asserted by 45-header-fix.css §B (li width:236px, 3px padding
     each side). If that li is ever squeezed below its 236px the panel's right
     edge lands at most 80px past the field — still inside the bag icon's own
     99px column, so it cannot overflow the viewport at any desktop width.
     Verified at 992 / 1024 / 1180 / 1280: scrollWidth == clientWidth.

   993px, matching §70.1: below it the panel is the full-screen mobile overlay
   (375px wide, its own width machinery) and must not get either declaration.
   ========================================================================== */

@media (min-width: 993px) {
	body.kc-rd .dgwt-wcas-suggestions-wrapp {
		--kc-sug-w: 400px;
		--kc-sug-anchor-w: 230px;
		min-width: var(--kc-sug-w);
		margin-left: calc(var(--kc-sug-anchor-w) - var(--kc-sug-w));
	}

	/* The 2-line clamp stays — a dropdown row is not a place for a four-line
	   title — but at 304px of title box instead of 134px the longest name in
	   the catalogue ("200mg THC Gummies - Lullaby Lavender (Night) by WILLO1",
	   53 characters) fits inside it, so the clamp stops firing on the rows
	   that need to be told apart. Re-measured 2026-08-29 at 993 / 1024 / 1180
	   / 1280 / 1440: panel 400px, right edge == the field's right edge, title
	   box 304px, scrollHeight == height on every product row (nothing clipped)
	   and scrollWidth == clientWidth on the document at every one of those
	   widths.
	   The `text-overflow: ellipsis` belt that used to live in this block has
	   moved to §70.3, unconditionally: the -webkit-box fallback it guards is
	   not a desktop-only condition, and the phone overlay was computing
	   `text-overflow: clip` without it. */
}


/* ==========================================================================
   70.14  RESULTS PAGE — THE CATEGORY JUMP CARD  (fix2-search-nav, F02)
   --------------------------------------------------------------------------
   Markup owner: inc-search.php §8 (woocommerce_no_products_found, priority 5,
   so the card prints ABOVE savoy/woocommerce/loop/no-products-found.php's
   div.nm-shop-no-products, which runs at 10).

   The defect it answers: typing "$99" suggests the category "OZ Deal $99" and
   pressing Enter returns 0 products, because the suggestion matched a CATEGORY
   NAME and the results page only searches product titles. Same for "99",
   "oz", "28g" and every other shelf word. The panel promised something the
   next page did not have.

   Sizing note: the card is the only thing on the page in that state, and on
   mobile it is now the only exit too — .kc-catnav ("Shop by category") is not
   rendered at all below 992 (offsetParent === null, measured), which is why
   the empty-state sentence that pointed at it is replaced in §70.10's
   ::after below.
   ========================================================================== */

body.kc-rd.search .kc-search-jump {
	max-width: var(--measure);
	margin: var(--kc-space-4) 0 var(--kc-space-4);
	padding: var(--kc-space-5);
	background: var(--surface-raised);
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-lg);
}
body.kc-rd.search .kc-search-jump__lead {
	margin: 0 0 var(--kc-space-4);
	color: var(--text);
	font-family: var(--kc-font-body);
	font-size: 17px;
	font-weight: 600;
	line-height: 1.35;
}
body.kc-rd.search .kc-search-jump__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--kc-space-3);
	margin: 0;
	padding: 0;
	list-style: none;
}
/* One real button per matching shelf. The floor is --btn-min-height because
   86.5% of this store's traffic is a thumb, and in this state the card holds
   every control on the screen. */
body.kc-rd.search .kc-search-jump__cta {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 2px;
	min-height: var(--btn-min-height);
	padding: var(--kc-space-2) var(--kc-space-4);
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border: 2px solid var(--btn-primary-border);
	border-radius: var(--kc-radius-md);
	box-shadow: var(--btn-primary-shadow);
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 600;
	line-height: 1.2;
	text-decoration: none;
}
body.kc-rd.search .kc-search-jump__cta:hover,
body.kc-rd.search .kc-search-jump__cta:focus {
	color: var(--btn-primary-text);
	text-decoration: none;
}
body.kc-rd.search .kc-search-jump__cta:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
}
/* The stock line is the second row of the same button, never a second line of
   the label: the label is a proper noun (a category name that may itself
   contain a price, e.g. "OZ Deal $99") and the count is data. Data font, one
   step down, so the button still carries exactly one headline. */
body.kc-rd.search .kc-search-jump__n {
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: .02em;
	text-transform: lowercase;
	opacity: .75;
}
/* The all-products fallback is the same control at a lower voice: it is an
   escape hatch, not the recommendation. */
body.kc-rd.search .kc-search-jump--fallback .kc-search-jump__cta {
	background: var(--surface-sunken);
	color: var(--text);
	box-shadow: none;
	border-color: var(--border-strong);
}
body.kc-rd.search .kc-search-jump--fallback .kc-search-jump__cta:hover,
body.kc-rd.search .kc-search-jump--fallback .kc-search-jump__cta:focus {
	color: var(--text);
}

/* The empty-state box now sits UNDER the card, so it is guidance rather than
   the whole answer. Its sentence and its box geometry are both owned by
   §70.10 — this section used to re-declare the ::after content down here as a
   second, later copy of the same (0,4,2) selector, which rendered correctly
   only because of source order and left a stale sentence live in the file
   above it. One declaration now, in §70.10, where the rest of that box lives.

   GUTTER, both boxes, below 993 only. Measured 2026-08-29 at 375: every
   ancestor from .kc-search-jump up to <body> is 375px wide with 0 padding, so
   the card and the empty-state box both rendered x=0 → right=375 — a bordered,
   radiused card with its 1px border sitting ON both screen edges. The product
   grid IS full-bleed here by design (li.product measures x=0 → 188), and
   that is fine for a borderless tile; it is not fine for a card that draws
   its own edge. --gutter is the site's own token (clamp(16px, 5vw, 32px) —
   18.75px at 375), the same one 40-nav-menu / 69-checkout-shell / 87-city
   use, so this introduces no new number. Desktop is untouched: there the
   sidebar column already puts both boxes at x=320. */
@media (max-width: 992px) {
	body.kc-rd.search .kc-search-jump,
	body.kc-rd.search #nm-shop .nm-shop-no-products {
		margin-left: var(--gutter);
		margin-right: var(--gutter);
	}
}

/* --------------------------------------------------------------------------
   70.R Regression armour (2026-09-01 production incident): FiboSearch's
   settings page lists Pirx FIRST in the Style select, so when the stored
   key is absent the admin UI shows Pirx while the front-end renders the
   code default Solaris — and ANY save of that page silently writes
   search_style=pirx. Pirx force-enables a submit button the collapsed
   44px header icon and the overlay bar were never designed to hold (the
   flying yellow magnifier). search_style is now written explicitly as
   solaris on both sites, so this rule is a no-op today; it exists so a
   future flip degrades to "slightly different input skin" instead of a
   broken header. Enter still submits the form natively.
   -------------------------------------------------------------------------- */
body.kc-rd .kc-fibo-header .dgwt-wcas-search-submit,
.dgwt-wcas-om-bar .dgwt-wcas-search-submit { display: none !important; }

/* ===== 80-notices.css ===== */
/* ==========================================================================
   80 — NOTICES · EMPTY STATES · 404            surface owner: notices session
   --------------------------------------------------------------------------
   Group A (no SEO surface). Presentation only: no markup, no copy, no URLs.
   Everything is scoped `body.kc-rd` so it leaves with the kill switch.

   DOM VERIFIED ON STAGING (curl, basic auth, 2026-08-15). Not guessed —
   Savoy's notice markup is three different shapes depending on the path:

     a) shop / category / PDP
        #nm-shop-notices-wrap                       (always present, empty)
          > div.nm-shop-notice.woocommerce-message[role=alert]
              > span > i.nm-font.nm-font-check + text + a.button.wc-forward

     b) cart (and every Woo notices slot)
        div.woocommerce > div.woocommerce-notices-wrapper
          > ul.nm-shop-notice.woocommerce-error[role=alert]
              > li > span > i.nm-font.nm-font-close + span(+ .amount)

     c) empty cart
        div.nm-cart-empty
          > ul.nm-shop-notice.woocommerce-error      (theme-hidden)
          > p.icon > i.nm-font.nm-font-close2
          > p.return-to-shop > a.button.wc-backward
          > div.woocommerce-notices-wrapper          (empty)
          > div.wc-empty-cart-message
              > div.nm-shop-notice.woocommerce-info > span   <-- NO <i>

     d) checkout, cart below minimum
        div.woocommerce > div.error-message > p + p > a.button.wc-backward
        (this block has ZERO css anywhere in theme, child theme or plugins)

     e) checkout inline toggles — DELIBERATELY OUT OF SCOPE
        ul.nm-checkout-login-coupon > li > div.nm-shop-notice.woocommerce-info
        ("Returning customer?" / "Have a coupon?"). These carry the notice
        classes but are inline form controls, not feedback. Every rule below
        is keyed off a *container* so these are never touched — that is the
        only reason the selectors are as long as they are.

     f) 404  (body.error404)
        div.nm-page-not-found > div.nm-row > div.col-xs-12
          > img[src=savoy/assets/img/404.svg]  (natural size 347x571)
          > div.desc > h2 > span"404" + "Page not found." / p / a.button

   COMPETING SELECTORS BEATEN (all measured from the served stylesheets):
     savoy/shop.css
       .nm-shop-notice                                   (0,1,0) padding/centre
       .nm-shop .nm-shop-notice:last-child               (0,3,0) padding
       .woocommerce-checkout .woocommerce > .nm-shop-notice (0,3,0) padding
       .nm-shop-notice a { display:none }                (0,1,1) hides recovery
       .woocommerce-error .wc-backward { display:none }  (0,2,0) hides recovery
       .nm-cart-empty .nm-shop-notice { display:none }   (0,2,0) hides the words
       .woocommerce-message { color:#03a678 }            (0,1,0)
       .woocommerce-error   { color:#f27362 }            (0,1,0)
       .nm-shop-notice > i / span i                      (0,1,1)/(0,1,2)
     savoy-child/style.css
       .woocommerce-cart .nm-shop-notice  margin !important  (0,2,0)+!
       body.woocommerce-cart .nm-shop-notice margin-top !imp (0,2,1)+!
       .nm-cart-empty p.icon / p.return-to-shop          (0,2,2)
     savoy-child/assets/css/main.css
       * { outline: 0 none }                             (0,0,0) kills focus
       .nm-page-not-found .desc { margin-left:100px; width:100% }  (0,2,0)
       .nm-page-not-found .desc h2 span { font-size:98px; #AA2934 } (0,3,0)
       .nm-page-not-found .desc .button { 176x56; #AA2934 }         (0,3,0)
     Savoy customizer inline <style class="nm-custom-styles">
       .woocommerce-message,.woocommerce-info,.woocommerce-error {18px} (0,1,0)
       .button { color:#fff; background:#282828 }        (0,1,0)
       body { color:#777777 }                            (0,0,1)

   Specificity wins are structural, not shouted: every base rule lands at
   (0,3,1) or better. !important appears in exactly two declaration blocks
   below (80.1's cart-notice margins and 80.4's empty-cart sentence), both
   purely to beat the same pair of existing !importants, and both labelled
   where they sit. Nothing else in this file shouts.

   NOT TOUCHED ON PURPOSE (belongs to another surface — see the report):
     #nm-widget-panel .woocommerce-mini-cart li.empty   (side cart)
     .nm-shop-no-products > h3.woocommerce-info         (search / category)
     .nm-validation-inline-notices .form-row:after      (checkout fields)
     .woocommerce-thankyou-order-received               (checkout)
     .nm-cart-empty layout, p.icon, p.return-to-shop and its CTA
       — 60-cart-page.css:815 claims these and cedes ".nm-shop-notice chrome"
         to this file. Boundary honoured in both directions: everything below
         inside .nm-cart-empty is notice chrome and nothing else.
     the paint/shape/state of any .button
       — 20-buttons.css owns the button vocabulary cross-surface. The only
         control repainted here is the 404 anchor, because the child theme
         pins it at (0,3,0) — above 20-buttons' (0,2,1) base — so it stays
         crimson otherwise. Restated per its §20 note: a surface that needs a
         new variant should add .kc-btn-primary in markup; CSS-only, this is
         the available move. Flagged for the lead.
   ========================================================================== */

body.kc-rd {
	/* Height of the reserved notice slot, derived from the card built in 80.1
	   at TWO lines of text — not one. Sized for one line (the 84px this used to
	   be) it under-reserved the notice this cart actually shows most: Woo's
	   remove-item notice interpolates the product title, and this store's
	   titles are long ("1000mg THC Grape Goo (Night) Gummies by Willo"), so
	   "… removed. Undo?" wraps. At the 375px target the card's text column is
	   ~305px, which at 15px/1.5 Poppins is ~40-45 characters per line. The
	   one-line slot absorbed the first line and the page still moved ~21px
	   under the thumb — the exact shift 80.3 exists to prevent.

	   Written as the sum rather than a magic number so the dependency is
	   visible: it is only correct while it tracks 80.1's numbers. If the card's
	   padding, the label size or the type scale moves, this moves with it or
	   the reservation stops absorbing the shift it exists to absorb.

	   The terms, in order:
	     var(--kc-space-3) * 2   card padding, top + bottom      24
	     12px * 1.3              the state label, size x l-h      15.6
	     var(--kc-space-1)       label -> text gap                 4
	     2 * (15px * 1.5)        TWO lines of notice text         45
	     2px                     card border, top + bottom         2
	     var(--kc-space-4)       the card's own bottom margin     16
	                                                            ------
	                                                            106.6px
	   Annotated here rather than inline: comments inside a custom property's
	   value are carried through substitution, and this declaration fails
	   silently — an unparsed calc drops min-height back to auto and the
	   reservation just quietly stops existing. Nothing in the calc but maths.
	   Verified 106.6px computed. */
	--kc-notice-slot: calc(var(--kc-space-3) * 2 + (12px * 1.3) + var(--kc-space-1) + 2 * (15px * 1.5) + 2px + var(--kc-space-4));
}


/* --------------------------------------------------------------------------
   80.1  The notice card — one box, three states
   --------------------------------------------------------------------------
   Savoy centres notices at 18px with 38px of top padding and no box at all,
   so a cart error reads as a stray sentence. One card, left-aligned (centred
   error text is the hardest thing to read on a 375px screen), accent carried
   on the left edge the way the brand kit's own evidence callout does it.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice,
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice,
body.kc-rd .wc-empty-cart-message > .nm-shop-notice,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice,
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice,
body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error,
body.kc-rd.woocommerce-checkout .woocommerce > .error-message {
	position: relative;
	box-sizing: border-box;
	margin: 0 0 var(--kc-space-4);
	padding: var(--kc-space-3) var(--kc-space-4);
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-left: 4px solid var(--border-strong);
	border-radius: var(--card-radius);
	color: var(--text);
	font-size: 15px;
	line-height: 1.5;
	text-align: left;
	list-style: none;
	/* Woo interpolates raw customer input ('Coupon "%s" does not exist!');
	   an unbroken token must wrap inside the card, not extend the page's
	   scrollable overflow. Same treatment 70-search.css uses for
	   user-supplied strings. */
	overflow-wrap: anywhere;
}

/* The state word. This is the "never colour alone" guarantee: it is real
   text, so it survives colour blindness, forced-colours mode and a printed
   page. display:block puts it on its own line in both the <ul> and the <div>
   shapes, which is why the two markups end up looking identical.

   12px is the brand kit's own label step ("Label 12 · .10em caps · Data").
   It was 11px, which was both off-spec and below the size at which the AA
   floor for this word is negotiable — and this word is the only non-colour
   carrier of the state, so it does not get to be marginal on either axis. */
body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice::before,
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice::before,
body.kc-rd .wc-empty-cart-message > .nm-shop-notice::before,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice::before,
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice::before,
body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error::before,
body.kc-rd.woocommerce-checkout .woocommerce > .error-message::before {
	content: "Note";
	display: block;
	margin-bottom: var(--kc-space-1);
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: 0.12em;
	line-height: 1.3;
	text-transform: uppercase;
	color: var(--text-muted);
}

/* Success — pine. Reads "confirmed", never celebratory. */
body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice.woocommerce-message,
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-message,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice.woocommerce-message,
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice.woocommerce-message {
	border-left-color: var(--trust);
}
body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice.woocommerce-message::before,
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-message::before,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice.woocommerce-message::before,
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice.woocommerce-message::before {
	content: "Done";
	color: var(--trust);
}

/* Error — tomato. */
body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice.woocommerce-error,
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-error,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice.woocommerce-error,
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice.woocommerce-error,
body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error,
body.kc-rd.woocommerce-checkout .woocommerce > .error-message {
	border-left-color: var(--urgent);
}
body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice.woocommerce-error::before,
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-error::before,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice.woocommerce-error::before,
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice.woocommerce-error::before,
body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error::before,
body.kc-rd.woocommerce-checkout .woocommerce > .error-message::before {
	content: "Error";
	/* Ink, NOT --urgent. Computed, the way 20-buttons.css §20.6 computes its
	   disabled pair rather than eyeballing it: --urgent (--kc-tomato-600) has
	   relative luminance .1956, so against this card's own --card-bg
	   (-> --surface-raised -> --kc-white) it lands at 4.28:1 — under the 4.5:1
	   AA floor for normal-size text, and 12px is nowhere near the large-text
	   exemption (18.66 bold / 24). This word is the only non-colour carrier of
	   the error state, so it is the one thing in the card that cannot be
	   allowed to sit under the floor: ink is 15.9:1.
	   The state colour is not lost, it moves to where it is used as colour
	   rather than as text — the 4px accent edge above and the glyph in 80.1's
	   icon rule, both non-text indicators, which need only 3:1 and get 4.28:1.
	   (Sibling labels were already clear: "Done" on --trust is 8.0:1, "Note"
	   on --text-muted is 6.3:1. This one was the outlier.) */
	color: var(--text);
}

/* Informational — neutral, as briefed. Border is the strong neutral, not a
   fourth hue: the kit allows one accent and it is spoken for. */
body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice.woocommerce-info::before,
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-info::before,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice.woocommerce-info::before {
	content: "Note";
	color: var(--text-muted);
}

/* Cart page margins are the one place the child theme shouts. Two !important
   declarations at (0,2,0) and (0,2,1) set margin-top:40px / bottom:20px; the
   reserved slot below owns that spacing now, so they have to be beaten in
   kind.

   SCOPE, because it is narrower than it looks: this counter only reaches
   notices inside `.woocommerce-notices-wrapper`. Both shouted rules are keyed
   off the BODY class, so they also reach `.wc-empty-cart-message >
   .nm-shop-notice`, which this selector does not match. That second path is
   beaten separately in 80.4 — the only other !important in this file. */
body.kc-rd.woocommerce-cart .woocommerce-notices-wrapper > .nm-shop-notice {
	margin-top: 0 !important;
	margin-bottom: var(--kc-space-4) !important;
}

/* Inner shapes. These are safe to write unqualified: the checkout login /
   coupon toggles have no <li>, no icon and no price inside them. */
body.kc-rd .nm-shop-notice > li {
	margin: 0;
	padding: 0;
	list-style: none;
}
body.kc-rd .nm-shop-notice > li + li {
	margin-top: var(--kc-space-2);
}
body.kc-rd .nm-shop-notice > i,
body.kc-rd .nm-shop-notice span i {
	top: 0;
	font-size: 16px;
	margin-right: var(--kc-space-2);
	vertical-align: baseline;
}
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-error span i,
body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error span i {
	color: var(--urgent);
}
body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice.woocommerce-message span i,
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-message span i {
	color: var(--trust);
}

/* CART PAGE ONLY. savoy-child/style.css turns the notice's inner span into a
   centred flex row — `.woocommerce-cart .nm-shop-notice li > span { display:
   flex; justify-content: center; align-items: center }` (0,2,2) — which wins
   over the card's text-align and re-centres the sentence. Found by reading
   computed styles, not the stylesheet: the rule keys off the body class, so
   it never appears in a grep for the notice selector.

   The same block gives the leading state glyph `order: 1` (moves it to the
   END of the line), `cursor: pointer` and a hover fade — the full costume of
   a dismiss button. Nothing in savoy, savoy-child, nm-core or kc-redesign.js
   binds a click to it: it is a control that cannot be operated, sitting on
   the page where the customer is deciding whether to spend money. Putting the
   glyph back at the head of the line is one change that fixes both readings —
   it stops looking like a dismiss and starts looking like what it is, the
   icon half of the icon-plus-label pair. */
body.kc-rd.woocommerce-cart .woocommerce-notices-wrapper > .nm-shop-notice li > span {
	justify-content: flex-start;
	align-items: baseline;
	text-align: left;
	/* The anonymous text flex item takes min-width:auto, which CSS cannot
	   zero — but overflow-wrap does reduce its min-content contribution, so
	   a long interpolated token wraps instead of widening the row. */
	overflow-wrap: anywhere;
}
body.kc-rd.woocommerce-cart .woocommerce-notices-wrapper > .nm-shop-notice li > span .nm-font-close {
	order: 0;
	margin-left: 0;
	cursor: default;
}
body.kc-rd.woocommerce-cart .woocommerce-notices-wrapper > .nm-shop-notice li > span .nm-font-close:hover {
	opacity: 1;
}

/* savoy/shop.css:`.woocommerce-error strong { font-weight: inherit }` (0,1,1)
   flattens the one word that says WHICH field failed — a checkout summary
   reads "Billing First name is a required field" in a single even weight.
   The emphasis WooCommerce put there is the whole navigation aid. */
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice strong,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice strong,
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice strong,
body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error strong {
	font-weight: 600;
	color: var(--text);
}

/* Money inside a notice is still money: mono, ink, never the muted grey the
   customizer paints body text (#777). "$60.00 is minimum order amount" is a
   number the customer has to act on. */
body.kc-rd .nm-shop-notice .woocommerce-Price-amount,
body.kc-rd .woocommerce-NoticeGroup .woocommerce-Price-amount {
	font-family: var(--price-font);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
	color: var(--text);
}

/* The checkout "issues with the items in your cart" block ships with no CSS
   at all. Its paragraphs need collapsing into the card. */
body.kc-rd.woocommerce-checkout .woocommerce > .error-message p {
	margin: 0;
	max-width: none;
}
body.kc-rd.woocommerce-checkout .woocommerce > .error-message p + p {
	margin-top: var(--kc-space-3);
}


/* --------------------------------------------------------------------------
   80.2  Recovery routes — undoing `.nm-shop-notice a { display: none }`
   --------------------------------------------------------------------------
   Savoy hides every link inside a notice. On an added-to-cart success that
   is harmless (the side cart is the confirmation — nm-core.js opens it off
   the `nm-added-to-cart` body class). On an *error* it is the failure mode
   the playbook calls out by name: the blanket rule swallows the only way out
   of the error it just reported. Errors and info notices get their links
   back wholesale. Success keeps the theme's hide EXCEPT for a.restore-item:
   Woo's remove-item notice is a *success* notice ("… removed. Undo?" — the
   very text 80.3's slot is sized around), so its undo link is a recovery
   route too, and hiding it removes the undo window from a destructive
   action.
   -------------------------------------------------------------------------- */

/* `:not(.button)` is load-bearing, not tidiness: without it this rule's
   `display:inline` outranks 20-buttons.css's inline-flex base and quietly
   collapses every recovery BUTTON in a notice back to an inline box, which
   throws away its 48px floor. Measured before the exclusion: "Return to cart"
   computed display:inline with min-height:48px doing nothing. */
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-error a:not(.button),
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-info a:not(.button),
body.kc-rd form.woocommerce-checkout > .nm-shop-notice.woocommerce-error a:not(.button),
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice.woocommerce-error a:not(.button),
body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error a:not(.button),
body.kc-rd.woocommerce-checkout .woocommerce > .error-message a:not(.button) {
	display: inline;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* The one success-notice link that IS a recovery route. Woo's remove-item
   notice ships as success (wc_add_notice() defaults to 'success') and core
   appends the undo as <a class="restore-item"> with no .button class, so the
   blanket hide was swallowing it. Un-hidden by name, not by class family, so
   every other success link stays as the theme has it. (0,5,2) also outranks
   the empty-cart re-hide below at (0,3,2) — deliberately: the removal that
   empties the cart is exactly the one that needs its Undo. Focus ring: 80.6
   already matches any a under these containers, so no second rule there. */
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice.woocommerce-message a.restore-item,
body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice.woocommerce-message a.restore-item {
	display: inline;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* When the recovery route is a Woo button (.wc-backward / .wc-forward),
   20-buttons.css already un-hides it — its base `.button` rule is (0,2,1),
   above both `.nm-shop-notice a` (0,1,1) and `.woocommerce-error .wc-backward`
   (0,2,0) — and owns its shape, fill and 48px floor. Nothing is repainted
   here; the notice only owns where the control sits inside the card. */
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice a.button,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice a.button,
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice a.button,
body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error a.button,
body.kc-rd.woocommerce-checkout .woocommerce > .error-message a.button {
	margin-top: var(--kc-space-3);
	vertical-align: middle;
}

/* The empty cart keeps the theme's hide: it already renders its own
   "Return to shop" button one node up, and a second copy inside the notice
   would put two identical CTAs on a page whose whole job is one route out.

   This is (0,3,2), which is BELOW the un-hide rules directly overhead, not
   above them — `:not(.button)` contributes the specificity of its argument,
   so those land at (0,5,2) and (0,5,3). Do not read this rule as a blanket
   guarantee over everything in .nm-cart-empty; it is not one.

   What it does hold for is the case it is written for. The duplicate CTA is
   `a.button.wc-backward`, and every un-hide excludes a.button, so this rule
   only has to outrank 20-buttons.css's (0,2,1) base — which (0,3,2) does.
   It also covers the notice paths in this container that no un-hide names:
   the stale `ul.nm-shop-notice.woocommerce-error` and
   `.wc-empty-cart-message > .nm-shop-notice`, neither of which sits under a
   `.woocommerce-notices-wrapper`.

   Left deliberately un-raised. `.nm-cart-empty > .woocommerce-notices-wrapper`
   IS present here (empty today), and if Woo ever prints a bare recovery link
   into it — a removed coupon's "Undo", say — the un-hide wins and the link
   stays visible. That is the right outcome: swallowing the only way out of an
   error is the precise failure 80.2 exists to undo, and it does not become
   acceptable because the cart happens to be empty. */
body.kc-rd .nm-cart-empty .nm-shop-notice a {
	display: none;
}


/* --------------------------------------------------------------------------
   80.3  No layout shift when a notice arrives
   --------------------------------------------------------------------------
   The mid-tap risk is real on exactly one surface: the cart, where Woo's
   update-quantity / apply-coupon AJAX injects a notice ABOVE the controls the
   thumb is already on. `div.woocommerce-notices-wrapper` is rendered on every
   cart load whether or not it has content (verified empty in the served HTML),
   so the slot can simply be reserved: the notice arrives into space the page
   already gave it.

   The guarantee is bounded, and saying so precisely is the point. The slot
   absorbs notices up to TWO lines — which covers this store's real cart
   notices at the 375px target, including the remove-item sentence that wraps
   because it interpolates a long product title (derivation on
   --kc-notice-slot above). A three-line notice still moves the page by
   whatever it overflows. Reserving for three would leave ~130px of blank
   column between the "Cart" heading and the table on every load of a page
   that usually carries no notice at all, which is the worse trade.

   The reservation is not dead whitespace — the notice margin is folded into
   it (flow-root stops the child margin escaping), so on a cart with no notice
   it reads as the gap between the "Cart" heading and the table, which the
   page needed anyway.

   Checkout cannot use this trick: WooCommerce *creates*
   .woocommerce-NoticeGroup-checkout at submit time by prepending it to
   form.checkout, so there is no container to pre-size. What CSS can do there
   is make Woo's own scroll_to_notices() land the notice below the fixed
   chrome instead of under it — --kc-chrome-h is published on <html> by
   kc-redesign.js. A persistent slot on checkout needs markup; it is in the
   report as a lead item.
   -------------------------------------------------------------------------- */

body.kc-rd.woocommerce-cart .woocommerce > .woocommerce-notices-wrapper {
	display: flow-root;
	min-height: var(--kc-notice-slot);
	overflow-anchor: auto;
}

body.kc-rd .woocommerce-NoticeGroup,
body.kc-rd form.checkout > .woocommerce-NoticeGroup-checkout,
body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice,
body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice {
	scroll-margin-top: calc(var(--kc-chrome-h, 104px) + var(--kc-space-4));
}


/* --------------------------------------------------------------------------
   80.4  Empty cart — the notice chrome only
   --------------------------------------------------------------------------
   60-cart-page.css:815 owns this block's layout and its return CTA and states
   in writing that ".nm-shop-notice chrome belongs to the notices surface".
   Everything below stays strictly on my side of that line: no layout, no
   button, no p.icon.

   THE BUG. What ships today, verified on staging: an ✕ glyph and a "Return to
   shop" button — and NOT ONE WORD. WooCommerce moved the empty-cart sentence
   into `div.wc-empty-cart-message > .woocommerce-info` some versions back;
   Savoy's `.nm-cart-empty .nm-shop-notice { display:none }` (0,2,0) was
   written for the older `p.cart-empty` markup and now swallows the only copy
   on the page. Giving the sentence back is the single highest-value line in
   this file.

   CONCURRENT EDIT, resolved: 60-cart-page.css:896 landed the same reveal
   mid-session, with the flex `order` that puts the sentence between the icon
   and the button, and it sets display/margin/padding/font-size/line-height at
   (0,5,1). Those numbers are theirs and are not fought here. What stays on
   this side is strictly the chrome: cancelling the card and the state label
   that 80.1 would otherwise stamp onto it (only the rule that applies them
   can sensibly remove them), and the weight/colour that keep the sentence
   from falling back to the customizer's #777 body grey. `display:block` is
   kept as well — if their rule ever moves, Savoy's blanket hide returns and
   the page loses its only copy again.

   ONE EXCEPTION to "their numbers are not fought here", and it is the
   opposite of a fight — it is the only way their number survives. See the
   margin rule at the end of this section.

   Typeset as a statement, not a notice: nothing failed, so a bordered "NOTE"
   box would be lying about the state.
   -------------------------------------------------------------------------- */

/* Give the sentence back. (0,4,1) over Savoy's (0,2,0). */
body.kc-rd .nm-cart-empty .wc-empty-cart-message > .nm-shop-notice {
	display: block;
	background: none;
	border: 0;
	border-radius: 0;
	font-family: var(--kc-font-body);
	font-weight: 600;
	color: var(--text);
	text-align: center;
}
/* No state label: this is an empty state, not a report of something failing. */
body.kc-rd .nm-cart-empty .wc-empty-cart-message > .nm-shop-notice::before {
	content: none;
}

/* Woo renders this slot on every empty cart whether or not it holds anything;
   an empty box here would open a hole between the theme's icon and its CTA. */
body.kc-rd .nm-cart-empty > .woocommerce-notices-wrapper:empty {
	display: none;
}

/* The child theme's shouted cart margins reach this sentence too, and until
   this rule they were winning it.

   `.woocommerce-cart .nm-shop-notice { margin-top:40px !important;
   margin-bottom:20px !important }` (0,2,0)+! and `body.woocommerce-cart
   .nm-shop-notice { margin-top:40px !important }` (0,2,1)+! are both keyed off
   the BODY class, so they match EVERY .nm-shop-notice on the cart page — this
   one included. 80.1's counter does not cover it: that selector is scoped
   `.woocommerce-notices-wrapper > .nm-shop-notice`, and this sentence lives in
   `.nm-cart-empty > .wc-empty-cart-message` (header map, shape c). And
   60-cart-page.css:965 does declare a margin here — `0 0 var(--kc-space-5)` at
   (0,5,1) — but without !important, so it loses to the shout no matter how
   specific it gets. .nm-cart-empty is a flex column, where these margins do
   not collapse away, so what actually shipped was 40px above / 20px below the
   one sentence 80.4 exists to reveal.

   This is the case where added specificity genuinely cannot do the job. An
   !important has to be beaten in kind, which is the same reasoning that put
   the one in 80.1 there.

   The 24px is 60-cart-page.css:965's number, restated here only as the vehicle
   for making it stick — it is not a second opinion about the spacing. If that
   file's value moves, move this with it. */
body.kc-rd.woocommerce-cart .nm-cart-empty .wc-empty-cart-message > .nm-shop-notice {
	margin-top: 0 !important;
	margin-bottom: var(--kc-space-5) !important;
}


/* --------------------------------------------------------------------------
   80.5  404
   --------------------------------------------------------------------------
   Brand surface, so the display face is allowed and used — on the numeral
   only, which clears 28px by a distance.

   The defect here is measured, not assumed. I first read `.nm-page-not-found
   .desc { margin-left:100px; width:100% }` in a flex row and wrote up a 100px
   horizontal overflow. Rendering it proved that wrong: the child theme
   unwinds it under two media queries keyed off the BODY class rather than the
   block, `.error404 .desc` at <=991px (15px) and at <=679px (0px, width
   100%), which a grep for `.nm-page-not-found` never finds. No overflow. The
   claim is recorded here because the real defect is the one next to it and
   the two are easy to confuse.

   WHAT IS ACTUALLY WRONG, measured in a 360px layout viewport against the
   real stylesheets:
     `.error404 img { width: 100% }` (<=679px) scales the 347x571 artwork to
     the full column: 543px tall. The whole 404 block measures 966px — taller
     than an iPhone viewport — so the single link off this page starts below
     the fold. Capping the art at min(160px, 42vw) brings the art to 259px and
     the block to 639px, which fits. Same numbers, kc-parts.css toggled off
     and on in the same document.

   ROUTES OUT — read this before signing off. The template ships exactly one
   link ("Home Page"). CSS cannot add a link, and I did not fake one. Savoy
   already has a built-in 404 products block (.nm-page-not-found-products,
   styled by savoy/style.css, driven by a theme option); it is pre-styled at
   the end of this section so that switching the option on lands designed
   rather than raw. Shop / search / popular-category links need markup — in
   the report as a lead item.
   -------------------------------------------------------------------------- */

body.kc-rd .nm-page-not-found {
	padding: var(--kc-space-7) 0 var(--kc-space-8);
	text-align: center;
}

body.kc-rd .nm-page-not-found .col-xs-12 {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--kc-space-6);
}

/* Kills the 100px overflow. (0,3,1) over the child theme's (0,2,0). */
body.kc-rd .nm-page-not-found .desc {
	margin-left: 0;
	margin-right: 0;
	max-width: 460px;
	width: 100%;
}

/* One definite axis plus the natural ratio (347x571, header map shape f),
   so the box is reserved before the SVG resolves and the .desc column does
   not drop by the painted height on arrival. Painted geometry is unchanged:
   auto width already resolved to the cap, since the art's natural 347px
   exceeds it at every viewport. */
body.kc-rd .nm-page-not-found img {
	display: block;
	width: min(160px, 42vw);
	height: auto;
	aspect-ratio: 347 / 571;
}

body.kc-rd .nm-page-not-found .desc h2 {
	margin: 0 0 var(--kc-space-4);
	font-family: var(--kc-font-body);
	font-size: 21px;
	font-weight: 600;
	line-height: 1.25;
	letter-spacing: -0.01em;
	color: var(--text);
	text-align: center;
}

/* The numeral. Was 98px of #AA2934 — a crimson that is in no layer of the
   token system. Ink, display face, fluid so it cannot overflow a phone. */
body.kc-rd .nm-page-not-found .desc h2 span {
	display: block;
	margin-bottom: var(--kc-space-2);
	font-family: var(--kc-font-display);
	font-size: clamp(56px, 17vw, 92px);
	font-weight: 400;
	line-height: 1;
	letter-spacing: -0.02em;
	color: var(--text);
}

body.kc-rd .nm-page-not-found .desc p {
	max-width: 42ch;
	margin: 0 auto var(--kc-space-5);
	font-size: 15px;
	line-height: 1.65;
	color: var(--text-muted);
}

/* One primary action. Was a fixed 176x56 box of #AA2934 with
   `flex-direction: column` and `padding: 0`, all at (0,3,0) — which sits
   above 20-buttons.css's (0,2,1) base, so the base never reaches this
   control. Only the geometry the child theme pins and the fill it invents are
   restated; shape, sizing floor, focus ring and transitions all still come
   from 20-buttons.css. */
body.kc-rd .nm-page-not-found .desc a.button {
	flex-direction: row;
	width: auto;
	min-width: 200px;
	height: auto;
	margin: 0 auto;
	padding: var(--kc-space-3) var(--kc-space-5);
	background: var(--btn-primary-bg);
	color: var(--btn-primary-text);
	border: 2px solid var(--btn-primary-border);
	box-shadow: var(--btn-primary-shadow);
}
/* State table: hover moves 1px and drops the shadow to 2px, press seats the
   full 3px. Gated on (hover:hover) — on touch a sticky :hover leaves the only
   exit on this page looking permanently pressed. */
@media (hover: hover) {
	body.kc-rd .nm-page-not-found .desc a.button:hover {
		background: var(--btn-primary-bg);
		color: var(--btn-primary-text);
		transform: translate(1px, 1px);
		box-shadow: 2px 2px 0 var(--text);
	}
}
body.kc-rd .nm-page-not-found .desc a.button:active {
	transform: translate(3px, 3px);
	box-shadow: 0 0 0 var(--text);
}

@media (min-width: 768px) {
	body.kc-rd .nm-page-not-found .col-xs-12 {
		flex-direction: row;
		align-items: center;
		gap: var(--kc-space-8);
	}
	body.kc-rd .nm-page-not-found img {
		/* width, not max-width: the base rule's definite width must move with
		   the breakpoint or the aspect-ratio reservation stops at 160px. */
		width: 200px;
	}
	body.kc-rd .nm-page-not-found .desc,
	body.kc-rd .nm-page-not-found .desc h2,
	body.kc-rd .nm-page-not-found .desc p {
		text-align: left;
	}
	body.kc-rd .nm-page-not-found .desc p {
		margin-left: 0;
	}
	/* The child theme's display:flex (0,3,0) outranks 20-buttons' inline-flex
	   base, so the anchor is block-level and eats the full 460px column.
	   Full width is right on a phone, wrong on a desktop. Measured: 460px
	   before this rule, 200px after. */
	body.kc-rd .nm-page-not-found .desc a.button {
		display: inline-flex;
		margin-left: 0;
	}
	body.kc-rd .nm-page-not-found .desc h2 {
		font-size: 24px;
	}
}

/* Inert until Savoy's "404 products" theme option is switched on — unlike the
   rest of this file, `.nm-page-not-found-products` is NOT in the served DOM
   today, so nothing below is DOM-verified; it is written against Savoy's own
   markup for that option. Styled now so that flipping the option is a
   one-click change that lands on-brand instead of raw — it is the shortest
   path to real routes out of this page, which is exactly why it has to be
   correct before anyone flips it rather than after. */
body.kc-rd .nm-page-not-found-products {
	padding: var(--kc-space-7) 0 var(--kc-space-6);
	border-top: 1px solid var(--border);
}
body.kc-rd .nm-page-not-found-products-heading {
	margin: 0 0 var(--kc-space-5);
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	/* --text-muted, not --text-faint. Same substitution 20-buttons.css §20.6
	   made for the same reason: --text-faint is rgba(31,26,23,.45), which
	   composites on the page ground --surface (--kc-cream-50) to #9A968F =
	   2.85:1, nowhere near the 4.5:1 floor for 13px. This is the heading over
	   the only real routes off a 404 — the last thing on the page that should
	   be hard to read. --text-muted composites to #625E59 = 6.23:1. */
	color: var(--text-muted);
	text-align: center;
}


/* --------------------------------------------------------------------------
   80.6  Focus, motion, forced colours
   --------------------------------------------------------------------------
   savoy-child/assets/css/main.css opens with `* { outline: 0 none }` and
   savoy/style.css:291 adds `.button:focus { outline: none }`, so the site
   currently has no focus ring at all. 20-buttons.css restores it for
   everything carrying `.button`; what is left uncovered on this surface is
   the bare <a> inside a notice, which is exactly the recovery link 80.2 just
   un-hid. It gets the same ring so the two never look like different systems.
   -------------------------------------------------------------------------- */

body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice a:focus-visible,
body.kc-rd form.woocommerce-checkout > .nm-shop-notice a:focus-visible,
body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice a:focus-visible,
body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error a:focus-visible,
body.kc-rd.woocommerce-checkout .woocommerce > .error-message a:focus-visible {
	outline: 3px solid var(--trust);
	outline-offset: 3px;
	border-radius: var(--kc-radius-sm);
}

/* 20-buttons.css kills the transition under reduced motion with an
   `!important` that reaches this control. What it cannot reach is the child
   theme's geometry, so the press displacement is neutralised here instead —
   a 3px jump is still motion. */
@media (prefers-reduced-motion: reduce) {
	body.kc-rd .nm-page-not-found .desc a.button:hover,
	body.kc-rd .nm-page-not-found .desc a.button:active {
		transform: none;
		box-shadow: var(--btn-primary-shadow);
	}
}

/* In forced-colours mode every background and colour above is replaced by the
   OS palette, so the tomato / pine edge stops carrying meaning on its own.
   The ::before word is real text and survives untouched — that is the whole
   reason the state is spelled out rather than only painted. The card border
   is pinned to the system ink so the box still reads as a box, and the accent
   edge stays 4px so it still reads as an accent. */
@media (forced-colors: active) {
	body.kc-rd #nm-shop-notices-wrap > .nm-shop-notice,
	body.kc-rd .woocommerce-notices-wrapper > .nm-shop-notice,
	body.kc-rd form.woocommerce-checkout > .nm-shop-notice,
	body.kc-rd .woocommerce-NoticeGroup > .nm-shop-notice,
	body.kc-rd .woocommerce-NoticeGroup > .woocommerce-error,
	body.kc-rd.woocommerce-checkout .woocommerce > .error-message {
		border: 1px solid CanvasText;
		border-left: 4px solid CanvasText;
	}
}

/* ===== 81-notices-fix.css ===== */
/* ==========================================================================
   81 — NOTICE PIPELINE (root fix)              surface owner: notices session
   --------------------------------------------------------------------------
   80-notices.css owns how a notice LOOKS. This file owns the two places where
   the customer could not see one at all, and it is deliberately small: the
   whole point of the fix lives in inc-notices.php, which makes sure the queue
   reaches a container that exists. Everything here is about that container and
   about undoing one blanket hide.

   81.1  the terminal outlet (#kc-notices) — the toast that catches every
         notice raised on a page with no in-content outlet of its own.
   81.2  recovery links — keyed on the NOTICE, not on a list of containers.

   Read 80-notices.css's header first; the markup shapes are mapped there and
   are not repeated. Nothing in this file uses !important, and it introduces no
   colour of its own: the notice cards inside the toast are still 80.1's cards.
   ========================================================================== */


/* --------------------------------------------------------------------------
   81.1  #kc-notices — one container, always visible
   --------------------------------------------------------------------------
   Printed by inc-notices.php §2 on every front-end page, empty unless the
   request finished with notices still in the queue. It is the COMPLEMENT of
   the in-content outlets, not a replacement for them: printing clears the
   queue, so on shop / category / PDP / cart / checkout / my-account this stays
   empty and nothing is drawn twice. That is why there is no page list here.

   STACKING, measured on staging 2026-08-28 (/shop/, both viewports):
     #nm-widget-panel  z-index 100000  position fixed   (the side cart)
     #nm-header        z-index   1002
     #nm-page-overlay  z-index   1000
   100010 clears all three, which is the requirement: the failure this file
   exists to end is an EMPTY side cart sliding over the error that explains why
   it is empty. html, body, .nm-page-overflow and .nm-page-wrap were all
   verified to carry transform:none / filter:none / will-change:auto /
   contain:none, so a fixed element emitted at wp_footer() inside
   .nm-page-overflow is not trapped in a stacking context. If a page-transition
   or parallax effect ever puts a transform on one of those, this silently
   becomes an absolutely-positioned block mid-page — that is the one
   maintenance dependency worth knowing about.

   POSITION. Top, not bottom: the mobile sticky add-to-cart bar owns the bottom
   of the PDP, which is precisely the screen where the add fails. Offset by the
   real chrome height that kc-redesign.js measures and publishes on <html>, so
   it lands under the header at both viewports and follows the announcement bar
   when it is present.
   -------------------------------------------------------------------------- */

body.kc-rd #kc-notices {
	display: none;
}

body.kc-rd #kc-notices.is-on {
	display: block;
	position: fixed;
	top: calc(var(--kc-chrome-h, 104px) + var(--kc-space-3));
	inset-inline: var(--kc-space-4);
	margin-inline: auto;
	z-index: 100010;
	box-sizing: border-box;
	max-width: 560px;
	/* Bounded so a multi-notice stack can never own the whole screen. The
	   overflow clips the shadow's outer edge only when scrolling actually
	   engages, which for this store's notice lengths is close to never. */
	max-height: calc(100dvh - var(--kc-chrome-h, 104px) - var(--kc-space-6));
	overflow-y: auto;
	overscroll-behavior: contain;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-radius: var(--card-radius);
	box-shadow: var(--panel-shadow);
}

/* Older engines without dvh keep the old behaviour rather than no cap. */
@supports not (height: 1dvh) {
	body.kc-rd #kc-notices.is-on {
		max-height: calc(100vh - var(--kc-chrome-h, 104px) - var(--kc-space-6));
	}
}

/* The notices inside are 80.1's cards, minus their own frame: the toast is
   already the box, and a card inside a card reads as a rendering mistake. The
   4px state edge on the left is what carries the state, so it stays.
   (1,4,1) over 80.1's (0,3,1) — an id, not an !important. */
body.kc-rd #kc-notices .kc-notices-list > .nm-shop-notice,
body.kc-rd #kc-notices .kc-notices-list > .woocommerce-error,
body.kc-rd #kc-notices .kc-notices-list > .woocommerce-message,
body.kc-rd #kc-notices .kc-notices-list > .woocommerce-info {
	margin: 0;
	border-top: 0;
	border-right: 0;
	border-bottom: 0;
	border-radius: 0;
	background: none;
	box-shadow: none;
}

/* Two notices in one toast need a line between them, not a gap: the gap would
   let the toast's own background read as a third surface. */
body.kc-rd #kc-notices .kc-notices-list > .nm-shop-notice + .nm-shop-notice {
	border-top: 1px solid var(--card-border);
}

/* Dismiss. Plain text, never a filled control: nothing in this box is a
   purchase, and §2 of the brand rules reserves the button shape for spending
   money. 44px is the touch floor, and the row is full-width so the target is
   the whole strip rather than the three words. */
body.kc-rd #kc-notices .kc-notices-dismiss {
	display: block;
	width: 100%;
	min-height: 44px;
	margin: 0;
	padding: 0 var(--kc-space-4);
	background: none;
	border: 0;
	border-top: 1px solid var(--card-border);
	border-radius: 0;
	box-shadow: none;
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: 0.12em;
	line-height: 1.3;
	text-align: right;
	text-transform: uppercase;
	color: var(--text-muted);
	cursor: pointer;
}
body.kc-rd #kc-notices .kc-notices-dismiss:hover,
body.kc-rd #kc-notices .kc-notices-dismiss:focus-visible {
	color: var(--text);
}
/* savoy-child/assets/css/main.css `* { outline: 0 none }` (0,0,0) removes the
   only affordance a keyboard user has. Restated here for this control. */
body.kc-rd #kc-notices .kc-notices-dismiss:focus-visible {
	outline: 2px solid var(--text);
	outline-offset: -2px;
}


/* --------------------------------------------------------------------------
   81.2  Recovery links — keyed on the notice, not on its container
   --------------------------------------------------------------------------
   savoy/assets/css/shop.css:98 `.nm-shop-notice a { display: none }` (0,1,1)
   hides every link inside every notice, everywhere. Measured on
   /shop/?add-to-cart=35280 before this rule: the banner renders
   "Please choose product options by visiting ." — a sentence ending in a bare
   period, because the one thing it names, <a title="Donkey Butter">, computes
   to display:none with a 0x0 rect. The single action the message names was
   both unnamed and unreachable, on both viewports.

   80.2 already undoes this, but by enumerating four containers — and it misses
   `#nm-shop-notices-wrap`, which is the theme's PRIMARY outlet on shop,
   category and PDP, i.e. the busiest surface in the store. That is the shape
   of bug this whole session exists to stop: a whitelist that a new surface
   silently falls outside of. Keyed on the notice itself, every outlet that
   exists now or is written later gets working links for free.

   SPECIFICITY, worked out rather than shouted:
     savoy            .nm-shop-notice a                       (0,1,1)
     this rule        .nm-shop-notice.woocommerce-error a:not(.button)
                      = 2 classes + :not()'s argument + <a>   (0,3,1)   wins
     80.2's own       ….woocommerce-notices-wrapper > …       (0,4,2)   still
                      above this, same declarations, no conflict
     80.4's           .nm-cart-empty .nm-shop-notice a        (0,2,1)   BELOW
                      this — deliberately not fought: that rule exists to hide
                      the DUPLICATE "Return to shop" CTA, which is
                      a.button.wc-backward, and `:not(.button)` excludes it.
                      The re-assertion below keeps that intact by name.

   `:not(.button)` is load-bearing, not tidiness — 80.2 documents why: without
   it, display:inline outranks 20-buttons.css's inline-flex base and collapses
   every recovery BUTTON back to an inline box, throwing away its 48px floor.

   Success notices keep the theme's hide (the side cart is the confirmation);
   80.2 already carves out the one success link that is a recovery route,
   a.restore-item, and that carve-out is untouched here.
   -------------------------------------------------------------------------- */

body.kc-rd .nm-shop-notice.woocommerce-error a:not(.button),
body.kc-rd .nm-shop-notice.woocommerce-info a:not(.button),
body.kc-rd .woocommerce-NoticeGroup .woocommerce-error a:not(.button),
body.kc-rd .woocommerce > .error-message a:not(.button) {
	display: inline;
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* Re-assert 80.4's deliberate exception above the rule that now outranks it.
   The empty cart already renders its own "Return to shop" one node up; a
   second identical CTA inside the notice would put two of the same route on a
   page whose whole job is one route out. (0,4,2) — above the (0,3,1) block
   overhead, still below 80.2's a.restore-item carve-out at (0,5,2), which is
   the correct order: the Undo on a removal that emptied the cart is exactly
   the link that must survive.

   If 80-notices.css:376-383 is ever deleted as redundant (it is — see the
   report), THIS RULE MUST STAY. Deleting both halves un-hides the duplicate
   empty-cart CTA. */
body.kc-rd .nm-cart-empty .nm-shop-notice.woocommerce-error a:not(.button),
body.kc-rd .nm-cart-empty .nm-shop-notice.woocommerce-info a:not(.button) {
	display: none;
}

/* Focus ring for the links this file just brought back. Same reason as the
   dismiss button: main.css's `* { outline: 0 none }` leaves a keyboard user
   with no way to tell where they are. */
body.kc-rd .nm-shop-notice a:focus-visible {
	outline: 2px solid var(--text);
	outline-offset: 2px;
}

/* ===== 82-thirdparty-mute.css ===== */
/* ==========================================================================
   82 — Third-party prompt mute (owner: 2026-09-02)
   --------------------------------------------------------------------------
   OneSignal's slide-down push prompt ("Subscribe to Receive Exclusive
   Offers & Discounts!"). Owner 2026-09-02: kill it — the store runs its
   own Klaviyo popup flow, and this one was riding the checkout.
   The v3 plugin (3.9.2) offers no local switch or filter: the prompt is
   the OneSignal DASHBOARD's remote config, fetched by the SDK at runtime,
   so a plugin-file edit would not survive updates and a settings change
   does not exist. Hiding the container is the update-proof local kill.
   The bell (#onesignal-bell-container) stays — owner kept it on 08-31 —
   and existing push subscribers are untouched.
   ========================================================================== */
#onesignal-slidedown-container { display: none !important; }

/* ===== 84-sale.css ===== */
/* ==========================================================================
   84 — On Sale Now page (/on-sale-now/). Surface owner: sale.
   Scope: body.kc-sale only (inc-sale.php adds the class). Cards themselves
   are inc-cards.php markup styled by the shared card layers — nothing about
   card internals is restated here; this file owns the page shell: float
   containment, hero, shelf headers, grid rhythm, empty state.
   Tokens only, zero !important.
   ========================================================================== */

/* --------------------------------------------------------------------------
   84.1 Float containment — the reason the old page drowned in pine green.
   Savoy's plain page template floats .col-xs-12 and never clears it, so
   .nm-row-full collapses to 0 and the get_footer newsletter band (#kcnl)
   floods up UNDER the whole grid (measured: content column 0px tall, #kcnl
   stretched to 3,770px). kc-redesign.css §29 carries this exact fix scoped
   body.home; this is the same one-liner scoped to this page.
   -------------------------------------------------------------------------- */
body.kc-sale .nm-page-wrap-inner > .nm-row-full,
body.kc-sale .nm-row-full {
	display: flow-root;
}

/* --------------------------------------------------------------------------
   84.2 Page track — same 1160 register as the home sections.
   -------------------------------------------------------------------------- */
body.kc-sale .kc-sale-wrap {
	max-width: 1160px;
	margin: 0 auto;
	padding: 34px 40px 56px;
}

/* --------------------------------------------------------------------------
   84.3 Hero — H1 (Gazella, brand face ≥28px) over a ledger count band,
   the same double-hairline instrument as the category title band.
   -------------------------------------------------------------------------- */
body.kc-sale .kc-sale-hero {
	text-align: center;
	padding: 10px 0 8px;
}
body.kc-sale .kc-sale-hero h1 {
	font-family: var(--kc-font-display);
	font-weight: 400;
	font-size: 42px;
	line-height: 1.15;
	color: var(--text);
	margin: 0 0 14px;
}
body.kc-sale .kc-sale-count {
	max-width: 420px;
	margin: 0 auto;
	padding: 6px 0;
	border-top: 1px solid var(--text);
	border-bottom: 1px solid var(--border-strong);
	font-family: var(--kc-font-body);
	font-size: 13px;
	line-height: 1.5;
	color: var(--text-muted);
}
body.kc-sale .kc-sale-count b {
	font-weight: 600;
	color: var(--text);
}
body.kc-sale .kc-sale-n {
	font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   84.4 Shelf headers — H2 verbatim keywords in the brand face, item count
   right-aligned on the shared baseline, one hairline underneath.
   -------------------------------------------------------------------------- */
body.kc-sale .kc-sale-shelf {
	padding-top: 38px;
}
body.kc-sale .kc-sale-shelfhead {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 12px;
	border-bottom: 1px solid var(--border-strong);
	padding-bottom: 10px;
	margin-bottom: 20px;
}
body.kc-sale .kc-sale-shelfhead h2 {
	font-family: var(--kc-font-display);
	font-weight: 400;
	font-size: 28px;
	line-height: 1.2;
	color: var(--text);
	margin: 0;
}
body.kc-sale .kc-sale-shelfcount {
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 500;
	color: var(--text-muted);
	white-space: nowrap;
}

/* --------------------------------------------------------------------------
   84.5 Grid rhythm — the theme's block-grid (columns="4") owns the columns;
   only the row gap needs the archive's 30px breathing room. The trailing
   .woocommerce margin would double up with the shelf's own padding.
   -------------------------------------------------------------------------- */
body.kc-sale .kc-sale-shelf ul.nm-products > li.product {
	padding-bottom: 30px;
}
body.kc-sale .kc-sale-shelf .woocommerce {
	margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   84.6 Honest empty state — renders only when nothing at all is on sale.
   Quiet card, ink-bordered secondary action; amber stays reserved for
   Add to cart everywhere.
   -------------------------------------------------------------------------- */
body.kc-sale .kc-sale-empty {
	max-width: 520px;
	margin: 34px auto 0;
	padding: 40px 24px;
	text-align: center;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-radius: var(--card-radius);
}
body.kc-sale .kc-sale-empty p {
	font-family: var(--kc-font-body);
	font-size: 15px;
	line-height: 1.6;
	color: var(--text-muted);
	margin: 0 0 18px;
}
body.kc-sale .kc-sale-empty-link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 48px;
	padding: 0 22px;
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	color: var(--text);
	background: var(--surface-raised);
	border: 1px solid var(--text);
	border-radius: var(--kc-radius-md);
}

/* --------------------------------------------------------------------------
   84.7 Phone (≤768) — 18px gutter (the SECOND ≤768 block in kc-redesign.css
   sets the site's phone gutter to 18, not 16 — measured lesson, home-social
   §5.3). H1 steps down but the H2 stays at the 28px brand-face floor.
   -------------------------------------------------------------------------- */
@media (max-width: 768px) {
	body.kc-sale .kc-sale-wrap {
		padding: 22px 18px 44px;
	}
	body.kc-sale .kc-sale-hero h1 {
		font-size: 30px;
		margin-bottom: 12px;
	}
	body.kc-sale .kc-sale-shelf {
		padding-top: 30px;
	}
	body.kc-sale .kc-sale-shelfhead {
		margin-bottom: 14px;
	}
	body.kc-sale .kc-sale-shelf ul.nm-products > li.product {
		padding-bottom: 22px;
	}
}

/* ===== 85-brand.css ===== */
/* ==========================================================================
   85 — Brand surfaces: /brand/ hub directory + /brands/<slug>/ archive banner
   Owner: brand surface (inc-brand.php). Tokens only, zero raw hex.
   ========================================================================== */

/* --------------------------------------------------------------------------
   85.0 (retired) — the /brand/-only float-containment patch that used to live
   here is gone. The collapse was never this page's bug: savoy-child/page.php
   opens its content row as `<div class="nm-row-full">` without the base class
   `nm-row` that carries Savoy's clearfix, so EVERY page rendered by that
   template collapses. Fixed once, structurally, in 88-static-pages.css §88.0.
   88 loads after 85, so the two files ship together — see the redeploy note in
   docs/redesign/rootfix-layout-pages.md.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   85.1 /brand/ — the directory grid (markup from inc-brand.php)
   --------------------------------------------------------------------------
   Same tile language as the homepage Trusted Brands row (95-home-seo.css
   §95.7): raised card, hairline border, logos blended onto the card,
   border+lift on hover. A directory tile is navigation, not a buy control —
   no hard shadow, no amber. 22 tiles: 5 columns desk, down to 2 on phone
   (22 = 11 clean rows of 2).
   -------------------------------------------------------------------------- */
/* Self-boxed: the page template boxes the H1/breadcrumb column to 1280
   (measured x=335 / w=1250 at a 1920 viewport) but the_content sits in an
   unboxed wrapper — without this the grid spans the full 1920. Same numbers
   as the template's own box so the grid's edges align with the H1's. */
.kc-brand {
	max-width: 1280px;
	margin: 6px auto 56px;
	padding: 0 15px;
}

/* --------------------------------------------------------------------------
   85.1a Hub header — same language as the archive pages' H1 (kc-redesign.css
   §11 styles `#nm-shop h1.title`; this page has no #nm-shop wrapper, so the
   hub H1 shipped as 28px default-font left-flush — and at 375px the template
   gives the column zero side padding, so the text touched the screen edge).
   Scoped to this page; values mirror §11 so a future merge is a no-op.
   -------------------------------------------------------------------------- */
body.page-id-7481 .breadcrumbs {
	color: var(--text-faint);
	font-size: 12px;
	letter-spacing: .05em;
	text-transform: uppercase;
	text-align: center;
	padding: 6px 15px 0;
	/* theme ships nowrap + overflow:auto — on a 375px phone this page's long
	   crumb becomes a 610px clipped side-scroller. Let it wrap. The theme also
	   sets nowrap on the bcn_display spans themselves, so the parent fix alone
	   does nothing (measured: parent normal, spans still nowrap). */
	white-space: normal;
	overflow: visible;
}
body.page-id-7481 .breadcrumbs span { white-space: normal; }
body.page-id-7481 .breadcrumbs a { color: var(--text-muted); }
body.page-id-7481 .breadcrumbs a:hover { color: var(--text); }

body.page-id-7481 h1.title {
	font-family: var(--kc-font-display);
	font-size: 44px;
	line-height: 1.12;
	color: var(--text);
	text-align: center;
	position: relative;
	padding: 0 15px 16px;
	margin: 10px auto 26px;
	max-width: 900px;
}
body.page-id-7481 h1.title::after {
	content: "";
	position: absolute;
	left: 50%;
	transform: translateX(-50%);
	bottom: 0;
	width: 56px;
	height: 6px;
	border-radius: 999px;
	background: var(--action);
	box-shadow: 2px 2px 0 var(--text);
}

@media (max-width: 768px) {
	body.page-id-7481 h1.title { font-size: 30px; margin-top: 6px; }
}

.kc-brand-grid {
	display: grid;
	grid-template-columns: repeat(5, 1fr);
	gap: 14px;
	list-style: none;
	margin: 0;
	padding: 0;
}
.kc-brand-grid li { margin: 0; padding: 0; }

.kc-brand-tile a {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: flex-start;
	gap: 12px;
	min-height: 150px;
	padding: 20px 14px 16px;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-radius: var(--card-radius);
	text-decoration: none;
	transition: border-color .2s, transform .35s cubic-bezier(.32, .72, 0, 1);
}
.kc-brand-tile a:hover {
	border-color: var(--border-strong);
	transform: translateY(-2px);
}
.kc-brand-tile a:focus-visible {
	outline: 3px solid var(--action);
	outline-offset: 2px;
}

.kc-brand-logobox {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 64px;
	flex: 0 0 auto;
}
.kc-brand-logo {
	max-width: 84%;
	max-height: 64px;
	width: auto;
	height: auto;
	object-fit: contain;
	/* most of these logos ship on baked-in white; multiply drops that onto
	   the card cleanly without re-cutting 22 third-party assets */
	mix-blend-mode: multiply;
}

/* No trustworthy image → typographic monogram, never a placeholder graphic */
.kc-brand-logobox-mono {
	background: var(--surface-sunken);
	border-radius: var(--kc-radius-sm);
}
.kc-brand-mono {
	font-family: var(--kc-font-display);
	/* display face floor is 28px — this is a brand mark, its one legit home */
	font-size: 34px;
	line-height: 1;
	color: var(--text);
}
/* Painted, not typed: as DOM text the letter would prepend itself to the
   link's anchor text and trip the SEO parity gate (see inc-brand.php). */
.kc-brand-mono::before { content: attr(data-ch); }

.kc-brand-name {
	font-family: var(--kc-font-body);
	font-size: 14px;
	font-weight: 600;
	line-height: 1.35;
	text-align: center;
	color: var(--text);
}
.kc-brand-n {
	font-weight: 500;
	font-size: 12.5px;
	color: var(--text-muted);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

/* --------------------------------------------------------------------------
   85.1b Brands whose archive will show nothing today.
   --------------------------------------------------------------------------
   The count beside the name is now the number the archive will actually
   render (inc-brand.php::kc_rd_brand_live_count), so "(0)" is the truth and
   no longer a contradiction of the page behind the link. A bare (0) still
   reads like a glitch, so the tile says what is happening instead: the link
   stays — the URL is indexed and the brand is expected back — but it steps
   back from the brands that can be bought right now, and the customer knows
   before the click rather than after it.

   Purely a consequence of the data: a restock flips the count and the tile
   returns to normal with nobody editing anything.
   -------------------------------------------------------------------------- */
.kc-brand-tile-empty a {
	background: var(--surface-sunken);
	border-style: dashed;
}
.kc-brand-tile-empty .kc-brand-logobox {
	opacity: .45;
}
.kc-brand-tile-empty .kc-brand-name,
.kc-brand-tile-empty .kc-brand-n {
	color: var(--text-muted);
}
/* Painted, not typed — as DOM text it would join the link's anchor text and
   trip the SEO parity gate, exactly like the monogram above. */
.kc-brand-restock {
	font-family: var(--kc-font-body);
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: .1em;
	text-transform: uppercase;
	line-height: 1;
	color: var(--text-muted);
	background: var(--surface-raised);
	border: 1px solid var(--border);
	border-radius: 999px;
	padding: 4px 8px;
	margin-top: -2px;
}
.kc-brand-restock::before { content: "Restocking"; }
.kc-brand-tile-empty a:hover {
	border-color: var(--border-strong);
}

@media (max-width: 1100px) {
	.kc-brand-grid { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 820px) {
	.kc-brand-grid { grid-template-columns: repeat(3, 1fr); gap: 12px; }
}
@media (max-width: 560px) {
	.kc-brand-grid { grid-template-columns: repeat(2, 1fr); }
	.kc-brand-tile a { min-height: 138px; padding: 16px 10px 14px; }
	.kc-brand-logobox { height: 56px; }
	.kc-brand-logo { max-height: 56px; }
}

/* --------------------------------------------------------------------------
   85.2 /brands/<slug>/ — the "About Brand" banner (savoy-child
        woocommerce/archive-product.php prints it; markup is not ours to edit)
   --------------------------------------------------------------------------
   Ships as a 85px-padded box with a palm-tree stock photo background
   (assets/img/bg.jpg) and a 50/50 split that gives half the page to a logo
   capped at 406px. Recast in the brand system: raised panel, hairline,
   compact logo column, eyebrow-style boilerplate heading.
   -------------------------------------------------------------------------- */
.tax-berocket_brand .top-description {
	background: var(--card-bg);
	background-image: none;
	border: 1px solid var(--card-border);
	border-radius: var(--card-radius);
	padding: 26px 30px;
	gap: 30px;
	flex-wrap: nowrap;
	align-items: center;
}
.tax-berocket_brand .top-description .logo-img {
	/* the theme grid floats these cols to 50%; flex-basis wins inside the
	   flex parent, floats are ignored on flex items */
	flex: 0 0 200px;
	width: auto;
	max-width: 200px;
	padding: 0;
}
/* main.css pins this img to `max-width:406px !important` — same weapon back,
   scoped tighter and loaded later. The one !important in this file. */
.tax-berocket_brand .top-description .logo-img img {
	max-width: 100% !important;
	max-height: 120px;
	width: auto;
	height: auto;
	object-fit: contain;
	mix-blend-mode: multiply;
}
.tax-berocket_brand .top-description > div:not(.logo-img) {
	flex: 1 1 auto;
	width: auto;
	padding: 0;
}
.tax-berocket_brand .top-description h2 {
	/* boilerplate label ("About Brand" on all 22 pages), not a brand face —
	   eyebrow treatment, body face */
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--text-muted);
	margin: 0 0 10px;
}
.tax-berocket_brand .top-description p {
	font-family: var(--kc-font-body);
	font-size: 15.5px;
	line-height: 1.7;
	color: var(--text);
	margin: 0;
}

/* Data-driven degradation (term meta measured across all 22 brands):
   6 brands have no jwa-tax-img → the template prints <img src=""> (a broken-
   image icon); 4 more have a logo but an empty description → a heading over
   an empty <p>. Hide exactly the missing half; hide the whole panel only
   when both halves are empty. CSS-only — the DOM (and SEO parity) keep every
   node. */
.tax-berocket_brand .top-description .logo-img img[src=""] { display: none; }
.tax-berocket_brand .top-description .logo-img:has(> img[src=""]) { display: none; }
/* Empty-description brands serve `<p></p>` but a minifier strips it before
   render (measured: the column holds only the h2 at runtime) — so the hook is
   "no non-empty <p>", which covers both the stripped and the served form. */
.tax-berocket_brand .top-description > div:not(.logo-img):not(:has(p:not(:empty))) { display: none; }
.tax-berocket_brand .top-description:has(.logo-img > img[src=""]):not(:has(p:not(:empty))) { display: none; }

@media (max-width: 768px) {
	.tax-berocket_brand .top-description {
		flex-wrap: wrap;
		padding: 20px 18px;
		gap: 18px;
	}
	.tax-berocket_brand .top-description .logo-img {
		flex: 0 0 100%;
		max-width: none;
	}
	.tax-berocket_brand .top-description .logo-img img {
		max-height: 84px;
		margin: 0;
	}
	.tax-berocket_brand .top-description p { font-size: 15px; }
}

/* ===== 86-blog.css ===== */
/* ==========================================================================
   86 · Blog hub (/blog/ + /blog/page/N/) and single-post reading pass
   Owner: blog-hub. Tokens only — no raw brand values, not even as rgba().
   Every rule is keyed off body.kc-blog-rd, which inc-blog.php only adds while
   the kill switch (kc_blog_off) is unset: one option strips the whole skin.
   The DOM underneath is Savoy's own loop — same URLs, H1, titles, excerpts,
   dates, anchors. This file only changes how those bytes are drawn.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1 · Hub frame — cap the full-bleed 1250px sprawl at the site's 1160 column
   -------------------------------------------------------------------------- */
body.kc-blog-rd.blog .nm-blog-wrap > .breadcrumbs,
body.kc-blog-rd.blog .nm-blog-wrap > h1.title {
	max-width: 1190px;
	margin-left: auto;
	margin-right: auto;
	padding-left: 15px;   /* theme's own gutter; cards below share it */
	padding-right: 15px;
}
body.kc-blog-rd.blog .nm-blog-wrap > .breadcrumbs {
	font-family: var(--kc-font-body);
	font-size: 12px;
	color: var(--text-muted);
}
body.kc-blog-rd.blog .nm-blog-wrap > h1.title {
	font-family: var(--kc-font-display);
	font-weight: 400;              /* Gazella ships one weight */
	font-size: 38px;
	line-height: 1.1;
	color: var(--text);
	margin-top: 10px;
	margin-bottom: 6px;
}
body.kc-blog-rd.blog .nm-blog-content-col {
	float: none;
	max-width: 1190px;
	margin: 0 auto;
}

/* --------------------------------------------------------------------------
   2 · Topic chips (server-rendered by inc-blog.php, links are real archives)
   -------------------------------------------------------------------------- */
body.kc-blog-rd.blog .kc-blog-chips {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--kc-space-2);
	margin: 6px 0 22px;
}
body.kc-blog-rd.blog .kc-blog-chip {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	min-height: 44px;              /* touch floor */
	padding: 0 16px;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	line-height: 1.2;
	color: var(--text);
	background: var(--surface-raised);
	border: 1px solid var(--border);
	border-radius: 999px;
	text-decoration: none;
	transition: border-color .18s ease;
}
body.kc-blog-rd.blog .kc-blog-chip:hover {
	border-color: var(--border-strong);
	text-decoration: underline;
}
/* "Where you are", not "what you picked" — pine is reserved for the
   customer's own choices, so the current chip goes quiet ink instead. */
body.kc-blog-rd.blog .kc-blog-chip.is-current {
	background: var(--text);
	border-color: var(--text);
	color: var(--surface);
}
body.kc-blog-rd.blog .kc-blog-chip-n {
	font-weight: 400;
	font-variant-numeric: tabular-nums;
	opacity: .72;
}

/* --------------------------------------------------------------------------
   3 · Card grid — Savoy's float block-grid becomes a real grid
   -------------------------------------------------------------------------- */
body.kc-blog-rd.blog #nm-blog-list {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 22px;
	margin: 0;
	padding: 0;
}
/* The theme's clearfix — as grid items these empty table boxes would occupy
   the first two cells and shove every card off by two. */
body.kc-blog-rd.blog #nm-blog-list::before,
body.kc-blog-rd.blog #nm-blog-list::after { content: none; display: none; }

body.kc-blog-rd.blog #nm-blog-list > li {
	float: none;
	width: auto;
	height: auto;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-radius: var(--card-radius);
	overflow: hidden;
	transition: border-color .18s ease, transform .18s ease;
}
body.kc-blog-rd.blog #nm-blog-list > li:hover {
	border-color: var(--border-strong);
	transform: translateY(-2px);
}

/* image */
body.kc-blog-rd.blog #nm-blog-list .nm-post-thumbnail {
	order: 0;
	margin: 0;
	aspect-ratio: 16 / 9;
	background: var(--card-shot-bg);
	overflow: hidden;
}
body.kc-blog-rd.blog #nm-blog-list .nm-post-thumbnail a { display: block; height: 100%; }
body.kc-blog-rd.blog #nm-blog-list .nm-post-thumbnail img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}
body.kc-blog-rd.blog #nm-blog-list .nm-image-overlay { display: none; }

/* title */
body.kc-blog-rd.blog #nm-blog-list .nm-post-title {
	order: 1;
	padding: 14px 18px 0;
	margin: 0;
}
body.kc-blog-rd.blog #nm-blog-list .nm-post-title a {
	font-family: var(--kc-font-body);
	font-size: 16px;
	line-height: 1.4;
	font-weight: 600;
	color: var(--text);
	text-decoration: none;
}
body.kc-blog-rd.blog #nm-blog-list > li:hover .nm-post-title a { text-decoration: underline; }

/* excerpt + More */
body.kc-blog-rd.blog #nm-blog-list .nm-post-content {
	order: 2;
	padding: 8px 18px 0;
}
body.kc-blog-rd.blog #nm-blog-list .nm-post-excerpt p {
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	line-height: 1.6;
	color: var(--text-muted);
	margin: 0;
	display: -webkit-box;
	-webkit-line-clamp: 3;
	line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
body.kc-blog-rd.blog #nm-blog-list .nm-post-read-more {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	margin-top: 8px;
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	font-weight: 600;
	color: var(--trust);
	text-decoration: none;
}
body.kc-blog-rd.blog #nm-blog-list .nm-post-read-more:hover { text-decoration: underline; }

/* date · read time — pinned to the card floor so rows align.
   The span is targeted alongside the div: the theme's generated styles size
   the span directly, and a direct match always beats inheritance. */
body.kc-blog-rd.blog #nm-blog-list .nm-post-meta,
body.kc-blog-rd.blog #nm-blog-list .nm-post-meta span {
	font-family: var(--kc-font-body);
	font-size: 12px;
	line-height: 1.5;
	color: var(--text-muted);
	font-variant-numeric: tabular-nums;
}
body.kc-blog-rd.blog #nm-blog-list .nm-post-meta {
	order: 3;
	margin-top: auto;
	padding: 12px 18px 16px;
}
body.kc-blog-rd.blog #nm-blog-list .nm-post-meta::before { content: none; }
body.kc-blog-rd.blog .kc-blog-dot { margin: 0 6px; }

/* --------------------------------------------------------------------------
   4 · Lead card — first post of page 1, image beside text
   -------------------------------------------------------------------------- */
body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead {
	grid-column: 1 / -1;
	display: grid;
	grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
	grid-template-rows: auto auto 1fr;
}
body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-thumbnail {
	grid-column: 1;
	grid-row: 1 / -1;
	aspect-ratio: auto;
	height: 100%;
	min-height: 350px;
}
body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-title {
	grid-column: 2;
	grid-row: 1;
	padding: 26px 30px 0;
}
body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-title a {
	font-size: 25px;
	line-height: 1.28;
	font-weight: 700;
}
/* Decorative cue, drawn not written; truthful — the loop is date-descending. */
body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-title::before {
	content: "Latest";
	display: block;
	font-size: 10px;
	letter-spacing: .22em;
	text-transform: uppercase;
	font-weight: 600;
	color: var(--trust);
	margin-bottom: 10px;
}
body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-content {
	grid-column: 2;
	grid-row: 2;
	padding: 12px 30px 0;
}
body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-excerpt p {
	font-size: 14.5px;
	line-height: 1.65;
	-webkit-line-clamp: 4;
	line-clamp: 4;
}
body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-meta {
	grid-column: 2;
	grid-row: 3;
	align-self: end;
	margin-top: 0;
	padding: 14px 30px 26px;
}

/* --------------------------------------------------------------------------
   5 · Pagination — Savoy's own /blog/page/N/ links, drawn as buttons.
       "Newer" sits left (back toward page 1), "Older" right.
   -------------------------------------------------------------------------- */
body.kc-blog-rd.blog #nm-blog-pagination { margin: 36px 0 8px; }
body.kc-blog-rd.blog #nm-blog-pagination .col-xs-12 {
	display: flex;
	align-items: center;
	gap: 12px;
}
body.kc-blog-rd.blog #nm-blog-pagination .nm-blog-prev,
body.kc-blog-rd.blog #nm-blog-pagination .nm-blog-next {
	float: none;
	width: auto;
}
body.kc-blog-rd.blog #nm-blog-pagination .nm-blog-next { order: 1; margin-right: auto; }
body.kc-blog-rd.blog #nm-blog-pagination .nm-blog-prev { order: 2; margin-left: auto; }
body.kc-blog-rd.blog #nm-blog-pagination a {
	display: inline-flex;
	align-items: center;
	min-height: 48px;
	padding: 0 20px;
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	font-weight: 600;
	color: var(--text);
	background: var(--surface-raised);
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	text-decoration: none;
	transition: border-color .18s ease;
}
body.kc-blog-rd.blog #nm-blog-pagination a:hover { border-color: var(--text); }
body.kc-blog-rd.blog .nm-blog-prev a::after  { content: "\2192"; margin-left: 8px; }
body.kc-blog-rd.blog .nm-blog-next a::before { content: "\2190"; margin-right: 8px; }

/* --------------------------------------------------------------------------
   6 · Hub responsive
   -------------------------------------------------------------------------- */
@media (max-width: 1024px) {
	body.kc-blog-rd.blog #nm-blog-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead {
		grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
	}
}

@media (max-width: 768px) {
	body.kc-blog-rd.blog .nm-blog-wrap > h1.title { font-size: 30px; }

	/* chips become an edge-to-edge scroll strip; the page itself never
	   scrolls sideways, only this row does */
	body.kc-blog-rd.blog .kc-blog-chips {
		flex-wrap: nowrap;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
		margin: 6px -15px 18px;
		padding: 2px 15px;
	}
	body.kc-blog-rd.blog .kc-blog-chips::-webkit-scrollbar { display: none; }
	body.kc-blog-rd.blog .kc-blog-chip { flex: none; }

	body.kc-blog-rd.blog #nm-blog-list {
		grid-template-columns: minmax(0, 1fr);
		gap: 12px;
	}

	/* lead card: stacked, full presence */
	body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead {
		display: flex;
		flex-direction: column;
	}
	body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-thumbnail {
		aspect-ratio: 16 / 9;
		height: auto;
		min-height: 0;
	}
	body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-title { padding: 14px 16px 0; }
	body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-title a { font-size: 19px; line-height: 1.32; }
	body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-title::before { margin-bottom: 8px; }
	body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-content { padding: 8px 16px 0; }
	body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-excerpt p {
		font-size: 13.5px;
		-webkit-line-clamp: 3;
		line-clamp: 3;
	}
	body.kc-blog-rd.blog #nm-blog-list > li.kc-blog-lead .nm-post-meta {
		/* the desktop lead is a grid where align-self:end means "card floor";
		   in this flex COLUMN the cross axis is horizontal, so the same value
		   would shove the date to the right edge and shrink-wrap it */
		align-self: stretch;
		margin-top: auto;
		padding: 12px 16px 16px;
	}

	/* every other card: compact press row — square thumb, title, meta.
	   The excerpt and More stay in the DOM (parity, indexing); the title link
	   already carries the tap. */
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) {
		display: grid;
		grid-template-columns: 104px minmax(0, 1fr);
		grid-template-rows: auto auto;
		align-content: start;
		gap: 6px 14px;
		padding: 12px;
	}
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) .nm-post-thumbnail {
		grid-column: 1;
		grid-row: 1 / span 2;
		aspect-ratio: 1 / 1;
		border-radius: var(--kc-radius-sm);
	}
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) .nm-post-title {
		grid-column: 2;
		grid-row: 1;
		padding: 0;
	}
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) .nm-post-title a {
		font-size: 14.5px;
		line-height: 1.35;
	}
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) .nm-post-content { display: none; }
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) .nm-post-meta {
		grid-column: 2;
		grid-row: 2;
		align-self: start;
		margin: 0;
		padding: 0;
	}
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) .nm-post-meta,
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) .nm-post-meta span { font-size: 11.5px; }
	/* Whole row is the tap target (a bare text link would be ~20px tall).
	   Safe here because the row's only other link — the thumbnail — goes to
	   the same URL, and the More link is display:none in this form. */
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) { position: relative; }
	body.kc-blog-rd.blog #nm-blog-list > li:not(.kc-blog-lead) .nm-post-title a::after {
		content: "";
		position: absolute;
		inset: 0;
	}
	body.kc-blog-rd.blog #nm-blog-list > li:hover { transform: none; }
}

/* --------------------------------------------------------------------------
   7 · Single post — the PDP long-form spec applied to articles:
       one centered 72ch reading column, PDP heading hierarchy, constrained
       imagery. Scoped to direct children so the header block keeps its own
       rules and nothing leaks into widgets or comments markup.
   -------------------------------------------------------------------------- */
body.kc-blog-rd.single-post .nm-post-content-col { float: none; }

body.kc-blog-rd.single-post .nm-post-content.entry-content {
	max-width: 72ch;                 /* the PDP long-form measure */
	margin: 0 auto;
	font-family: var(--kc-font-body);
	font-size: 16px;
	line-height: 1.75;
	color: var(--text);
}

/* header: image, caption, H1, byline */
body.kc-blog-rd.single-post .entry-content > .nm-post-header { margin-bottom: 26px; }
body.kc-blog-rd.single-post .nm-post-featured-image img {
	width: 100%;
	height: auto;
	display: block;
	border-radius: var(--kc-radius-md);
}
body.kc-blog-rd.single-post .nm-post-featured-image .credits {
	font-size: 12px;
	line-height: 1.5;
	color: var(--text-muted);
	margin: 8px 2px 0;
}
/* Body face, not Gazella: an article title is the piece's name — data, not
   brand voice — and fails test 3 of kc-redesign.css §20 exactly the way a
   product name does. Mirrors §20.1's PDP treatment (600 / 34px / -0.01em).
   The hub's H1 keeps the display face; that one IS marketing chrome. */
body.kc-blog-rd.single-post .entry-content h1 {
	font-family: var(--kc-font-body);
	font-weight: 600;
	font-size: 34px;
	line-height: 1.2;
	letter-spacing: -0.01em;
	color: var(--text);
	margin: 16px 0 10px;
}
body.kc-blog-rd.single-post .nm-single-post-meta-top {
	font-size: 13px;
	color: var(--text-muted);
}
body.kc-blog-rd.single-post .nm-single-post-meta-top em { font-style: normal; }
body.kc-blog-rd.single-post .nm-single-post-meta-top a {
	color: var(--trust);
	text-decoration: none;
	margin: 0 3px;
}
body.kc-blog-rd.single-post .nm-single-post-meta-top a:hover { text-decoration: underline; }
body.kc-blog-rd.single-post .nm-single-post-meta-top time { font-variant-numeric: tabular-nums; }

/* body copy — PDP hierarchy (24 / 18 / 16 over a 16px base) */
body.kc-blog-rd.single-post .entry-content > p { margin: 0 0 16px; }
body.kc-blog-rd.single-post .entry-content > h2 {
	font-family: var(--kc-font-body);
	font-size: 24px;
	line-height: 1.25;
	font-weight: 700;
	letter-spacing: -0.01em;
	color: var(--text);
	margin: 36px 0 12px;
}
body.kc-blog-rd.single-post .entry-content > h3 {
	font-size: 18px;
	line-height: 1.35;
	font-weight: 700;
	color: var(--text);
	margin: 26px 0 8px;
}
body.kc-blog-rd.single-post .entry-content > h4 {
	font-size: 16px;
	font-weight: 700;
	color: var(--text);
	margin: 20px 0 6px;
}
body.kc-blog-rd.single-post .entry-content > p a,
body.kc-blog-rd.single-post .entry-content > ul a,
body.kc-blog-rd.single-post .entry-content > ol a,
body.kc-blog-rd.single-post .entry-content > h2 a,
body.kc-blog-rd.single-post .entry-content > h3 a,
body.kc-blog-rd.single-post .entry-content > table a {
	color: var(--trust);
	text-decoration: underline;
	text-underline-offset: 2px;
}
body.kc-blog-rd.single-post .entry-content > p a:hover,
body.kc-blog-rd.single-post .entry-content > ul a:hover,
body.kc-blog-rd.single-post .entry-content > ol a:hover { color: var(--text); }
body.kc-blog-rd.single-post .entry-content > p strong { font-weight: 700; color: var(--text); }

body.kc-blog-rd.single-post .entry-content > hr {
	border: 0;
	border-top: 1px solid var(--border);
	opacity: 1;
	width: 100%;
	margin: 30px 0;
}
body.kc-blog-rd.single-post .entry-content > ul,
body.kc-blog-rd.single-post .entry-content > ol {
	margin: 0 0 18px;
	padding-left: 22px;
}
body.kc-blog-rd.single-post .entry-content > ul li,
body.kc-blog-rd.single-post .entry-content > ol li { margin: 0 0 8px; }
body.kc-blog-rd.single-post .entry-content > blockquote {
	border-left: 3px solid var(--border-strong);
	margin: 24px 0;
	padding: 4px 0 4px 18px;
	color: var(--text-muted);
}

/* imagery inside the article — constrained, never overflowing the measure */
body.kc-blog-rd.single-post .entry-content > figure,
body.kc-blog-rd.single-post .entry-content > p > img { margin: 28px 0; }
body.kc-blog-rd.single-post .entry-content figure img,
body.kc-blog-rd.single-post .entry-content > p img {
	max-width: 100%;
	height: auto;
	border-radius: var(--kc-radius-md);
}
body.kc-blog-rd.single-post .entry-content figcaption {
	font-size: 12.5px;
	color: var(--text-muted);
	margin-top: 8px;
}

/* tables — the PDP treatment */
body.kc-blog-rd.single-post .entry-content > table,
body.kc-blog-rd.single-post .entry-content figure.wp-block-table table {
	width: 100%;
	border-collapse: collapse;
	margin: 8px 0 22px;
	font-size: 14px;
	background: var(--surface-raised);
	border: 1px solid var(--border);
}
body.kc-blog-rd.single-post .entry-content table td,
body.kc-blog-rd.single-post .entry-content table th {
	padding: 10px 14px;
	border-bottom: 1px solid var(--border);
	text-align: left;
	vertical-align: top;
	line-height: 1.45;
}
body.kc-blog-rd.single-post .entry-content table tr:last-child td,
body.kc-blog-rd.single-post .entry-content table tr:last-child th { border-bottom: 0; }

/* table of contents plugin box */
body.kc-blog-rd.single-post .entry-content #toc_container {
	width: 100%;
	background: var(--surface-raised);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
	padding: 18px 22px;
	margin: 6px 0 30px;
	font-size: 14px;
}
body.kc-blog-rd.single-post #toc_container .toc_title {
	font-size: 11px;
	letter-spacing: .14em;
	text-transform: uppercase;
	font-weight: 600;
	color: var(--text-muted);
	margin: 0 0 10px;
}
body.kc-blog-rd.single-post #toc_container .toc_list {
	list-style: none;
	margin: 0;
	padding: 0;
}
body.kc-blog-rd.single-post #toc_container .toc_list li { margin: 0 0 8px; padding: 0; }
body.kc-blog-rd.single-post #toc_container .toc_list a {
	color: var(--text);
	text-decoration: none;
}
body.kc-blog-rd.single-post #toc_container .toc_list a:hover {
	color: var(--trust);
	text-decoration: underline;
}
body.kc-blog-rd.single-post #toc_container .toc_number {
	color: var(--text-muted);
	margin-right: 6px;
	font-variant-numeric: tabular-nums;
}
/* the plugin's [hide] toggle ships in red — quiet it down to a utility link */
body.kc-blog-rd.single-post #toc_container .toc_toggle,
body.kc-blog-rd.single-post #toc_container .toc_toggle a {
	color: var(--text-muted);
	font-weight: 400;
}

/* tags / categories / share strip + comments: same column as the article */
body.kc-blog-rd.single-post .nm-single-post-meta-wrap {
	max-width: 72ch;
	margin: 34px auto 0;
	border-top: 1px solid var(--border);
	padding-top: 18px;
	font-size: 13px;
	color: var(--text-muted);
}
body.kc-blog-rd.single-post .nm-single-post-tags a {
	display: inline-flex;
	align-items: center;
	min-height: 32px;
	padding: 0 12px;
	margin: 0 6px 6px 0;
	font-size: 12px;
	font-weight: 600;
	color: var(--text);
	background: var(--surface-raised);
	border: 1px solid var(--border);
	border-radius: 999px;
	text-decoration: none;
}
body.kc-blog-rd.single-post .nm-single-post-tags a:hover { border-color: var(--border-strong); }
body.kc-blog-rd.single-post .nm-single-post-categories a {
	color: var(--trust);
	text-decoration: underline;
	text-underline-offset: 2px;
}
body.kc-blog-rd.single-post .nm-post-share a { color: var(--text-muted); }
body.kc-blog-rd.single-post .nm-post-share a:hover { color: var(--trust); }

body.kc-blog-rd.single-post .nm-comments-inner {
	max-width: 72ch;
	margin: 0 auto;
}
body.kc-blog-rd.single-post .comment-reply-title {
	font-family: var(--kc-font-body);
	font-size: 20px;
	font-weight: 700;
	color: var(--text);
}

@media (max-width: 768px) {
	body.kc-blog-rd.single-post .entry-content h1 { font-size: 28px; }
	body.kc-blog-rd.single-post .entry-content > h2 { font-size: 22px; }
}

/* --------------------------------------------------------------------------
   8 · Focus + motion discipline
   -------------------------------------------------------------------------- */
body.kc-blog-rd .kc-blog-chip:focus-visible,
body.kc-blog-rd.blog #nm-blog-list .nm-post-title a:focus-visible,
body.kc-blog-rd.blog #nm-blog-list .nm-post-thumbnail a:focus-visible,
body.kc-blog-rd.blog #nm-blog-pagination a:focus-visible {
	outline: 2px solid var(--trust);
	outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
	body.kc-blog-rd.blog #nm-blog-list > li,
	body.kc-blog-rd.blog .kc-blog-chip,
	body.kc-blog-rd.blog #nm-blog-pagination a { transition: none; }
	body.kc-blog-rd.blog #nm-blog-list > li:hover { transform: none; }
}

/* ===== 87-city.css ===== */
/* ==========================================================================
   87. CITY PAGES — presentation layer for the SEO landing-page family
   --------------------------------------------------------------------------
   Scope: body.kc-city (+ -classic / -raw), set by inc-city.php from template
   markers. ~140 pages, 100% ranked copy — this file restyles, it never hides
   and never reflows content order. Tokens only; every !important below is a
   fight with an inline style hard-coded in post_content (DB copy this surface
   is forbidden to edit), never with another stylesheet — each one is
   annotated at the site of use. Zero !important against any .css file.
   ========================================================================== */

/* --------------------------------------------------------------------------
   87.0 Un-bury the page (the #kcnl overlay bug)
   --------------------------------------------------------------------------
   Savoy's WPBakery page template floats the content column (.col-xs-12) and
   never clears it, so its wrapper (.nm-row-full) measures 0px tall. The
   newsletter band (#kcnl, mu-plugins/kc-newsletter.php via get_footer) is the
   first in-flow element after that wrapper: it lands at y≈120, right on top
   of the floated page, and its opaque pine gradient paints the whole page
   green (docs/redesign/deadpages-recon.md §0 — verified on sale/brand/city).
   Clearance pushes the band below the float. Scoped to this surface; the
   band's own file belongs to the newsletter line and is not touched.
   -------------------------------------------------------------------------- */
body.kc-city #kcnl { clear: both; }

/* --------------------------------------------------------------------------
   87.1 Title band (classic template: the centred [page_title] H1 row)
   --------------------------------------------------------------------------
   The H1 arrives from post_content with inline styles:
   text-align:center; font-size:35px; color:black; font-weight:bold.
   Only font-size (phones) and font-weight need force — see notes at each.
   Font-family, spacing and the ornament are free: inline doesn't set them.
   -------------------------------------------------------------------------- */
body.kc-city-classic .wpb-content-wrapper > .nm-row-full:first-child {
	padding: 26px 0 30px;
	border-bottom: 1px solid var(--border);
	margin-bottom: 34px;
}
body.kc-city-classic .wpb-content-wrapper > .nm-row-full:first-child .wpb_text_column {
	margin-bottom: 0;
}
body.kc-city-classic .wpb-content-wrapper h1 {
	font-family: var(--kc-font-display);
	/* Gazella ships exactly one weight (400). The inline font-weight:bold
	   would make the browser smear a synthetic bold over a 70s fat face —
	   the DB wrote the inline style, so only !important reaches it. */
	font-weight: 400 !important;
	letter-spacing: 0.01em;
	line-height: 1.16;
	text-wrap: balance;
	max-width: 21ch;
	margin: 0 auto;
}
/* pass 1 of the display-face law: 35px inline ≥ 28px, so Gazella stands */
body.kc-city-classic .wpb-content-wrapper h1::after {
	content: "";
	display: block;
	width: 52px;
	height: 3px;
	margin: 16px auto 0;
	background: var(--trust);
	border-radius: 2px;
}
@media (max-width: 768px) {
	/* The inline 35px is a desktop number: a 40+ character title in a fat
	   face needs a notch off on a 375px screen. Floor 29px keeps the
	   ≥28px display-face law intact. Inline style ⇒ !important. */
	body.kc-city-classic .wpb-content-wrapper h1 {
		font-size: clamp(29px, 8vw, 35px) !important;
	}
	body.kc-city-classic .wpb-content-wrapper > .nm-row-full:first-child {
		padding: 18px 0 24px;
		margin-bottom: 26px;
	}
}

/* --------------------------------------------------------------------------
   87.2 Body copy rows (.city-description-block × N per page)
   --------------------------------------------------------------------------
   The old skeleton (flex row, alternation via -reverse, 50vw images) stays —
   it is serviceable and the copy layout order is content, not presentation.
   What changes: the type wears the brand (Gazella H2s at ≥28px, Poppins
   body), links read pine, and the stock photos get the house frame (hairline
   + hard shadow) so six random images become one system.
   -------------------------------------------------------------------------- */
body.kc-city-classic .city-description-block h2 {
	/* Every one of these H2s ships inline font-family:"Abril Fatface" from
	   post_content — a font this site no longer loads, so it renders as
	   fallback bold sans (the sale-page recon flagged the same residue).
	   Inline style ⇒ !important is the only reach. Weight is already 400
	   inline, so only the face needs force. */
	font-family: var(--kc-font-display) !important;
	font-weight: 400;
	font-size: clamp(28px, 3.2vw, 33px);
	line-height: 1.18;
	color: var(--text);
	letter-spacing: 0.01em;
	text-wrap: balance;
	margin-bottom: 18px;
}
body.kc-city-classic .city-description-block .wpb_text_column {
	font-family: var(--kc-font-body);
	font-size: 15.5px;
	line-height: 1.85;
	color: var(--text);
	max-width: 60ch;
}
body.kc-city-classic .city-description-block .wpb_text_column a {
	color: var(--trust);
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
	text-decoration-color: var(--kc-pine-300);
	transition: text-decoration-color 0.2s;
}
body.kc-city-classic .city-description-block .wpb_text_column a:hover,
body.kc-city-classic .city-description-block .wpb_text_column a:focus-visible {
	text-decoration-color: var(--trust);
}
body.kc-city-classic .city-description-block .wpb_text_column strong {
	font-weight: 600;
}
body.kc-city-classic .city-description-block .wpb_text_column ul,
body.kc-city-classic .city-description-block .wpb_text_column ol {
	margin: 14px 0 0;
	padding-left: 2px;
	list-style: none;
}
body.kc-city-classic .city-description-block .wpb_text_column li {
	margin: 0 0 10px;
	padding-left: 18px;
	position: relative;
}
body.kc-city-classic .city-description-block .wpb_text_column li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.72em;
	width: 7px;
	height: 2px;
	background: var(--trust);
}
/* the house picture frame; width itself belongs to the old 50vw rule */
body.kc-city-classic .city-description-block .vc_single_image-wrapper {
	display: inline-block;
	border: 1.5px solid var(--text);
	border-radius: var(--kc-radius-md);
	box-shadow: 5px 5px 0 var(--text);
	overflow: hidden;
	background: var(--surface-raised);
	max-width: calc(100% - 5px); /* keep the shadow inside the column */
}
body.kc-city-classic .city-description-block .vc_single_image-wrapper img {
	display: block;
	max-width: 100%;
	height: auto;
}

/* --------------------------------------------------------------------------
   87.3 Contact + subscribe rows (light touch: headings join the system)
   -------------------------------------------------------------------------- */
body.kc-city-classic .wpb-content-wrapper .contact h2 {
	/* same inline Abril Fatface residue as §87.2 */
	font-family: var(--kc-font-display) !important;
	font-weight: 400;
	font-size: clamp(28px, 3.2vw, 33px);
	letter-spacing: 0.01em;
	color: var(--text);
}

/* --------------------------------------------------------------------------
   87.4 "Areas we cover" — appended internal-link module (map + town chips)
   --------------------------------------------------------------------------
   Markup from inc-city.php. Appears on local classic pages only. Visual
   values mirror the homepage's delivery-zone module (95-home-seo.css §95.4/5)
   so the map reads as the same object everywhere; restated here under the
   kc-ct- namespace because that file is scoped to body.home and belongs to
   another owner. clear:both on the section root drops it below the
   template's floated rows (same disease as §87.0, handled locally).
   -------------------------------------------------------------------------- */
body.kc-city .kc-ct-areas {
	clear: both;
	border-top: 1px solid var(--border);
	background: var(--surface);
	padding: 58px 0 66px;
}
body.kc-city .kc-ct-areas-in {
	max-width: 1160px;
	margin: 0 auto;
	padding: 0 var(--gutter);
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(300px, 440px);
	gap: 24px 56px;
	align-items: start;
}
body.kc-city .kc-ct-eyebrow {
	display: inline-block;
	font-family: var(--kc-font-body);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: var(--trust);
	margin-bottom: 10px;
}
body.kc-city .kc-ct-areas h2 {
	font-family: var(--kc-font-display);
	font-weight: 400;
	font-size: clamp(28px, 3.2vw, 33px);
	line-height: 1.18;
	color: var(--text);
	letter-spacing: 0.01em;
	margin: 0 0 22px;
}
body.kc-city .kc-ct-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	list-style: none;
	margin: 0;
	padding: 0;
}
body.kc-city .kc-ct-chips li { margin: 0; }
body.kc-city .kc-ct-chips a,
body.kc-city .kc-ct-chip-here {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding: 0 15px;
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	background: var(--surface-raised);
	color: var(--text);
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	line-height: 1.2;
}
body.kc-city .kc-ct-chips a {
	transition: border-color 0.2s, color 0.2s;
}
body.kc-city .kc-ct-chips a:hover,
body.kc-city .kc-ct-chips a:focus-visible {
	border-color: var(--trust);
	color: var(--trust);
}
/* the town this page is about: state, not a control — quiet ink, no link,
   and deliberately not pine (green is reserved for the customer's choices) */
body.kc-city .kc-ct-chip-here {
	background: var(--text);
	border-color: var(--text);
	color: var(--surface);
}
body.kc-city .kc-ct-chip-all a {
	background: transparent;
	border-style: dashed;
	color: var(--trust);
}

/* ---- the map itself (values mirrored from the homepage module) ---- */
body.kc-city .kc-ct-mapfig { margin: 0; }
body.kc-city .kc-ct-map {
	display: block;
	width: 100%;
	height: auto;
	background: var(--surface-raised);
	border: 1.5px solid var(--text);
	border-radius: var(--kc-radius-md);
	box-shadow: 3px 3px 0 var(--text);
	overflow: hidden;
}
body.kc-city .kc-ct-m-ground { fill: var(--surface-raised); }
body.kc-city .kc-ct-hg0 { stop-color: var(--trust); stop-opacity: 0.11; }
body.kc-city .kc-ct-hg1 { stop-color: var(--trust); stop-opacity: 0.075; }
body.kc-city .kc-ct-hg2 { stop-color: var(--trust); stop-opacity: 0; }
body.kc-city .kc-ct-m-water { fill: var(--text); opacity: 0.06; }
body.kc-city .kc-ct-m-shore {
	fill: none;
	stroke: var(--text);
	stroke-opacity: 0.38;
	stroke-width: 2;
	stroke-linejoin: round;
}
body.kc-city .kc-ct-m-isle {
	fill: var(--surface-raised);
	stroke: var(--text);
	stroke-opacity: 0.38;
	stroke-width: 1.6;
}
body.kc-city .kc-ct-m-road {
	fill: none;
	stroke: var(--text);
	stroke-opacity: 0.13;
	stroke-width: 2.6;
	stroke-linecap: round;
	stroke-linejoin: round;
}
body.kc-city .kc-ct-m-roadlab text {
	font-family: var(--kc-font-body);
	font-variant-numeric: tabular-nums;
	font-size: 12px;
	font-weight: 600;
	letter-spacing: 0.06em;
	fill: var(--text);
	fill-opacity: 0.4;
}
body.kc-city .kc-ct-m-zone {
	fill: none;
	stroke: var(--trust);
	stroke-opacity: 0.42;
	stroke-width: 2.2;
	stroke-dasharray: 10 9;
}
body.kc-city .kc-ct-m-pins circle {
	fill: var(--trust);
	fill-opacity: 0.92;
	stroke: var(--surface-raised);
	stroke-width: 2;
}
body.kc-city .kc-ct-m-city text {
	font-family: var(--kc-font-body);
	font-size: 15.5px;
	font-weight: 600;
	letter-spacing: 0.01em;
	fill: var(--text);
	fill-opacity: 0.82;
}
body.kc-city .kc-ct-m-city .kc-ct-m-city-big {
	font-size: 17px;
	font-weight: 700;
	letter-spacing: 0.04em;
}
body.kc-city .kc-ct-m-lab text {
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	font-weight: 600;
	letter-spacing: 0.17em;
	fill: var(--text);
	fill-opacity: 0.6;
}
body.kc-city .kc-ct-m-lab .kc-ct-m-lake { fill-opacity: 0.44; }
body.kc-city .kc-ct-m-key {
	stroke: var(--trust);
	stroke-opacity: 0.55;
	stroke-width: 2.2;
	stroke-dasharray: 10 9;
}
body.kc-city .kc-ct-m-compass text {
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 700;
	fill: var(--text);
	fill-opacity: 0.55;
}
body.kc-city .kc-ct-m-compass path {
	fill: none;
	stroke: var(--text);
	stroke-opacity: 0.55;
	stroke-width: 2;
}
body.kc-city .kc-ct-mapcap {
	margin: 12px 2px 0;
	font-family: var(--kc-font-body);
	font-size: 11.5px;
	line-height: 1.5;
	color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   87.5 Hero — the burned-text banner slide rebuilt as a live component
   --------------------------------------------------------------------------
   Markup from inc-city.php (pre_do_shortcode_tag replaces the classic
   template's [nm_banner_slider] burned composite, and the mail templatera's
   phone-only [vc_single_image 30966]). Desktop instance renders inside the
   old col-lg-9 slider column, next to the legacy right rail; phone instance
   (mail pages) inside the xs-only column. House frame = the same hairline +
   hard shadow object language as §87.2 images and the §87.4 map. The CTA is
   typed by 20-buttons.css (.kc-btn.kc-btn-secondary: navigation, not a
   commit action) — no button colour lives here.
   -------------------------------------------------------------------------- */
body.kc-city .kc-cthero {
	border: 1.5px solid var(--text);
	border-radius: var(--kc-radius-md);
	box-shadow: 3px 3px 0 var(--text);
	background: var(--surface-raised);
	overflow: hidden;
	margin: 0 3px 3px 0; /* keep the hard shadow inside the column */
	font-variant-numeric: tabular-nums;
}
body.kc-city .kc-cthero-in {
	display: flex;
	align-items: stretch;
	min-height: 330px;
}
/* local slide had its photo on the left; mail had the mailbox on the right */
body.kc-city .kc-cthero--local .kc-cthero-in { flex-direction: row-reverse; }
body.kc-city .kc-cthero-copy {
	flex: 1 1 56%;
	min-width: 0;
	padding: 34px 36px;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: flex-start;
}
body.kc-city .kc-cthero-title {
	margin: 0;
	font-family: var(--kc-font-display);
	font-weight: 400;
	/* floor 30px: display face stays over the 28px brand line at every width */
	font-size: clamp(30px, 3.4vw, 44px);
	line-height: 1.05;
	letter-spacing: 0.01em;
	text-transform: uppercase;
	color: var(--text);
}
body.kc-city .kc-cthero-title::after {
	/* same ornament as the H1 band (§87.1) — one system, two sizes */
	content: "";
	display: block;
	width: 52px;
	height: 3px;
	margin: 14px 0 0;
	background: var(--trust);
	border-radius: 2px;
}
body.kc-city .kc-cthero-usps {
	list-style: none;
	margin: 26px 0 0;
	padding: 0;
	display: flex;
	gap: 12px;
	align-self: stretch;
}
body.kc-city .kc-cthero-usps li {
	flex: 1 1 0;
	min-width: 0;
	margin: 0;
	padding: 0;
	text-align: center;
}
body.kc-city .kc-cthero-usps li::before { content: none; } /* §87.2 list dash never reaches here (scoped to description rows) but state it */
body.kc-city .kc-cthero-usps svg {
	display: block;
	width: 34px;
	height: 34px;
	margin: 0 auto 8px;
	color: var(--text);
}
body.kc-city .kc-cthero-usps strong {
	display: block;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 700;
	letter-spacing: 0.05em;
	line-height: 1.25;
	color: var(--text);
}
body.kc-city .kc-cthero-usps span {
	display: block;
	margin-top: 2px;
	font-family: var(--kc-font-body);
	font-size: 11.5px;
	line-height: 1.45;
	color: var(--text-muted);
}
/* the one USP that navigates: underlined like every body link on the surface */
body.kc-city .kc-cthero-usp-link {
	display: block;
	color: inherit;
	text-decoration: none;
}
body.kc-city .kc-cthero-usp-link span {
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
	text-decoration-color: var(--kc-pine-300);
	transition: text-decoration-color 0.2s, color 0.2s;
}
body.kc-city .kc-cthero-usp-link:hover strong,
body.kc-city .kc-cthero-usp-link:focus-visible strong { color: var(--trust); }
body.kc-city .kc-cthero-usp-link:hover span,
body.kc-city .kc-cthero-usp-link:focus-visible span {
	color: var(--trust);
	text-decoration-color: var(--trust);
}
body.kc-city .kc-cthero-cta { margin-top: 26px; }
body.kc-city .kc-cthero-photo {
	flex: 1 1 44%;
	min-width: 0;
	position: relative;
}
body.kc-city .kc-cthero-photo img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}
@media (max-width: 767px) {
	body.kc-city .kc-cthero-in,
	body.kc-city .kc-cthero--local .kc-cthero-in {
		flex-direction: column;
		min-height: 0;
	}
	body.kc-city .kc-cthero-copy { padding: 26px 18px 24px; }
	body.kc-city .kc-cthero-title { font-size: clamp(29px, 8.6vw, 38px); }
	body.kc-city .kc-cthero-usps { gap: 8px; }
	body.kc-city .kc-cthero-photo {
		position: static;
		aspect-ratio: 16 / 10;
	}
	body.kc-city .kc-cthero-photo img {
		position: static;
		height: 100%;
	}
}

@media (max-width: 900px) {
	body.kc-city .kc-ct-areas { padding: 44px 0 52px; }
	body.kc-city .kc-ct-areas-in {
		grid-template-columns: minmax(0, 1fr);
		gap: 30px;
	}
	/* phone: the SVG text sizes are tuned for a wide frame; bump them the
	   same way the homepage does when the map runs full-column */
	body.kc-city .kc-ct-m-city text { font-size: 18.5px; }
	body.kc-city .kc-ct-m-city .kc-ct-m-city-big { font-size: 20px; }
	body.kc-city .kc-ct-m-lab text { font-size: 17px; letter-spacing: 0.14em; }
	body.kc-city .kc-ct-m-roadlab text { font-size: 15px; }
	body.kc-city .kc-ct-m-pins circle { r: 7px; }
	body.kc-city .kc-ct-m-pins .kc-ct-m-pin-big { r: 9px; }
}

/* ===== 88-static-pages.css ===== */
/* ==========================================================================
   88 — Legacy page-template rescue (page.php: every plain WP page on the site)
   Owner: orchestrator (SITE_REVIEW_2026-08-26 R-5 / FINAL_UX_AUDIT B4 · M65).
   Tokens only, zero raw hex, zero !important.
   ========================================================================== */

/* --------------------------------------------------------------------------
   88.0 Float containment — the "whole page is a pine-green wall" disease.
   --------------------------------------------------------------------------
   ROOT CAUSE (source-level, not a symptom):
   savoy-child/page.php:18 opens the content row as

       <div class="nm-row-full">          <!-- modifier only, base class missing -->
         <div class="col-xs-12"> … </div> <!-- float:left (grid.css:280) -->

   Savoy's clearfix lives on the BASE class — `.nm-row:before/:after
   {display:table}` + `.nm-row:after{clear:both}` (savoy/assets/css/grid.css
   :34-40) — and `.nm-row-full` exists only as the compound
   `.nm-row.nm-row-full` (grid.css:53). A bare `.nm-row-full` therefore matches
   NO theme rule at all: nothing clears its one floated child, so the row
   computes to height 0 on EVERY page rendered by this template. The mu-plugin
   newsletter band `#kcnl` (kc-newsletter.php:241, `get_footer`,
   position:relative, opaque pine gradient) is the row's next in-flow sibling,
   so it starts at the collapsed row's top and paints over the whole page.

   WHY THIS RULE AND NOT ANOTHER PAGE-ID PATCH:
   the defect is in the template's markup, so the fix is keyed to the
   template's structure — `.nm-page-wrap-inner > .nm-row-full` — and to nothing
   else. No page id, no surface class, no body class. Every page that exists
   today and every page anyone creates later is covered the moment it renders
   through page.php. This replaces the five per-page patches that grew around
   the symptom (measured buried pages the patch list never reached:
   /contact/ · /privacy-policy/ · /terms-of-use/ · /order-tracking/ ·
   /sitemap/ · /landing/ · /wishlist/ — 7 of them, of which the audit found 1).

   It is a byte-faithful copy of the clearfix Savoy wrote for `.nm-row`, so a
   page whose row someone later sets to flex/grid behaves exactly as every
   other Savoy row already does. `content:""` rather than Savoy's `content:" "`
   only so the pseudo-element cannot contribute a space's width if that
   happens.

   `#kcnl{clear:both}` is the second line of defence: the band is injected by a
   mu-plugin into whatever markup precedes it (archives, singulars, templates
   we do not control), so it declines to sit beside ANY float, not just this
   row's. It never renders on cart/checkout (kc-newsletter.php:141 returns
   early there), so it cannot disturb the buying flow.
   -------------------------------------------------------------------------- */
.nm-page-wrap-inner > .nm-row-full::before,
.nm-page-wrap-inner > .nm-row-full::after {
	content: "";
	display: table;
}
.nm-page-wrap-inner > .nm-row-full::after {
	clear: both;
}
#kcnl {
	clear: both;
}

/* --------------------------------------------------------------------------
   88.1 Type scale for editor prose — the FLOOR, not a ceiling.
   --------------------------------------------------------------------------
   ROOT CAUSE: savoy-child/assets/css/main.css:89-167 is a Meyer reset that
   applies `font: inherit` to h1…h6. It loads AFTER savoy/style.css, so it
   erases Savoy's own `h1{30px}` / `h2{28px}` site-wide. The previous owner
   restored headings one page at a time with wrapper classes
   (main.css:5121-5139 `.terms-of-use-content …` / `.privacy-policy-content …
   h2{font-size:24px}` — each class used by exactly one page). Any page without
   such a wrapper — /about/, /sitemap/, and every page created from now on —
   renders its H1 and every H2 byte-identical to its paragraphs (measured on
   /about/: H1 and five H2s all 16px/400 Roboto, the same as its <p>).

   SCOPE = `.wpb_text_column`, THE EDITOR'S TEXT BLOCK. Measured across the
   site, that class is exactly the line between the two kinds of heading:
     · inside it — every heading someone TYPED into a page: /about/'s five
       section heads, /sitemap/'s seven, /privacy-policy/'s twenty-five.
       Nobody has designed these and nobody ever will, one page at a time.
     · outside it — every heading a DESIGNER or a PLUGIN emitted: the
       homepage's `.kc-hm-sechead` / `.kc-hero-copy` (owned by inc-home*),
       /order-tracking/'s `.nm-order-track-top`, the wishlist plugin's empty
       state, and the `vc_custom_heading` banners the editor sized by hand.
   So the floor is not "every page", it is "copy an operator typed" — which is
   the thing that has no owner and keeps arriving. A new page written the
   normal way is covered the moment it is published; a designed component is
   never reached, on any page, now or later.

   SPECIFICITY is deliberately (0,1,1): the structural part sits in :where(),
   and `:where(:not(.vc_custom_heading))` excludes builder-sized headings at
   no cost. That is enough to beat the (0,0,1) reset and nothing else — city
   pages (0,2,1), blog singles (0,3,1) and cart/checkout keep their own type.
   Verified by diffing the computed style of all 197 headings on 21 pages
   before and after.

   NO DISPLAY FACE. Gazella is a single-weight 70s fat face and kc-redesign.css
   §20 licenses it only at >= 28px, on brand surfaces, by choice. A floor that
   lands unattended on pages nobody has designed is by definition not a chosen
   branding moment, and it cannot know what size it will render at, because a
   page-specific rule may still win the size while this one wins the face
   (measured: privacy/terms would have come out as Gazella at 24px in a
   synthesised 600 the font does not have). The floor speaks in the body face
   and takes its hierarchy from size and weight. Gazella stays opt-in.
   -------------------------------------------------------------------------- */
:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column h1:where(:not(.vc_custom_heading)) {
	font-family: var(--kc-font-body);
	font-size: 30px;
	line-height: 1.2;
	font-weight: 600;
	letter-spacing: -.01em;
	color: var(--text);
	margin: 0 0 var(--kc-space-4);
}
:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column h2:where(:not(.vc_custom_heading)) {
	font-family: var(--kc-font-body);
	font-size: 23px;
	line-height: 1.28;
	font-weight: 600;
	letter-spacing: -.005em;
	color: var(--text);
	margin: var(--kc-space-6) 0 var(--kc-space-3);
}
:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column h3:where(:not(.vc_custom_heading)) {
	font-family: var(--kc-font-body);
	font-size: 18px;
	line-height: 1.35;
	font-weight: 600;
	color: var(--text);
	margin: var(--kc-space-5) 0 var(--kc-space-2);
}
:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column h4:where(:not(.vc_custom_heading)),
:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column h5:where(:not(.vc_custom_heading)),
:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column h6:where(:not(.vc_custom_heading)) {
	font-family: var(--kc-font-body);
	font-size: 15.5px;
	line-height: 1.4;
	font-weight: 600;
	letter-spacing: .01em;
	color: var(--text);
	margin: var(--kc-space-5) 0 var(--kc-space-2);
}
/* A heading that opens a block should not push a gap the editor never typed. */
:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column > .wpb_wrapper > *:first-child {
	margin-top: 0;
}
/* The rules the editor pastes between sections are structure, not decoration. */
:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column hr {
	border: 0;
	border-top: 1px solid var(--border);
	margin: var(--kc-space-6) 0;
}

@media (max-width: 768px) {
	:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column h1:where(:not(.vc_custom_heading)) { font-size: 25px; }
	:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column h2:where(:not(.vc_custom_heading)) { font-size: 20px; }
	:where(.nm-page-wrap-inner > .nm-row-full .type-page) .wpb_text_column h3:where(:not(.vc_custom_heading)) { font-size: 17px; }
}

/* --------------------------------------------------------------------------
   88.2 The legacy "label column + body column" row.
   --------------------------------------------------------------------------
   A WPBakery pattern the editor reaches for whenever a page wants a section
   label beside its copy: a narrow `col-sm-*` column pushed by
   `col-sm-offset-*`, then the body column. Savoy floats both, so on a 1280
   viewport /about/ spends 122px on dead space, 320px on a column holding the
   two words "The Brand", and starts its body at x=442 — ~34% of the page is
   empty while the copy runs as one undifferentiated block.

   Keyed to the PATTERN (an offset column and the column after it), not to
   /about/: any page built this way, now or later, gets the same treatment —
   the label becomes an eyebrow over its section and the copy gets a readable
   measure instead of a leftover column. DOM and copy are untouched.
   -------------------------------------------------------------------------- */
.type-page .vc_row > .nm_column[class*="col-sm-offset-"],
.type-page .vc_row > .nm_column[class*="col-sm-offset-"] + .nm_column {
	float: none;
	width: 100%;
	max-width: none;
	margin-left: 0;
	margin-right: 0;
}
.type-page .vc_row:has(> .nm_column[class*="col-sm-offset-"]) {
	max-width: 860px;
	margin-left: auto;
	margin-right: auto;
}
/* The label column carries one short heading — read it as a section eyebrow,
   not as a second H2 competing with the copy's own headings. */
.type-page .vc_row > .nm_column[class*="col-sm-offset-"] .wpb_text_column h2 {
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	font-weight: 600;
	letter-spacing: .14em;
	text-transform: uppercase;
	line-height: 1.2;
	color: var(--text-muted);
	margin: 0 0 var(--kc-space-1);
}
/* The spacer the editor put under the label was sized for a side column. */
.type-page .vc_row > .nm_column[class*="col-sm-offset-"] .vc_empty_space {
	height: var(--kc-space-2);
}

/* --------------------------------------------------------------------------
   88.3 Legacy newsletter shell (see inc-static-fix.php).
   --------------------------------------------------------------------------
   Two of the legacy block's call sites are hard-coded rows in the child
   theme's WooCommerce templates — `[vc_row el_class="subscribe-container"]
   [vc_column][templatera id="15047"][/vc_column][/vc_row]`
   (woocommerce/archive-product.php:335). With the block retired the shortcode
   returns nothing but WPBakery still emits the empty row. Collapse the shell
   only while it IS empty: bring the block back with the kill switch and the
   row has a form again, so this rule stops matching on its own.
   -------------------------------------------------------------------------- */
.subscribe-container:not(:has(form)) {
	display: none;
}

/* ===== 89-mobile-fix.css ===== */
/* ==========================================================================
   89 — Intrinsic sizing contracts (rootfix: mobile-layout, 2026-08-28)
   ==========================================================================
   WHY THIS FILE EXISTS
   The five phone-layout defects in FINAL_UX_AUDIT_2026-08-27 (M12/M64 chips,
   M22 city column, M63 title clip, M52/M66 WhatsApp bubble, M23 anchors) are
   five different mechanisms with ONE shared meta-cause: every size and every
   offset was written as a literal, per surface, instead of being derived from
   a declared contract. So each new category / flavour / dock / page had to
   re-discover the same bug.

   This file declares the contracts. Every rule below is either (a) a token
   definition — the ONE place a number lives — or (b) a consumer that reads a
   token and never a literal. Nothing here is keyed to a specific product,
   category, city or post: a new SKU, a 6th flavour, a 51st city page or a
   brand-new bottom dock is covered the day it ships, with no new CSS.

   Loads last of the numbered partials that touch these surfaces (89 > 87 >
   26 > 25), so it wins ties on source order and needs no !important — the
   two exceptions are marked and each REPLACES an existing !important that is
   listed for deletion in docs/redesign/rootfix-mobile-layout.md.
   ========================================================================== */


/* --------------------------------------------------------------------------
   89.0  THE CONTRACTS
   --------------------------------------------------------------------------
   Declared on body.kc-rd (not :root — scripts/build_kc_css.py --check
   reserves :root for kc-redesign.css). Every element that needs one of these
   numbers is a descendant of body, so inheritance delivers it, and a surface
   that legitimately differs re-declares the token on its own body class
   instead of writing a second rule (see 89.4).
   -------------------------------------------------------------------------- */
body.kc-rd {
	/* — Card title slot (89.3) —
	   lines × leading defines the slot; nothing else may state its height.
	   trim: a clamped line's ink box is TALLER than its line box (Poppins'
	   normal line box is 1.667em), so a cut placed exactly at N×line-height
	   lands INSIDE the next line's glyphs. Measured on staging with canvas
	   metrics, Poppins 600: fontAscent 1.0667em, fontDescent 0.600em,
	   worst-case glyph ascent A 0.9573em (accented caps), descent D 0.2727em.
	   The safe cut is the middle of the dead band between line N's descender
	   and line N+1's ascender:
	       trim = (A − D)/2 − (fontAscent − fontDescent)/2 = 0.109em
	   and the band is (line-height − (A + D)) wide, i.e. the leading must
	   exceed 1.23em for ANY cut to be safe. 1.2 (the value this file
	   replaces) is below that floor — which is why no clip position worked.
	   1.4 leaves ±1.27px of margin at 15px and keeps the desktop slot within
	   0.2px of what it is today. */
	--kc-card-title-lines: 2;
	--kc-card-title-lh: 1.4;
	--kc-card-title-trim: 0.109em;
	--kc-card-title-slot: calc(
		var(--kc-card-title-lines) * var(--kc-card-title-lh) * 1em
		- var(--kc-card-title-trim)
	);

	/* — Floating third-party chrome (89.4) —
	   fab-inset: the plugin's own inline bottom offset.
	   bottom-dock-h: how much of the bottom edge OUR fixed furniture already
	   occupies on this page (its height + its own inset). 0 by default; a
	   surface with a dock re-declares it and the bubble moves itself.
	   fab-z / z-control: the hit-order contract — our controls always paint
	   and hit-test above any third-party overlay. */
	--kc-fab-inset: 10px;
	--kc-bottom-dock-h: 0px;
	--kc-fab-z: 3;
	--kc-z-control: 4;
}


/* --------------------------------------------------------------------------
   89.1  CHIPS ARE SIZED BY THEIR CONTENT, NOT BY "CARD WIDTH ÷ N"   (M12/M64)
   --------------------------------------------------------------------------
   kc-redesign.css:2036 gives every .kc-tier `flex: 1 1 0; min-width: 0`, so a
   chip is exactly (card − gaps)/N no matter what is written inside it, and
   .kc-tiers (:2021) has no flex-wrap to escape to. Two symptoms from one
   mechanism: on desktop the label/price overflows a visible border (measured
   /product-category/concentrate/ "Pot of Gold Hash": $200.00 needs 13.0px
   more than its chip's content box, and $125.00 9.7px, so the two read as
   "$125.00$200.00"); on phone kc-redesign.css:3538 papers over the same
   division with a smaller font + overflow:hidden and no ellipsis, so a long
   label is sliced mid-letter. Adding a 5th or 6th option makes every chip
   narrower again — the defect is a function of N, so it always comes back.

   The contract: a chip is never narrower than the narrowest layout its own
   content can take. That is `min-content`, and the choice is deliberate:
     · the price (.kc-tier-p, white-space:nowrap) has min-content ==
       max-content, so "$200.00" always gets its full 49.4px — the desktop
       defect is fixed;
     · a long flavour label can wrap inside its chip, so its min-content is
       one word, not the whole phrase — which keeps a 27-character strain
       name from pushing the chip past the card edge. (max-content was tried
       first and did exactly that, measured +10.9px past the card on a
       synthetic 6-flavour row; min-width beats max-width in the cascade so
       `max-width: 100%` cannot rescue it, and `min(max-content, 100%)` is
       invalid CSS — max-content is a keyword, not a <length-percentage> —
       so the whole declaration is dropped and the bug returns silently.)
   `flex-wrap: wrap` gives the row somewhere to go when the minimums no longer
   fit on one line. flex-basis stays 0, so as long as everything fits the
   chips are still divided equally and the rendering is byte-identical to
   today (verified: the 4 gram chips on /product-category/weed/aaa/ and
   /concentrate/ at 375px do not move by a pixel).
   CORRECTION 2026-08-28 (independent verification): the ellipsis pair below
   is NOT a last resort for an unbreakable token wider than the card — it can
   never fire in that case. min-width beats max-width in the cascade, so a
   24-character token with no break opportunity (adversarial label
   "SUPERSKUNKXLBULKPACK2026") makes the chip's min-content that wide and the
   chip leaves the card by 21.8px at 320px; .nm-page-wrap{overflow:hidden}
   clips it, so there is still no horizontal scroll. That is deliberately left
   as-is: the only CSS that would shrink that min-content is
   `overflow-wrap: anywhere` on the label, and it shrinks min-content for
   EVERY label, which re-opens the division defect this section exists to
   close (measured: 3 weight chips at 320px fall back from 57/57/118 to
   36.7 each and "QUARTER OZ" breaks mid-word). The pair still earns its keep
   for the ordinary case — a label that wraps and overruns its chip's height —
   and the pathological case is strictly better than before this file
   (which sliced the label mid-letter AND overlapped the neighbour's ink by
   117px). See the report's R1 entry.
   -------------------------------------------------------------------------- */
body.kc-rd .kc-tiers {
	flex-wrap: wrap;
}
body.kc-rd .kc-tier {
	min-width: min-content;
	max-width: 100%;
}
body.kc-rd .kc-tier .kc-tier-w,
body.kc-rd .kc-tier .kc-tier-p {
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
}


/* --------------------------------------------------------------------------
   89.2  A ROW IS NEVER WIDER THAN ITS LINE                              (M22)
   --------------------------------------------------------------------------
   savoy-child/assets/css/main.css:5629 lays .description-block out as
   `flex-direction: row; flex-wrap: wrap; align-items: center`, and
   savoy-child/style.css:4098 flips only the direction on phone
   (`column-reverse`). Flipping the main axis turns the columns' WIDTH into a
   cross-axis size — where `align-items: center` makes it fit-content and
   `flex-wrap: wrap` lets a second line sit beside the first. Nothing is left
   to hold a column to 375px, and `min-width: 0` cannot help because it only
   governs the automatic minimum on the MAIN axis (verified on staging: the
   column stayed 1338px with computed min-width 0px).

   max-width is the one declaration that converges a cross-axis size, so it is
   the authority. Keyed on the .description-block row itself — not on
   body.kc-city-classic, which inc-city.php only adds when post_content
   happens to contain "city-description-block" — so all 51 published pages
   built from this row (every city page, order-weed-online-*, the mushroom
   city pages) are covered, including any future clone. align-items returns to
   stretch so the columns fill the line instead of shrink-wrapping, and
   overflow-wrap keeps an unbreakable string (a long URL, a tracking code)
   from re-inflating the column's min-content size.
   -------------------------------------------------------------------------- */
@media (max-width: 768px) {
	body.kc-rd .description-block {
		align-items: stretch;
	}
	body.kc-rd .description-block > * {
		max-width: 100%;
		min-width: 0;
	}
	body.kc-rd .description-block :is(p, li, td, th, h1, h2, h3, h4, h5, h6, a, span, div) {
		overflow-wrap: anywhere;
	}
}


/* --------------------------------------------------------------------------
   89.3  THE TITLE SLOT IS DERIVED, NOT TYPED                            (M63)
   --------------------------------------------------------------------------
   Three files each wrote their own number for the same slot —
   kc-redesign.css:2014 (min-height 2.7em, leading 1.35), :2326 (36.8px /
   1.15), 25-card-mobile.css:72 (36px / 1.2) — and the phone copy landed below
   the structural floor derived in 89.0, so the third line's ascenders render
   inside the clipped box: on /on-sale-now/ 18 of 20 cards carry a 1.25px
   strip of chopped glyph tops between the ellipsised title and the price
   (pixel-scanned at dsf 4: ink bands at 1.25–12.00, 19.00–30.25 and then
   34.50–35.75 inside a 36px box).

   Here the height comes from the tokens and only from the tokens. min and max
   are the same expression so the slot is exact whether the name is one line
   or six — a 1-line card still reserves it (the CTA baseline the card system
   depends on is unchanged), and a 78-character name still cannot bleed.
   25-card-mobile.css §25.3 keeps the leading declaration (it has to out-shout
   kc-redesign.css:2330's !important) but now reads --kc-card-title-lh instead
   of a literal, so the number still lives in exactly one place.
   -------------------------------------------------------------------------- */
body.kc-rd ul.nm-products li.product .nm-shop-loop-title {
	min-height: var(--kc-card-title-slot);
	max-height: var(--kc-card-title-slot);
	-webkit-line-clamp: var(--kc-card-title-lines);
}
body.kc-rd ul.nm-products li.product .nm-shop-loop-title,
body.kc-rd ul.nm-products li.product .nm-shop-loop-title a,
body.kc-rd ul.nm-products li.product .nm-shop-loop-title .woocommerce-loop-product__title {
	line-height: var(--kc-card-title-lh);
}


/* --------------------------------------------------------------------------
   89.4  FLOATING CHROME: ONE POSITION RULE, ONE HIT-ORDER RULE     (M52/M66)
   --------------------------------------------------------------------------
   The click-to-chat plugin renders `position:fixed; bottom:10px; right:10px;
   z-index:99999999` inline. Every bottom dock this redesign has added since
   then answered it with its own literal lift — kc-redesign.css:365 (92px),
   :2241 (78px), 26-cat-final.css:262 (78px) — three rules, two of which
   contradict each other, none of which fires on /delivery-area/ or on a page
   whose primary control simply happens to sit low in the first fold. Measured
   at 375px, scrollY 0: the bubble steals a 41×33 patch of the PDP's Add to
   cart (/?p=28166) and a 41×40 patch of the /delivery-area/ "Check" button —
   document.elementFromPoint returns DIV.ctc-analytics for all of them — and a
   further 38×24 of the newsletter "Sign me up" further down that page.

   Two contracts replace all three literals:
   (1) POSITION — the bubble reads --kc-bottom-dock-h. A surface that adds
       fixed furniture at the bottom declares that token on its own body class
       (below) and the bubble moves itself. No new #ht-ctc-chat rule, ever.
   (2) HIT ORDER — our controls are raised to --kc-z-control and the bubble
       lowered to --kc-fab-z. This is the only guarantee that is independent
       of page, position and scroll offset: a third-party overlay can no
       longer take a tap meant for a control anywhere on the site, today or
       after any future template change.
   The !important below is not a new layer: it is what lets ONE rule retire
   the three above (all of which are !important because they fight an inline
   style). They are listed for deletion in the report; after that lands this
   drops to a plain declaration.
   -------------------------------------------------------------------------- */
body.kc-rd.kc-atc-shown { --kc-bottom-dock-h: 70px; } /* .kc-sticky-atc, measured h 70 at bottom 0 */
body.kc-rd.kc-dock-on   { --kc-bottom-dock-h: 66px; } /* .kc-dock, measured h 56 at bottom 10 */

body.kc-rd #ht-ctc-chat.ht-ctc {
	bottom: calc(var(--kc-fab-inset) + var(--kc-bottom-dock-h)) !important;
	z-index: var(--kc-fab-z) !important;
	transition: bottom .2s ease;
}
body.kc-rd .kc-cta,
body.kc-rd .kc-tier,
body.kc-rd .kc-sticky-btn,
body.kc-rd .single_add_to_cart_button,
body.kc-rd button[type="submit"],
body.kc-rd input[type="submit"],
body.kc-rd a.button,
body.kc-rd .button[href] {
	position: relative;
	z-index: var(--kc-z-control);
}

/* A raised control only wins if it is IN the same stacking context as the
   bubble. Anything with a transform between them traps it: slick writes
   `transform: matrix(...)` on .slick-track, which makes a stacking context at
   level 0, so every control inside a carousel painted BELOW a z-index:3
   bubble no matter what z-index the control itself asked for. Measured on
   /weed-delivery-kleinburg/ at 320px, scrollY 1200: the rail's "Add to cart"
   still lost a 28x8 patch after 89.4 until the track itself joined the
   control layer. The trap, not the control, is what has to be lifted.
   To extend this list, find the trap the same way: walk the control's
   ancestors and take the first with transform/filter/opacity<1/will-change or
   position+z-index — that element goes here. */
body.kc-rd .slick-track {
	position: relative;
	z-index: var(--kc-z-control);
}


/* --------------------------------------------------------------------------
   89.5  ONE ANCHOR OFFSET, READ FROM THE MEASURED CHROME                (M23)
   --------------------------------------------------------------------------
   kc-redesign.js:351 already measures the real marquee + header and publishes
   it as --kc-chrome-h on <html> (103px phone / 110px desktop, verified). The
   single truth existed; nothing was ever wired to the scroll. So every
   fragment jump landed at exactly y=0 — measured on
   /aaaa-vs-aaa-weed-whats-the-difference/, 17 TOC entries, both viewports:
   target.getBoundingClientRect().top = 0.0 every time, i.e. fully behind the
   chrome. The same 0 applied to the 14 FAQ anchors on every city page.

   scroll-padding-top on the scroll container is the one declaration that
   covers native fragment navigation, scrollIntoView() and focus scrolling at
   once, for every anchor that exists now or is written later. A new blog post
   is correct the day it is published and its author does nothing.

   KNOWN INTERACTION, and how it is settled here: scroll-padding-top on the
   scrollport ADDS to any scroll-margin-top on the target, and four rules still
   restate the whole chrome on their own targets. Measured: a Woo notice on
   /cart/, /checkout/ and /my-account/ landed at y=119 before and y=238 after —
   119px too low, on the checkout money path. So each of those targets is put
   back to exactly one declaration:

   · 65-checkout.css:344 and :355 and 80-notices.css:489 state the SAME value
     this rule now states globally, so they are pure duplicates. They are
     zeroed below, with their own selectors, at equal specificity from a later
     file — no !important, no new layer. Those three declarations should be
     DELETED at source (report), after which the three lines below go too.
   · 69-checkout-shell.css:250 is NOT a duplicate: under .kc-co-shell the site
     chrome is replaced by a 56px mini header + (≤1024px) a 48px summary bar
     (69-checkout-shell.css:28/195), so its calc is the only correct offset on
     that surface — and --kc-chrome-h, measured from .nm-header, is 0px there
     (verified with a populated cart) and cannot describe it.

   REGRESSION FIXED 2026-08-28 — read this before touching the block below.
   The first cut of this section zeroed .form-row and the checkout error group
   for the WHOLE of body.woocommerce-checkout. The shell page carries BOTH
   classes (body is "... woocommerce-checkout ... kc-co-shell kc-rd ..."), so
   the tombstone collided with 69-checkout-shell.css:250 at equal specificity
   (0,3,1) and won on source order, while html:has(body.kc-co-shell) had
   already switched the global padding off. Net offset on the shell: ZERO.
   Measured with two real gummies in the cart, scrollIntoView on
   #billing_phone_field: top 0.2px @375 with the chrome ending at 104px —
   103.8px of the field hidden behind the mini header + summary bar; 55.7px
   @1280. Same for the error group (-0.2 / -0.2). That is the money path:
   65-checkout.js focuses the first invalid control after checkout_error and
   69-checkout-shell.js:99 calls scrollIntoView, and both honour these two
   properties. (WC core's own scroll_to_notices animates to a hardcoded 100px
   and is unaffected, which is why an empty-cart check missed it.)

   The block below therefore says the offset TWICE, once per surface, and the
   two never overlap:
     · non-shell checkout / notices → 0, because the global html rule already
       states the same number (these are the 65/80 duplicates listed above);
     · shell → its own furniture, derived, never a literal:
       --kc-co-head-h (56px, always) + --kc-co-sum-h (48px, declared by
       69-checkout-shell.css only inside @media ≤1024, so var()'s 0px fallback
       makes the desktop value fall out of the same expression with no media
       query here) + the same --kc-space-4 seam every other anchor gets.
       Measured after: 120px @375 (= 56 + 48 + 16, chrome ends at 104 → 16px
       of daylight) and 72px @1280 (= 56 + 16). Identical to what 65/69 gave
       before this package existed, and it now covers the notice groups too,
       which 80-notices.css under-served on the shell (--kc-chrome-h 0 → 16px).

   :has() DEPENDENCY (Chrome 105+/Safari 15.4+): an engine that drops the
   html:has() rule keeps the global 16px padding on the shell (--kc-chrome-h
   is 0px there), so the landing point degrades to 136px/88px — 16px lower
   than intended, never behind the chrome. The :has()-free alternative is in
   docs/redesign/rootfix-mobile-layout.md §「回归修正 (2026-08-28)」.
   -------------------------------------------------------------------------- */
html {
	scroll-padding-top: calc(var(--kc-chrome-h, 104px) + var(--kc-space-4, 16px));
}
html:has(body.kc-co-shell) {
	scroll-padding-top: 0;
}
body.kc-rd.woocommerce-checkout:not(.kc-co-shell) form.checkout .woocommerce-NoticeGroup-checkoutErrors,
body.kc-rd.woocommerce-checkout:not(.kc-co-shell) .form-row,
body.kc-rd:not(.kc-co-shell) .woocommerce-NoticeGroup,
body.kc-rd:not(.kc-co-shell) form.checkout > .woocommerce-NoticeGroup-checkout,
body.kc-rd:not(.kc-co-shell) #nm-shop-notices-wrap > .nm-shop-notice,
body.kc-rd:not(.kc-co-shell) .woocommerce-notices-wrapper > .nm-shop-notice {
	scroll-margin-top: 0;
}
body.kc-rd.kc-co-shell .form-row,
body.kc-rd.kc-co-shell form.checkout .woocommerce-NoticeGroup-checkoutErrors,
body.kc-rd.kc-co-shell .woocommerce-NoticeGroup,
body.kc-rd.kc-co-shell form.checkout > .woocommerce-NoticeGroup-checkout {
	scroll-margin-top: calc(
		var(--kc-co-head-h, 56px)
		+ var(--kc-co-sum-h, 0px)
		+ var(--kc-space-4, 16px)
	);
}

/* ===== 90-footer.css ===== */
/* ==========================================================================
   90-footer.css — SURFACE: Footer (sitewide)
   Owner: footer agent. Do not edit from another surface.

   --------------------------------------------------------------------------
   DOM VERIFIED against staging (authenticated fetch of / and /shop/, identical
   markup — the whole footer is two Templatera shortcodes inside
   <footer id="nm-footer" class="nm-footer">):

     ROW 1  .nm-row.nm-row-full.vc_row.wpb_row.fast-weed-delivery-cities-list-title
              └ .wpb_text_column.fast-weed-delivery-cities-list-title > p
                "Fast Weed Delivery To:"          (class is on BOTH row and inner)
     ROW 2  .nm-row…​.fast-weed-delivery-cities-list
              ├ 4 × .nm_column.wpb_column.vc_column_container.col-sm-3.col-xs-6
              │     └ N × .wpb_text_column > .wpb_wrapper > p
              │           > img[destination-cart-icon-white.svg] + a.row-title
              │       (40 city links total)
              └ .nm_column…​.col-sm-12.col-xs-6 > .nm-divider > .nm-divider-line
     ROW 3  .nm-row…​.footer-template-bakery
              ├ .nm_column…​.col-sm-1\/5            BRAND column (no extra class)
              │   .wpb_single_image.footer-bakery-logo > figure > img
              │   .wpb_text_column.footer-bakery-first-column-color   (EMPTY)
              │   .wpb_text_column.footer-bakery-first-column-color2
              │        > p > a[mailto:], p > a[tel:], p "Toronto, Ontario, Canada"
              │   .nm-row.nm-row-full.inner.footer-bakery-first-column-socials (EMPTY)
              │   .wpb_raw_code.wpb_raw_html
              │        > <link cdnjs font-awesome> <style> .kc-footer-social
              │          > 6 × a > i.fa-brands (facebook/instagram/x/youtube/
              │            pinterest/linkedin) — NO accessible name, see report
              ├ 3 × .footer-bakery-column…​.col-sm-1\/5
              │        > .wpb_text_column > .wpb_wrapper
              │            > p > strong > a   (About Us / Customer Service / Shop)
              │            > p > a            (nav links)
              ├ .footer-bakery-column…​.col-sm-2.hidden-lg.hidden-md.hidden-sm.hidden-xs
              │        DEAD column: grid.css hides it at every breakpoint. Left alone.
              └ .footer-bakery-last-column…​.col-sm-1\/5   TRUST column
                   2 × .wpb_single_image > figure > img
                        (Norton Secured badge, Interac e-Transfer badge —
                         both are opaque light plates, verified by decoding the
                         base64 PNG inside each SVG: 99% / 98% opaque pixels,
                         so they stay legible on a dark ground)
                   .nm-row.nm-row-full.inner > .nm_column.nm_column_inner.col-sm-12
                        > .wpb_text_column.footer-delivery-text
                            > div  "SAME-DAY WEED DELIVERY"
                            > p    "We have a 2 hour delivery after your order
                                    confirmation. / Working Hours: 12PM - 11PM /
                                    Free Delivery: 200$+ / Min order: $60"
                                    (one <p>, four <br>-separated lines — cannot
                                     be split without touching markup)

   NOT PRESENT anywhere in the footer, on any page checked: a newsletter block,
   a copyright line, a legal/compliance line, and any 19+ statement. The child
   theme still ships .footer-bakery-copyright styles but no such element exists
   in the DOM. Nothing is invented here — see "needs_lead" in the report.

   --------------------------------------------------------------------------
   COMPETING SELECTORS BEATEN (all read out of the live stylesheets, not guessed).
   Specificity written (id, class, element).

   savoy-child/style.css
     .fast-weed-delivery-cities-list,
     .fast-weed-delivery-cities-list-title       (0,1,0) !important  bg #2b2520
     #nm-footer.nm-footer .nm-row.nm-row-full.vc_row.wpb_row
        .footer-template-bakery                  (1,6,0) !important  bg #2b2520
     .footer-bakery-column a                     (0,1,1)  18px  #FFFFFF
     .footer-bakery-column strong a              (0,1,2)  Gazella 22px
     .footer-bakery-first-column-color2 p        (0,1,1)  18px  #FFFFFF
     .fast-weed-delivery-cities-list p a.row-title           (0,2,2) 18px
     .fast-weed-delivery-cities-list p a.row-title:hover     (0,3,2) filter .75
     #nm-footer .footer-bakery-column.nm_column.wpb_column
        .vc_column_container a:hover             (1,5,1)  filter .75
     .footer-delivery-text p                     (0,1,1)  line-height 1.4
     .footer-delivery-text > div > div           (0,1,2)  700
     .footer-bakery-last-column .wpb_single_image.wpb_content_element
        .vc_align_left                           (0,4,0) !important  mb 10px
     @≤768  #nm-footer.nm-footer                 (1,2,0)  text-align center
     @≤768  #nm-footer.nm-footer .footer-bakery-last-column .wpb_wrapper
                                                 (1,3,0)  display flex row
     @≤768  #nm-footer.nm-footer .footer-bakery-column
              .wpb_text_column.wpb_content_element (1,4,0) padding-top 50px
     @≤768  #nm-footer.nm-footer .nm-row.nm-row-full.inner
              .footer-bakery-first-column-socials  (1,5,0) display flex
     @≤768  .fast-weed-delivery-cities-list      (0,1,0)  display flex
     @≤768  .fast-weed-delivery-cities-list > div (0,1,1) !important  width 49%
     @≤768  .fast-weed-delivery-cities-list > div.nm_column.wpb_column
              .vc_column_container               (0,4,1) !important  mb 0
     .fast-weed-delivery-cities-list-title p     (0,1,1)  22px 600 white

   inline <style> in the page body (raw-html widget — later in document order
   than kc-parts.css, so ties go to it and I must out-specify, not tie):
     .kc-footer-social                           (0,1,0)  gap 18px
     .kc-footer-social a                         (0,1,1)  24px #fff
     .kc-footer-social a:hover                   (0,2,1)  #f8a000
   inline <style> vc_custom_* (all (0,1,0) !important): bg #2b2520 + paddings.

   savoy-child/assets/css/main.css
     *                                           (0,0,0)  outline: 0 none
   savoy/style.css
     a                                           (0,0,1)  outline: 0; #dc9814
     a:hover                                     (0,0,2)  #282828  ← on the dark
        footer this would take every link to near-invisible on hover.
        AUDIT CORRECTION 2026-08-15: this note used to say "Fixed below",
        pointing at the :hover rules. That was the wrong mechanism. Nothing
        here needs a :hover override to stop (0,0,2) — the colour is pinned on
        the *base* rules in §90.2 / §90.4 / §90.5, every one of which carries
        #nm-footer and therefore outranks savoy in all states. What the :hover
        rules actually add is the affordance: an underline, plus clearing the
        child theme's filter:brightness(.75) on the two selectors that have it.
        The real gap the old note hid: the mailto/tel links had the pinned
        colour but no affordance at all, because .footer-bakery-first-column-
        color2 is not .footer-bakery-column and so never matched the §90.5
        hover. §90.4 now gives them the same underline as their neighbours.
   savoy/assets/css/grid.css
     .nm-row:before / :after                     (0,1,1)  display:table
        → these become phantom grid items once the rows are gridded; killed.
     .col-sm-1\/5 / .col-sm-3 / .col-xs-6        (0,1,0)  float + width

   !important appears 16 times below. Fourteen of them beat a declaration that
   is itself !important — the three row backgrounds, the vc_custom_* row
   paddings, the two logo/badge margins, the child theme's width/margin
   overrides on the city columns, and kc-redesign.css §20.4's font-family on
   the nav headings (§90.5). The remaining pair is the prefers-reduced-motion
   guard in §90.10 (reasoned there). Everything else wins on specificity alone.

   AUDIT CORRECTION 2026-08-15: the sentence above used to read "Every one of
   them", and used to list "width/float/margin" on the city columns. It was not
   true of float. There is no `float … !important` in savoy-child/style.css;
   the only competing float is savoy/assets/css/grid.css `.col-sm-3
   { float: left }` at (0,1,0), which §90.2's selector beats at (1,6,1) with
   nothing special — and float does not apply to that element at all, because
   §90.2 makes its parent display:grid. The !important is gone. Width and
   margin-bottom on the same rule are genuinely fighting !important and keep
   theirs. Do not re-generalise this paragraph: it is per-declaration.

   kc-redesign.css §20.4 puts the footer heading links on the body face with
   !important. §90.5 overrides it rather than matching it: the kit's Label role
   is the data face, and the other two labels on this surface already use it.
   Specificity does not beat !important, so §90.5's font-family carries one too.
   Size / case / tracking are changed there as well.
   ========================================================================== */


/* --------------------------------------------------------------------------
   90.1  Ground
   The footer becomes one continuous ink band — this is the kit's "INK 12"
   share of the screen, and it is the only place on the site large enough to
   carry it. The three rows each painted themselves #2b2520 with !important;
   the paint now moves to #nm-footer so the rows can be content-width without
   leaving seams.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-footer.nm-footer {
	background: var(--text);
	color: var(--surface);
	text-align: left;                     /* beats @≤768 text-align:center (1,2,0) */
}

/* (1,7,1) vs (1,6,0)!important and (0,1,0)!important — !important required
   because the declarations being beaten are themselves !important.

   Horizontal padding is deliberately NOT set here. savoy-child/assets/css/main.css
   has a sitewide `.nm-row { padding-left: 0 !important; padding-right: 0 !important }`
   and the whole site takes its gutter from the 15px on .nm_column instead. I
   measured this the hard way: an inline `padding-left: var(--gutter)` on the row
   still computed to 0px. Rather than out-!important a sitewide rule for 3.75px,
   the footer keeps the theme's gutter so its left edge lines up with the header
   and the product grid above it. */
body.kc-rd #nm-footer.nm-footer .nm-row.nm-row-full.vc_row.wpb_row.footer-template-bakery,
body.kc-rd #nm-footer.nm-footer .nm-row.nm-row-full.vc_row.wpb_row.fast-weed-delivery-cities-list,
body.kc-rd #nm-footer.nm-footer .nm-row.nm-row-full.vc_row.wpb_row.fast-weed-delivery-cities-list-title {
	background-color: var(--text) !important;
	max-width: 1280px;
	margin-inline: auto;
}

/* The cities heading carries the same class as its row, so it inherited the
   row's background and 30px top padding. (1,3,1) vs (0,1,0)!important. */
body.kc-rd #nm-footer .wpb_text_column.fast-weed-delivery-cities-list-title {
	background: transparent !important;
	padding-top: 0 !important;
	margin-bottom: 0;
}

/* Gridding a .nm-row would otherwise promote its clearfix pseudo-elements to
   grid items and leave two empty cells. (1,3,1) vs (0,1,1). */
body.kc-rd #nm-footer .nm-row.fast-weed-delivery-cities-list::before,
body.kc-rd #nm-footer .nm-row.fast-weed-delivery-cities-list::after,
body.kc-rd #nm-footer .nm-row.footer-template-bakery::before,
body.kc-rd #nm-footer .nm-row.footer-template-bakery::after {
	display: none;
}


/* --------------------------------------------------------------------------
   90.2  Delivery-area directory  (40 internal city links — link text and href
         are untouchable; this is layout, size and tap target only)
   -------------------------------------------------------------------------- */

/* "Fast Weed Delivery To:" was 22px/600 white — louder than the links it
   introduces. It is an index label, so it takes the kit's label spec
   (data face, 12px, .12em, caps). (1,3,2) vs (0,1,1). */
body.kc-rd #nm-footer .wpb_text_column.fast-weed-delivery-cities-list-title p {
	margin: 0;
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: .12em;
	text-transform: uppercase;
	line-height: 1.4;
	color: var(--surface);
	opacity: .62;
}

body.kc-rd #nm-footer .nm-row.nm-row-full.vc_row.wpb_row.fast-weed-delivery-cities-list-title {
	padding-top: var(--kc-space-7) !important;   /* beats vc_custom padding-top:40px!important */
	padding-bottom: var(--kc-space-4);
}

/* Four columns, dropping to two on phones. Replaces the float grid and the
   child theme's flex/49% mobile hack in one place. (1,3,1). */
body.kc-rd #nm-footer .nm-row.fast-weed-delivery-cities-list {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	column-gap: 0;                        /* the 15px on each .nm_column is the gutter */
	row-gap: 0;
	align-items: start;
	padding-bottom: var(--kc-space-6) !important;  /* beats vc_custom pb:10px!important */
}

/* (1,6,1) vs the child theme's (0,4,1)!important and (0,1,1)!important —
   which is the reason width and margin-bottom carry !important. float does
   not, and never needed to: nothing declares float !important, grid.css's
   `.col-sm-3 { float: left }` is (0,1,0), and this element is a grid item
   (the rule above), where float has no effect whatsoever. Kept as a plain
   declaration rather than deleted so the rule is still right if the grid
   above is ever unwound. */
body.kc-rd #nm-footer .nm-row.fast-weed-delivery-cities-list > .nm_column.wpb_column.vc_column_container {
	width: auto !important;
	float: none;
	margin-bottom: 0 !important;
}

/* Last child is the divider column, not a city column. */
body.kc-rd #nm-footer .nm-row.fast-weed-delivery-cities-list > .nm_column.wpb_column.vc_column_container:last-child {
	grid-column: 1 / -1;
	margin-top: var(--kc-space-5);
}
body.kc-rd #nm-footer .fast-weed-delivery-cities-list .nm-divider {
	height: 1px;
}
body.kc-rd #nm-footer .fast-weed-delivery-cities-list .nm-divider .nm-divider-line {
	background: var(--surface);
	border: 0;
	opacity: .14;
}

body.kc-rd #nm-footer .fast-weed-delivery-cities-list .wpb_text_column {
	margin-bottom: 0;
}

/* The pin <img> is a sibling of the link, not a child of it, so the row is
   laid out as flex and the link takes the remaining width — that way the
   whole band is the tap target, not just the glyphs.

   The bottom margin is the kit's target *separation*, which is a separate
   requirement from target size: brand-kit.html §06 Touch asks for "at least
   8px between adjacent targets", and its failure list names "two targets
   closer than 8px" outright. Without it 40 full-width bands stack edge to
   edge — visually there is whitespace, but the hit areas touch, and a
   near-miss opens a different city's landing page. These are 40 distinct
   local landing pages; a dense directory is exactly the case the rule is
   written for, not an exception to it.
   Expressed as a margin, not a row-gap, so it holds whether the builder emits
   one <p> per .wpb_text_column or several inside one: in the first shape the
   margin collapses through the wrapper and still resolves to 8px.
   Cost, measured at 375px: the directory grows ~240px. That is the honest
   price of the rule, and the footer is the one surface that can pay it. */
body.kc-rd #nm-footer .fast-weed-delivery-cities-list .wpb_wrapper > p {
	display: flex;
	align-items: center;
	gap: var(--kc-space-2);
	margin: 0 0 var(--kc-space-2);
}
body.kc-rd #nm-footer .fast-weed-delivery-cities-list .wpb_wrapper > p > img {
	flex: 0 0 auto;
	width: 11px;
	height: auto;                          /* the markup forces 12×12 onto a 12×16
	                                          viewBox; height:auto un-squashes it */
	opacity: .5;
}

/* (1,3,3) vs (0,2,2) — beats both the base 18px rule and its ≤768 variant.
   min-height is the layer-3 token, not a literal: 48 is the kit's floor and
   the house rule everywhere else in this build (60-cart-page.css §"48 is the
   kit's floor", ~59 call sites across the partials). The one sibling that
   rounds to 44, 40-nav-menu.css:275, states its own reason — that panel has
   no touch path, it is a desktop hover menu. This one is 40 city links on a
   store whose traffic is 86.5% mobile, so it takes the floor. */
body.kc-rd #nm-footer .fast-weed-delivery-cities-list p a.row-title {
	display: flex;
	flex: 1 1 auto;
	align-items: center;
	min-height: var(--btn-min-height);
	margin: 0;
	padding: var(--kc-space-1) 0;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 400;
	line-height: 1.35;
	color: var(--surface);
	text-decoration: none;
}
/* (1,4,3) vs (0,3,2). brightness(.75) dimmed white toward the ground, i.e.
   hover made the link harder to read; an underline is the honest affordance. */
body.kc-rd #nm-footer .fast-weed-delivery-cities-list p a.row-title:hover {
	filter: none;
	color: var(--surface);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 4px;
}


/* --------------------------------------------------------------------------
   90.3  Main footer row — five real columns (the sixth is display:none at
         every breakpoint via grid.css .hidden-*, so it takes no grid cell)
   -------------------------------------------------------------------------- */

body.kc-rd #nm-footer .nm-row.footer-template-bakery {
	display: grid;
	grid-template-columns: minmax(0, 1.25fr) repeat(3, minmax(0, 1fr)) minmax(0, 1.5fr);
	column-gap: var(--kc-space-2);         /* + the 15px each column already carries */
	row-gap: var(--kc-space-7);
	align-items: start;
	padding-top: var(--kc-space-7) !important;   /* beats vc_custom pt:30px!important */
	padding-bottom: var(--kc-space-7);
}

body.kc-rd #nm-footer .nm-row.footer-template-bakery > .nm_column.wpb_column.vc_column_container {
	width: auto;
	float: none;
}


/* --------------------------------------------------------------------------
   90.4  Brand column — logo, contact, social
   -------------------------------------------------------------------------- */

/* (1,5,1) vs the ≤768 centring rule (1,4,0). */
body.kc-rd #nm-footer.nm-footer .wpb_single_image.wpb_content_element.footer-bakery-logo {
	text-align: left;
	margin-bottom: var(--kc-space-5) !important;
}
body.kc-rd #nm-footer .footer-bakery-logo img {
	width: 210px;
	max-width: 100%;
	height: auto;
}

/* Empty containers the page builder left behind: an empty text column and an
   empty inner row that still reserved 25px and 20px of margin. */
body.kc-rd #nm-footer .wpb_text_column.footer-bakery-first-column-color,
body.kc-rd #nm-footer.nm-footer .nm-row.nm-row-full.inner.footer-bakery-first-column-socials {
	display: none;
}

body.kc-rd #nm-footer .footer-bakery-first-column-color2 {
	margin-bottom: 0;
}
body.kc-rd #nm-footer .footer-bakery-first-column-color2 .wpb_wrapper {
	display: grid;
	justify-items: start;
	row-gap: var(--kc-space-2);            /* target separation — see §90.2 */
}
/* (1,3,2) vs (0,1,1). */
body.kc-rd #nm-footer .footer-bakery-first-column-color2 p {
	margin: 0;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 400;
	line-height: 1.5;
	color: var(--surface);
}
/* Address is context, not a contact route — the only muted line here. */
body.kc-rd #nm-footer .footer-bakery-first-column-color2 p:last-child {
	opacity: .62;
	padding-top: var(--kc-space-2);
}
body.kc-rd #nm-footer .footer-bakery-first-column-color2 a {
	display: inline-flex;
	align-items: center;
	min-height: var(--btn-min-height);
	color: var(--surface);
	text-decoration: none;
}
/* These two are the only links on the surface that reach a human, and they
   were also the only ones with no hover affordance: the brand column carries
   no .footer-bakery-column class (verified in the DOM map above — it is a bare
   .nm_column.wpb_column.vc_column_container.col-sm-1\/5), so §90.5's hover
   never matched them. Same treatment as the city and nav links. */
body.kc-rd #nm-footer .footer-bakery-first-column-color2 a:hover {
	color: var(--surface);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 4px;
}
/* Phone number is a code, so it takes the data face per the kit's type roles. */
body.kc-rd #nm-footer .footer-bakery-first-column-color2 a[href^="tel:"] {
	font-family: var(--price-font);
	font-size: 16px;
	letter-spacing: .01em;
	font-variant-numeric: tabular-nums;
}

/* Social row. The inline <style> beside this markup sets 24px glyphs with no
   box at all — the tap target was the glyph itself, roughly 24×24.
   (1,3,1)/(1,3,2) beat the inline (0,1,0)/(0,1,1)/(0,2,1); ties would go to
   the inline block because it sits later in the document. */
body.kc-rd #nm-footer .kc-footer-social {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-start;
	gap: var(--kc-space-2);
	margin-top: var(--kc-space-5);
}
/* Six 48px targets need 328px. Measured: the brand column gives 238px of
   content at 1280 and 216px at 768, so the row wraps — left to itself it
   wrapped 4 + 2, which reads as a mistake. Capping the row at exactly three
   targets makes it 3 + 3 at every width, including a 320px phone where six
   would also have wrapped 5 + 1. One rule, no breakpoint to get wrong. */
body.kc-rd #nm-footer .kc-footer-social {
	max-width: calc(var(--btn-min-height) * 3 + var(--kc-space-2) * 2);
}
body.kc-rd #nm-footer .kc-footer-social a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--btn-min-height);
	height: var(--btn-min-height);
	font-size: 18px;
	line-height: 1;
	color: var(--surface);
	text-decoration: none;
	border: 1px solid var(--surface);
	border-radius: var(--kc-radius-md);
	opacity: .58;
	transition: opacity .15s ease;
}
body.kc-rd #nm-footer .kc-footer-social a:hover,
body.kc-rd #nm-footer .kc-footer-social a:focus-visible {
	color: var(--surface);
	opacity: 1;
}


/* --------------------------------------------------------------------------
   90.5  Nav columns — About Us / Customer Service / Shop
         Link text and hrefs untouched; §20.4 already owns the face.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-footer.nm-footer .footer-bakery-column .wpb_text_column.wpb_content_element {
	padding-top: 0;                        /* beats the ≤768 padding-top:50px (1,4,0) */
	margin-bottom: 0;
}
body.kc-rd #nm-footer .footer-bakery-column .wpb_wrapper > p {
	margin: 0 0 var(--kc-space-2);         /* target separation — see §90.2 */
}

/* Heading. §20.4 dropped it from Gazella 22px to the body face at 15px, which
   left it the same size as the links underneath it — no hierarchy at all.
   It becomes the kit's label instead: 12px, caps, tracked, and on the data
   face, which is what the kit's Label row specifies ("12 · .10em caps | Data")
   and what both other labels on this surface already use — §90.2's cities
   heading and §90.6's panel label, each on var(--label-font).
   AUDIT CORRECTION 2026-08-15: this used to declare var(--kc-font-body) and
   explain that §20.4's !important "stands". The face was the one thing the
   rule most needed to change, and leaving three label-spec elements in two
   faces on one surface was the result. Specificity cannot beat !important —
   (1,3,3) vs (0,2,2) wins the size, case and tracking but not the family — so
   the family carries !important here. That is the only way to land it, and it
   is accounted for in the header's !important tally. */
body.kc-rd #nm-footer .footer-bakery-column strong a {
	display: flex;
	align-items: center;
	min-height: var(--btn-min-height);
	font-family: var(--label-font) !important;
	font-size: 12px;
	font-weight: 600;
	letter-spacing: .12em;
	text-transform: uppercase;
	line-height: 1.3;
	color: var(--surface);
	text-decoration: none;
}
body.kc-rd #nm-footer .footer-bakery-column .wpb_wrapper > p > strong {
	display: block;
}
body.kc-rd #nm-footer .footer-bakery-column .wpb_wrapper > p > strong::after {
	content: "";
	display: block;
	height: 1px;
	margin: 0 0 var(--kc-space-2);
	background: var(--surface);
	opacity: .16;
}

/* (1,3,2) vs (0,1,1). Full-contrast paper: these are the routes to Track your
   Order, My Account and the category pages — nothing here is decorative. */
body.kc-rd #nm-footer .footer-bakery-column a {
	display: flex;
	align-items: center;
	min-height: var(--btn-min-height);
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 400;
	line-height: 1.35;
	color: var(--surface);
	text-decoration: none;
}

/* (1,7,2) vs (1,5,1) — the child theme's hover both dimmed the link with
   filter:brightness(.75) and re-asserted white; the filter has to be cleared
   explicitly or it keeps applying. */
body.kc-rd #nm-footer.nm-footer .footer-bakery-column.nm_column.wpb_column.vc_column_container a:hover {
	filter: none;
	color: var(--surface);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 4px;
}


/* --------------------------------------------------------------------------
   90.6  Trust column — payment/security badges + the delivery terms
         This is the reason a delivery footer exists. The hours, the minimum
         and the free-delivery threshold were 14px white body copy wedged
         under two badges in the narrowest column on the page; they are now a
         pine panel, which is exactly what --trust is for in the kit
         ("delivery promises"), set at the kit's body size so the threshold
         and the minimum read at full weight.
         No wording is added, removed or reordered.
   -------------------------------------------------------------------------- */

/* The child theme's ≤768 rule targets `.footer-bakery-last-column .wpb_wrapper`
   — every wrapper in the column, not just the outer one — so it was also
   centre-flexing the wrapper inside .footer-delivery-text and shrink-wrapping
   the "SAME-DAY WEED DELIVERY" label to the middle of the panel. Measured at
   375px before this rule: the label sat at x=92 in a 375px-wide panel.
   (1,3,1) vs (1,3,0) — wins on the element in `body`. */
body.kc-rd #nm-footer .footer-bakery-last-column .wpb_wrapper {
	display: block;
}

/* (1,4,1) vs the ≤768 flex rule (1,3,0). Badges sit side by side, the panel
   spans the full column beneath them.
   Flex rather than grid on purpose: with a two-track grid the full-width panel
   below still contributes to track sizing, which stretched the two badges
   140px apart at 1280. flex-wrap + `flex: 1 0 100%` on the panel keeps the
   badges at their intrinsic width and pushes the panel onto its own line.
   flex-direction is stated explicitly because the child theme sets `column`
   at ≤767 and only overriding `display` would leave that in force. */
body.kc-rd #nm-footer .footer-bakery-last-column > .vc_column-inner > .wpb_wrapper {
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
	justify-content: flex-start;
	align-items: center;
	gap: var(--kc-space-3);
}
/* (1,4,1) vs (0,4,0)!important and the ≤768 padding rule (1,4,0). */
body.kc-rd #nm-footer .footer-bakery-last-column .wpb_single_image.wpb_content_element {
	margin: 0 !important;
	padding-right: 0;
}
body.kc-rd #nm-footer .footer-bakery-last-column .wpb_single_image img {
	height: 40px;
	width: auto;
	max-width: 100%;
	border-radius: var(--kc-radius-sm);
}
body.kc-rd #nm-footer .footer-bakery-last-column > .vc_column-inner > .wpb_wrapper > .nm-row {
	flex: 1 0 100%;
	max-width: none;
	margin: var(--kc-space-2) 0 0;
	padding: 0;
}
body.kc-rd #nm-footer .footer-bakery-last-column .nm-row .nm_column.nm_column_inner {
	width: auto;
	float: none;
	padding-left: 0;
	padding-right: 0;
}

/* The hairline is load-bearing, not trim. --trust on --text computes to
   2.15:1 (sRGB relative luminance: pine .0808, ink .0109) — the fill alone
   barely separates the panel from the ground it sits on, which is the same
   measurement §90.8 uses to reject a pine focus ring. Arguing that pine is
   invisible on ink and then leaning on pine-on-ink for the panel's weight was
   the file contradicting itself; the border settles it in favour of the
   measurement. Cream on ink is 16.7:1, so the edge reads at any brightness.
   It is --on-trust, the colour already used by the type inside, so the panel
   stays one object and no second accent enters the surface. */
body.kc-rd #nm-footer .wpb_text_column.footer-delivery-text {
	margin: 0;
	padding: var(--kc-space-4);
	background: var(--trust);
	color: var(--on-trust);
	border: 1px solid var(--on-trust);
	border-radius: var(--kc-radius-md);
}
/* "SAME-DAY WEED DELIVERY" — already uppercase in the markup, so this is the
   label spec applied to text that is unchanged. (1,3,2) vs (0,1,2). */
body.kc-rd #nm-footer .footer-delivery-text > .wpb_wrapper > div {
	margin: 0 0 var(--kc-space-3);
	font-family: var(--label-font);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: .12em;
	line-height: 1.4;
	color: var(--on-trust);
}
/* Four <br>-separated lines in a single <p>: one sentence and three pieces of
   logistics data. They cannot be styled apart without editing markup, and the
   markup is not this file's to edit, so all four take one register.

   AUDIT CORRECTION 2026-08-15. That register used to be the data face at
   13.5px, dropping to 14px on phones. Both numbers were wrong:
     · 13.5 is not on the kit's scale at all. brand-kit.html §05 gives Body
       15–16/1.7 desktop and 15/1.65 mobile, Metric 12/1.4, Label 12. There is
       no 13.5 and no 14, so this was neither body nor metric.
     · These four lines carry the free-delivery threshold ($200+) and the order
       minimum ($60) — the two numbers a customer reads in order to decide.
       Setting them below the body floor, and smaller than the 15px city links
       directly above, inverted the hierarchy this panel exists to create. The
       section header calls this block "the reason a delivery footer exists";
       it now measures like it.
     · The first line is a sentence, not data, and the kit reserves the data
       face for Price / Metric / Label. Splitting the register would need the
       <br>s to become elements, which is a markup change.
   So: kit Body, 15px, body face, at every width — the tabular-nums gain is
   not worth shipping the delivery minimum under the floor, and it bought
   nothing here anyway since the numbers sit in running text, not a column.
   line-height stays at 2: the <br>s are doing the work of rows, and the kit's
   1.7 runs them together. (1,2,2) vs (0,1,1). */
body.kc-rd #nm-footer .footer-delivery-text p {
	margin: 0;
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 400;
	line-height: 2;
	letter-spacing: 0;
	color: var(--on-trust);
}


/* --------------------------------------------------------------------------
   90.7  Compliance line — HAZARD NOTE, no rules
   -------------------------------------------------------------------------- */
/* WHOEVER ADDS THE COPYRIGHT / LEGAL / 19+ LINE, READ THIS FIRST.

   savoy-child still ships .footer-bakery-copyright styles, and at ≤768 it sets
     .footer-bakery-copyright.nm_column.wpb_column.vc_column_container p
        { opacity: .4 }
   On this ink ground 40% opacity is precisely the "fine print" failure the
   whole surface is built to avoid — and a compliance line is the last thing
   that should ship unreadable. Override it in a rule written against the
   markup you actually add, and check the result at 375px.

   This section used to carry that override pre-emptively, for an element that
   does not exist: nothing in the footer matches .footer-bakery-copyright on
   any page checked (see the DOM map at the top of this file). Removed
   2026-08-15. A rule that has never matched anything cannot be verified, and
   its second selector only reached a <p> inside that exact wrapper shape — so
   any other shape escaped it while the section's presence implied it was
   handled. The hazard is real; the guess about the markup was not worth
   keeping. Write the rule when there is markup to write it against. */


/* --------------------------------------------------------------------------
   90.8  Focus. savoy-child/assets/css/main.css kills every outline on the site
   with `* { outline: 0 none }` and savoy adds `a { outline: 0 }`, so keyboard
   users currently get no footer feedback at all. Any scoped selector beats
   (0,0,0)/(0,0,1).

   Deviation from the kit's state table, deliberate: the ring is --action, not
   --trust. Pine on the ink ground measures 2.15:1 (recomputed 2026-08-15; this
   note previously said "about 1.9:1" — the conclusion is unchanged, the number
   was not) — a pine ring on this surface is invisible, which is worse than no
   rule. Amber on ink is 11.22:1. Amber is still the accent, not a second one,
   and this is a state rather than decoration.
   The same 2.15:1 is why §90.6's pine panel carries a cream hairline: this
   file gets one position on that measurement, and it is that pine does not
   separate itself from ink.
   -------------------------------------------------------------------------- */

body.kc-rd #nm-footer a:focus-visible {
	outline: 3px solid var(--action);
	outline-offset: 2px;
	border-radius: var(--kc-radius-sm);
	text-decoration: none;
}
body.kc-rd #nm-footer .fast-weed-delivery-cities-list p a.row-title:focus-visible,
body.kc-rd #nm-footer.nm-footer .footer-bakery-column.nm_column.wpb_column.vc_column_container a:focus-visible {
	filter: none;
	color: var(--surface);
	outline: 3px solid var(--action);
	outline-offset: 2px;
}

/* Ink ground: the UA tap highlight is translucent black, so it paints nothing
   on the --kc-ink-900 ground. Cream at low alpha restores the press flash on
   the one surface where the default is invisible. Note: touch browsers DO apply :hover on
   touchstart, so the §90.2 underline and the .58→1 opacity also paint — the
   tap highlight is the more reliable feedback here, not the only one. This
   surface defines no :active rule, and nothing in the build declares this
   property on a footer link, so plain (1,1,1) specificity is enough. */
body.kc-rd #nm-footer a {
	-webkit-tap-highlight-color: rgba(255, 251, 242, .22);
}


/* --------------------------------------------------------------------------
   90.8b  Tablet. Five columns inside 753px of content left the nav columns at
   125px and squeezed the delivery panel to 158px wide / 348px tall — measured,
   not guessed. Three columns, trust first, matches the 992px layout closely
   (panel 216px there vs 217px at 992).
   -------------------------------------------------------------------------- */

@media (min-width: 768px) and (max-width: 991px) {
	body.kc-rd #nm-footer .nm-row.footer-template-bakery {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		row-gap: var(--kc-space-6);
	}
	/* Spanning two tracks does two things at once: the delivery lines stop
	   wrapping ("Working Hours: 12PM - 11PM" needs ~200px and a single track
	   only gives 184), and the three nav columns end up alone on row two
	   instead of leaving a ragged empty cell.
	   The ~200px was measured with that line at 13.5px mono; it is now 15px
	   Poppins, which sets to roughly the same width, and the panel also lost
	   2px to its new border. The span-2 track is ~390px, so the headroom
	   absorbs both — but re-measure here before ever narrowing this back to a
	   single track. */
	body.kc-rd #nm-footer .nm-row.footer-template-bakery > .footer-bakery-last-column {
		order: -1;
		grid-column: span 2;
	}
}


/* --------------------------------------------------------------------------
   90.9  Mobile — 375px first. 86.5% of search traffic (GSC 90d).
   The trust column leads: on a phone the delivery terms are what someone is
   scrolling to the bottom to find. This is display order only — grid `order`
   moves nothing in the DOM, no link, heading or URL changes.
   -------------------------------------------------------------------------- */

@media (max-width: 767px) {

	body.kc-rd #nm-footer .nm-row.fast-weed-delivery-cities-list {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		padding-bottom: var(--kc-space-5) !important;
	}

	body.kc-rd #nm-footer .nm-row.nm-row-full.vc_row.wpb_row.fast-weed-delivery-cities-list-title {
		padding-top: var(--kc-space-6) !important;
	}

	body.kc-rd #nm-footer .nm-row.footer-template-bakery {
		grid-template-columns: minmax(0, 1fr);
		row-gap: var(--kc-space-6);
		padding-top: var(--kc-space-6) !important;
		padding-bottom: var(--kc-space-6);
	}

	body.kc-rd #nm-footer .nm-row.footer-template-bakery > .footer-bakery-last-column {
		order: -1;
	}

	body.kc-rd #nm-footer .wpb_text_column.footer-delivery-text {
		padding: var(--kc-space-4) var(--kc-space-4) var(--kc-space-5);
	}
	/* No font-size override here on purpose. It used to drop the delivery
	   terms to 14px, which is under the kit's 15px mobile body size — on the
	   viewport where 86.5% of this store's customers read the minimum and the
	   free-delivery threshold. The kit's mobile body size is the same 15px as
	   desktop, so the base rule already covers this breakpoint. */

	/* 6 × 48px + 5 × 8px = 328px, inside a 375px viewport minus gutters; wraps
	   safely on anything narrower. */
	body.kc-rd #nm-footer .kc-footer-social {
		margin-top: var(--kc-space-4);
	}
}


/* --------------------------------------------------------------------------
   90.10  Motion. The inline social <style> ships a .3s transition and savoy
   transitions every link colour; !important is needed here because the
   universal reset has to outrank a same-specificity author rule inside a
   media query on every descendant.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	body.kc-rd #nm-footer *,
	body.kc-rd #nm-footer *::before,
	body.kc-rd #nm-footer *::after {
		transition: none !important;
		animation: none !important;
	}
}

/* ===== 95-home-seo.css ===== */
/* ==========================================================================
   95-home-seo.css — SURFACE: homepage lower half (the restored production copy)
   Owner: home-seo agent. Do not edit from another surface.
   Markup: includes/kc-redesign/inc-home-seo.php

   Everything lives under `body.kc-rd.home .kc-hs`. The wrapper also carries .kc-home
   so it inherits the block's edge bleed and eyebrow styling from §29 of
   kc-redesign.css instead of restating them here.

   Tokens only — no raw brand hex. The two maps (real-geography GTA + Canada)
   are styled entirely from here — fills/strokes/gradient stops are classes,
   not attributes — which is why the same markup sits on cream and on pine.
   ========================================================================== */


/* --------------------------------------------------------------------------
   95.1 Shared shell
   -------------------------------------------------------------------------- */
body.kc-rd.home .kc-hs-in {
	max-width: 1160px;
	margin: 0 auto;
	padding: 0 40px;
}
body.kc-rd.home .kc-hs-sec { padding: 52px 0; }
body.kc-rd.home .kc-hs-sec + .kc-hs-sec { border-top: 1px solid var(--border); }

body.kc-rd.home .kc-hs h2 {
	font-family: var(--kc-font-display);
	font-size: 34px;
	line-height: 1.08;
	color: var(--text);
	margin: 0;
}
body.kc-rd.home .kc-hs-head { margin-bottom: 26px; }
body.kc-rd.home .kc-hs-lede {
	font-size: 15px;
	line-height: 1.6;
	color: var(--text-muted);
	margin: 14px 0 0;
	max-width: 46ch;
}
body.kc-rd.home .kc-hs-fine {
	font-size: 12px;
	line-height: 1.55;
	/* --text-faint measures 2.85:1 on cream: too low for a line that carries a
	   delivery caveat. --text-muted is 6.2:1 and still reads as fine print. */
	color: var(--text-muted);
	margin: 16px 0 0;
	max-width: 46ch;
}

/* two-column: copy beside artwork */
body.kc-rd.home .kc-hs-split {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
	gap: 48px;
	align-items: center;
}
body.kc-rd.home .kc-hs-split-rev .kc-hs-copy { order: 1; }
body.kc-rd.home .kc-hs-split-rev .kc-hs-lanefig { order: 2; }

/* --------------------------------------------------------------------------
   95.2 How to Order — four steps
   -------------------------------------------------------------------------- */
body.kc-rd.home .kc-hs-steps {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: 14px;
	list-style: none;
	margin: 0;
	padding: 0;
	counter-reset: none;
}
body.kc-rd.home .kc-hs-step {
	display: flex;
	flex-direction: column;
	background: var(--surface-raised);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
	padding: 22px 20px 24px;
	margin: 0;
	list-style: none;
}
body.kc-rd.home .kc-hs-step::marker { content: none; }
body.kc-rd.home .kc-hs-num {
	font-family: var(--kc-font-body);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
	font-size: 15px;
	letter-spacing: .06em;
	color: var(--trust);
	display: block;
	margin-bottom: 12px;
	padding-bottom: 10px;
	border-bottom: 2px solid var(--action);
	width: 2.6em;
}
body.kc-rd.home .kc-hs-step b {
	display: block;
	font-size: 14.5px;
	font-weight: 700;
	line-height: 1.3;
	color: var(--text);
	margin-bottom: 7px;
}
body.kc-rd.home .kc-hs-step p {
	font-size: 13px;
	line-height: 1.62;
	color: var(--text-muted);
	margin: 0;
}
/* the shared .kc-hbtn is 40px tall (12px padding + 14px/1.15 text); inside a
   content block it is a primary tap target, so it goes to the 44px floor.
   The hero's buttons have the same 40px and live in kc-redesign.css — see report. */
body.kc-rd.home .kc-hs-go { margin-top: 18px; align-self: flex-start; min-height: 44px; }

body.kc-rd.home .kc-hs-note {
	margin: 22px 0 0;
	font-size: 12.5px;
	line-height: 1.6;
	color: var(--text-muted);
	font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   95.3 Rate strip — production's ink-framed three-cell block
   -------------------------------------------------------------------------- */
body.kc-rd.home .kc-hs-rates {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	margin-top: 22px;
	border: 1.5px solid var(--text);
	border-radius: var(--kc-radius-md);
	box-shadow: 3px 3px 0 var(--text);
	background: var(--surface-raised);
	overflow: hidden;
}
body.kc-rd.home .kc-hs-rate { padding: 15px 14px; }
body.kc-rd.home .kc-hs-rate + .kc-hs-rate { border-left: 1px solid var(--border); }
body.kc-rd.home .kc-hs-rate i {
	display: block;
	font-style: normal;
	font-size: 11.5px;
	line-height: 1.35;
	color: var(--text-muted);
	margin-bottom: 5px;
}
body.kc-rd.home .kc-hs-rate b {
	display: block;
	font-size: 17px;
	font-weight: 700;
	line-height: 1.3;
	color: var(--text);
	font-variant-numeric: tabular-nums;
}
body.kc-rd.home .kc-hs-rate b span { display: block; font-size: 13px; }

/* "Calculated at checkout" (mail block, owner 2026-08-23): the one rate value
   that is words, not a price. The generic `b span` 13px above is sized as a
   price's sub-line; a whole value at 13px reads like a footnote, and 17px bold
   prose overfills the cell. 14px splits it — still clearly the value slot. */
body.kc-rd.home .kc-hs-rate b .kc-hs-rate-txt { font-size: 14px; letter-spacing: .01em; }

/* on pine the strip inverts: cream card would glare, so it goes translucent */
body.kc-rd.home .kc-hs-mail .kc-hs-rates {
	background: rgba(255, 251, 242, .08);
	border-color: var(--on-trust);
	box-shadow: 3px 3px 0 rgba(255, 251, 242, .28);
}
body.kc-rd.home .kc-hs-mail .kc-hs-rate + .kc-hs-rate { border-left-color: rgba(255, 251, 242, .22); }
body.kc-rd.home .kc-hs-mail .kc-hs-rate i { color: rgba(255, 251, 242, .72); }
body.kc-rd.home .kc-hs-mail .kc-hs-rate b { color: var(--on-trust); }

/* --------------------------------------------------------------------------
   95.4 Local delivery — real-geography GTA map
   -------------------------------------------------------------------------- */
body.kc-rd.home .kc-hs-mapfig { margin: 0; }
body.kc-rd.home .kc-hs-map {
	display: block;
	width: 100%;
	height: auto;
	background: var(--surface-raised);
	border: 1.5px solid var(--text);
	border-radius: var(--kc-radius-md);
	box-shadow: 3px 3px 0 var(--text);
	overflow: hidden;
}
body.kc-rd.home .kc-hs-m-ground { fill: var(--surface-raised); }
/* the 2-hour zone is a soft radial wash (the owner asked for a halo, not a
   diagram ring); the gradient stops are classes so the colour stays a token */
body.kc-rd.home .kc-hs-hg0 { stop-color: var(--trust); stop-opacity: .11; }
body.kc-rd.home .kc-hs-hg1 { stop-color: var(--trust); stop-opacity: .075; }
body.kc-rd.home .kc-hs-hg2 { stop-color: var(--trust); stop-opacity: 0; }
body.kc-rd.home .kc-hs-m-water { fill: var(--text); opacity: .06; }
body.kc-rd.home .kc-hs-m-shore {
	fill: none;
	stroke: var(--text);
	stroke-opacity: .38;
	stroke-width: 2;
	stroke-linejoin: round;
}
/* the Toronto Islands — the one shoreline feature everyone recognises */
body.kc-rd.home .kc-hs-m-isle {
	fill: var(--surface-raised);
	stroke: var(--text);
	stroke-opacity: .38;
	stroke-width: 1.6;
}
body.kc-rd.home .kc-hs-m-road {
	fill: none;
	stroke: var(--text);
	stroke-opacity: .13;
	stroke-width: 2.6;
	stroke-linecap: round;
	stroke-linejoin: round;
}
body.kc-rd.home .kc-hs-m-roadlab text {
	font-family: var(--kc-font-body);
	font-variant-numeric: tabular-nums;
	font-size: 12px;
	font-weight: 600;
	letter-spacing: .06em;
	fill: var(--text);
	fill-opacity: .4;
}
body.kc-rd.home .kc-hs-m-zone {
	fill: none;
	stroke: var(--trust);
	stroke-opacity: .42;
	stroke-width: 2.2;
	stroke-dasharray: 10 9;
}
body.kc-rd.home .kc-hs-m-pins circle {
	fill: var(--trust);
	fill-opacity: .92;
	stroke: var(--surface-raised);
	stroke-width: 2;
}
body.kc-rd.home .kc-hs-m-city text {
	font-family: var(--kc-font-body);
	font-size: 15.5px;
	font-weight: 600;
	letter-spacing: .01em;
	fill: var(--text);
	fill-opacity: .82;
}
body.kc-rd.home .kc-hs-m-city .kc-hs-m-city-big {
	font-size: 17px;
	font-weight: 700;
	letter-spacing: .04em;
}
body.kc-rd.home .kc-hs-m-lab text {
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	font-weight: 600;
	letter-spacing: .17em;
	fill: var(--text);
	fill-opacity: .6;
}
/* a place label on water is furniture, not copy — and the svg is role=img,
   so AT reads the aria-label, never this text */
body.kc-rd.home .kc-hs-m-lab .kc-hs-m-lake { fill-opacity: .44; }
/* the legend swatch has to be the same dash as the zone it explains */
body.kc-rd.home .kc-hs-m-key {
	stroke: var(--trust);
	stroke-opacity: .55;
	stroke-width: 2.2;
	stroke-dasharray: 10 9;
}
body.kc-rd.home .kc-hs-m-compass text {
	font-family: var(--kc-font-body);
	font-size: 15px;
	font-weight: 700;
	fill: var(--text);
	fill-opacity: .55;
}
body.kc-rd.home .kc-hs-m-compass path {
	fill: none;
	stroke: var(--text);
	stroke-opacity: .55;
	stroke-width: 2;
}
body.kc-rd.home .kc-hs-mapcap {
	margin: 12px 2px 0;
	font-size: 11.5px;
	line-height: 1.5;
	color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   95.5 Area chips — the real internal links
   -------------------------------------------------------------------------- */
body.kc-rd.home .kc-hs-areas { margin-top: 40px; }
body.kc-rd.home .kc-hs-areas-l {
	display: block;
	font-size: 10px;
	letter-spacing: .22em;
	text-transform: uppercase;
	font-weight: 600;
	color: var(--text-muted);
	margin-bottom: 12px;
}
body.kc-rd.home .kc-hs-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	list-style: none;
	margin: 0;
	padding: 0;
}
body.kc-rd.home .kc-hs-chips li { margin: 0; }
body.kc-rd.home .kc-hs-chips a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding: 0 15px;
	border: 1px solid var(--border-strong);
	border-radius: var(--kc-radius-md);
	background: var(--surface-raised);
	color: var(--text);
	font-size: 13px;
	font-weight: 600;
	line-height: 1.2;
	transition: border-color .2s, color .2s;
}
body.kc-rd.home .kc-hs-chips a:hover,
body.kc-rd.home .kc-hs-chips a:focus-visible { border-color: var(--trust); color: var(--trust); }
body.kc-rd.home .kc-hs-chip-all a {
	background: transparent;
	border-style: dashed;
	color: var(--trust);
}

/* --------------------------------------------------------------------------
   95.6 Mail order — pine band + Canada shipping map
   -------------------------------------------------------------------------- */
body.kc-rd.home .kc-hs-mail {
	background: var(--trust);
	color: var(--on-trust);
	border-top: 0;
	padding: 58px 0;
}
body.kc-rd.home .kc-hs-mail h2 { color: var(--on-trust); }
body.kc-rd.home .kc-hs-mail .kc-hs-lede { color: rgba(255, 251, 242, .84); }
body.kc-rd.home .kc-hs-mail .kc-hs-fine { color: rgba(255, 251, 242, .74); }   /* 5.0:1 on pine */

body.kc-rd.home .kc-hs-lanefig { margin: 0; }
body.kc-rd.home .kc-hs-lane { display: block; width: 100%; height: auto; }
body.kc-rd.home .kc-hs-c-land {
	fill: rgba(255, 251, 242, .10);
	stroke: var(--on-trust);
	stroke-opacity: .5;
	stroke-width: 1.6;
	stroke-linejoin: round;
}
body.kc-rd.home .kc-hs-c-arc path {
	fill: none;
	stroke: var(--on-trust);
	stroke-opacity: .5;
	stroke-width: 2.2;
	stroke-linecap: round;
	stroke-dasharray: 1 7;
}
body.kc-rd.home .kc-hs-c-stop circle {
	fill: none;
	stroke: var(--on-trust);
	stroke-opacity: .75;
	stroke-width: 2;
}
body.kc-rd.home .kc-hs-c-stop text,
body.kc-rd.home .kc-hs-c-hq text {
	font-family: var(--kc-font-body);
	font-size: 14.5px;
	font-weight: 600;
	letter-spacing: .12em;
	fill: var(--on-trust);
}
body.kc-rd.home .kc-hs-c-stop text { fill-opacity: .85; }   /* 5.7:1 on pine */
body.kc-rd.home .kc-hs-c-hq circle { fill: var(--action); }
body.kc-rd.home .kc-hs-c-hq text {
	font-weight: 700;
	letter-spacing: .14em;
	fill-opacity: .95;
}

/* --------------------------------------------------------------------------
   95.7 Trusted Brands
   -------------------------------------------------------------------------- */
body.kc-rd.home .kc-hs .kc-hm-sechead { margin-bottom: 18px; }
body.kc-rd.home .kc-hs .kc-hm-sechead h2 { font-size: 34px; }
/* the shared sechead link is a 20px-tall tap target — bring it to 44 */
body.kc-rd.home .kc-hs .kc-hm-sechead a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}
/* six logos in three columns fills a whole rectangle at every breakpoint
   (3x2 desktop and tablet, 2x3 phone) — a ragged last row of one or two
   wordmarks reads as a loading state */
body.kc-rd.home .kc-hs-brandrow {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 12px;
	list-style: none;
	margin: 0;
	padding: 0;
}
body.kc-rd.home .kc-hs-brandrow li { margin: 0; }
body.kc-rd.home .kc-hs-brandrow a {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 108px;
	padding: 18px;
	background: var(--surface-raised);
	border: 1px solid var(--border);
	border-radius: var(--kc-radius-md);
	transition: border-color .2s, transform .35s cubic-bezier(.32, .72, 0, 1);
}
body.kc-rd.home .kc-hs-brandrow a:hover { border-color: var(--border-strong); transform: translateY(-2px); }
body.kc-rd.home .kc-hs-brandrow img {
	max-width: 100%;
	max-height: 58px;
	width: auto;
	height: auto;
	object-fit: contain;
	/* logos ship on baked-in white; multiply drops it onto the card cleanly
	   without needing a transparent re-cut of six third-party assets */
	mix-blend-mode: multiply;
}

/* --------------------------------------------------------------------------
   95.8 Tablet — the split lies down before the columns get too narrow to read
   -------------------------------------------------------------------------- */
@media (max-width: 980px) {
	body.kc-rd.home .kc-hs-split { grid-template-columns: 1fr; gap: 30px; }
	body.kc-rd.home .kc-hs-split-rev .kc-hs-copy { order: 0; }
	body.kc-rd.home .kc-hs-split-rev .kc-hs-lanefig { order: 0; }
	body.kc-rd.home .kc-hs-steps { grid-template-columns: repeat(2, 1fr); }
	body.kc-rd.home .kc-hs-lede, body.kc-rd.home .kc-hs-fine { max-width: none; }
}

/* --------------------------------------------------------------------------
   95.9 Phone — nothing collapses, nothing truncates, no horizontal scroll
   -------------------------------------------------------------------------- */
@media (max-width: 768px) {
	body.kc-rd.home .kc-hs-in { padding: 0 18px; }
	body.kc-rd.home .kc-hs-sec { padding: 38px 0; }
	body.kc-rd.home .kc-hs-mail { padding: 42px 0; }
	/* display face floor is 28px — this is the smallest these H2s may go */
	body.kc-rd.home .kc-hs h2,
	body.kc-rd.home .kc-hs .kc-hm-sechead h2 { font-size: 28px; }
	body.kc-rd.home .kc-hs-head { margin-bottom: 20px; }

	body.kc-rd.home .kc-hs-steps { grid-template-columns: 1fr; gap: 10px; }
	body.kc-rd.home .kc-hs-step { padding: 18px 16px 20px; }
	body.kc-rd.home .kc-hs-go { align-self: stretch; }

	/* three rate cells in 339px turns "$15 within Ontario / $20 rest of
	   Canada" into a two-character column — stack instead of shrink */
	body.kc-rd.home .kc-hs-rates { grid-template-columns: 1fr; }
	body.kc-rd.home .kc-hs-rate { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; padding: 13px 15px; }
	body.kc-rd.home .kc-hs-rate + .kc-hs-rate { border-left: 0; border-top: 1px solid var(--border); }
	body.kc-rd.home .kc-hs-mail .kc-hs-rate + .kc-hs-rate { border-top-color: rgba(255, 251, 242, .22); }
	body.kc-rd.home .kc-hs-rate i { margin-bottom: 0; }
	body.kc-rd.home .kc-hs-rate b { text-align: right; }

	/* both maps' labels scale with the drawing (viewBox units), so at 339px
	   they would land near 8px — step them up in the viewBox's own units.
	   Checked against the dense Vaughan cluster at 375px: no collisions. */
	body.kc-rd.home .kc-hs-m-city text { font-size: 18.5px; }
	body.kc-rd.home .kc-hs-m-city .kc-hs-m-city-big { font-size: 20px; }
	body.kc-rd.home .kc-hs-m-lab text { font-size: 17px; letter-spacing: .14em; }
	body.kc-rd.home .kc-hs-m-roadlab text { font-size: 15px; }
	body.kc-rd.home .kc-hs-m-pins circle { r: 7px; }
	body.kc-rd.home .kc-hs-m-pins .kc-hs-m-pin-big { r: 9px; }
	body.kc-rd.home .kc-hs-c-stop text { font-size: 17.5px; }
	/* the origin caption is the longest string on the drawing — one step
	   smaller than the stops or it runs off the right margin at 339px */
	body.kc-rd.home .kc-hs-c-hq text { font-size: 16px; }
	body.kc-rd.home .kc-hs-c-stop circle { r: 8px; }
	body.kc-rd.home .kc-hs-c-hq circle { r: 12px; }

	body.kc-rd.home .kc-hs-areas { margin-top: 30px; }
	body.kc-rd.home .kc-hs-brandrow { grid-template-columns: repeat(2, 1fr); gap: 10px; }
	body.kc-rd.home .kc-hs-brandrow a { min-height: 84px; padding: 14px; }
	body.kc-rd.home .kc-hs-brandrow img { max-height: 44px; }
}

@media (prefers-reduced-motion: reduce) {
	body.kc-rd.home .kc-hs-brandrow a { transition: none; }
	body.kc-rd.home .kc-hs-brandrow a:hover { transform: none; }
}

/* §95.9 AstroBear landing-soon 文字卡(2026-08-28 owner 点名;货到换动态 logo 卡) */
.kc-hs-brandrow .kc-hs-brandsoon{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;opacity:.75}
.kc-hs-brandrow .kc-hs-brandsoon span{font-weight:700;letter-spacing:.02em}
.kc-hs-brandrow .kc-hs-brandsoon em{font-style:normal;font-size:.72em;opacity:.7;text-transform:uppercase;letter-spacing:.08em}

/* ===== 96-home-social.css ===== */
/* ==========================================================================
   96 · Homepage editorial block — "Guides & articles"
   Owner: home-social. Tokens only; no raw brand values, not even as rgba().
   Scoped to body.kc-rd.home so nothing here can reach another surface.
   ========================================================================== */

/* The block is appended AFTER .kc-home closes, so it does not inherit that
   element's `margin: 0 -20px` escape from the entry padding. Mirroring the
   bleed here is what makes its left edge land on the same pixel column as
   .kc-hm-picks above it — measured, not eyeballed. */
body.kc-rd.home .kc-hsoc-bleed { margin: 0 -20px; }

body.kc-rd.home .kc-hsoc {
	max-width: 1160px;
	margin: 0 auto;
	padding: 34px 40px 46px;
}

/* ---------- head ---------- */
body.kc-rd.home .kc-hsoc-head {
	display: flex;
	justify-content: space-between;
	align-items: flex-end;
	gap: var(--kc-space-5);
	margin-bottom: var(--kc-space-5);
}
body.kc-rd.home .kc-hsoc-eyebrow {
	display: block;
	font-family: var(--kc-font-body);
	font-size: 10px;
	line-height: 1.4;
	letter-spacing: .22em;
	text-transform: uppercase;
	font-weight: 600;
	color: var(--trust);
	margin-bottom: var(--kc-space-2);
}
/* 28px is the floor for the display face — below it, Poppins. */
body.kc-rd.home .kc-hsoc h2 {
	font-family: var(--kc-font-display);
	font-size: 28px;
	line-height: 1.15;
	margin: 0;
	color: var(--text);
}
body.kc-rd.home .kc-hsoc-lede {
	font-family: var(--kc-font-body);
	font-size: 13.5px;
	line-height: 1.55;
	color: var(--text-muted);
	margin: var(--kc-space-2) 0 0;
	max-width: 52ch;
}
body.kc-rd.home .kc-hsoc-all {
	flex: none;
	font-family: var(--kc-font-body);
	font-size: 13px;
	font-weight: 600;
	color: var(--trust);
	text-decoration: none;
	white-space: nowrap;
	padding: var(--kc-space-2) 0;
}
body.kc-rd.home .kc-hsoc-all:hover { text-decoration: underline; }

/* Any figure the reader could compare against another figure gets the
   monospaced-width treatment, so dates and counts do not jitter. */
body.kc-rd.home .kc-hsoc-n { font-variant-numeric: tabular-nums; }

/* ---------- card row ---------- */
body.kc-rd.home .kc-hsoc-grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 18px;
}
body.kc-rd.home .kc-hsoc-card {
	display: flex;
	flex-direction: column;
	background: var(--card-bg);
	border: 1px solid var(--card-border);
	border-radius: var(--card-radius);
	overflow: hidden;
	text-decoration: none;
	color: inherit;
	transition: border-color .18s ease, transform .18s ease;
}
body.kc-rd.home .kc-hsoc-card:hover {
	border-color: var(--border-strong);
	transform: translateY(-2px);
}
body.kc-rd.home .kc-hsoc-thumb {
	display: block;
	aspect-ratio: 16 / 9;
	background: var(--card-shot-bg);
	overflow: hidden;
}
body.kc-rd.home .kc-hsoc-thumb img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}
body.kc-rd.home .kc-hsoc-body {
	display: flex;
	flex-direction: column;
	gap: var(--kc-space-2);
	padding: 14px 16px 16px;
	flex: 1;
}
/* A label, not a control: no border, no shadow, no hover state — nothing here
   may read as something you can click and be charged for. */
body.kc-rd.home .kc-hsoc-cat {
	align-self: flex-start;
	font-family: var(--kc-font-body);
	font-size: 10px;
	letter-spacing: .14em;
	text-transform: uppercase;
	font-weight: 600;
	color: var(--text-muted);
	background: var(--surface-sunken);
	padding: 4px 8px;
	border-radius: var(--kc-radius-sm);
}
body.kc-rd.home .kc-hsoc-title {
	font-family: var(--kc-font-body);
	font-size: 15px;
	line-height: 1.35;
	font-weight: 600;
	color: var(--text);
	margin: 0;
	display: -webkit-box;
	-webkit-line-clamp: 3;
	line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
body.kc-rd.home .kc-hsoc-card:hover .kc-hsoc-title { text-decoration: underline; }
body.kc-rd.home .kc-hsoc-meta {
	margin-top: auto;
	font-family: var(--kc-font-body);
	font-size: 11.5px;
	color: var(--text-muted);
}
body.kc-rd.home .kc-hsoc-dot { margin: 0 4px; }

/* ---------- evergreen guide pills ---------- */
body.kc-rd.home .kc-hsoc-guides {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--kc-space-2);
	margin-top: var(--kc-space-5);
	padding-top: var(--kc-space-4);
	border-top: 1px solid var(--border);
}
/* Own line on purpose. Four verbatim post titles measure 1,116px against
   1,080px of room, so the row wraps no matter what — and with the label
   inline, the wrap strands one pill under it and reads as a mistake. Given
   its own line, the second row starts at the same left edge as the first and
   reads as a list. (--text-muted, not --text-faint: .45 alpha on cream is
   2.85:1, which fails AA for 11px type. .70 alpha measures 6.26:1.) */
body.kc-rd.home .kc-hsoc-glabel {
	flex: 0 0 100%;
	font-family: var(--kc-font-body);
	font-size: 11px;
	letter-spacing: .14em;
	text-transform: uppercase;
	font-weight: 600;
	color: var(--text-muted);
	margin-bottom: var(--kc-space-1);
}
body.kc-rd.home .kc-hsoc-pill {
	display: inline-flex;
	align-items: center;
	min-height: 44px;          /* touch floor — the reference build shipped 32px */
	padding: 0 14px;
	font-family: var(--kc-font-body);
	font-size: 12.5px;
	line-height: 1.3;
	font-weight: 500;
	color: var(--text);
	background: var(--surface-raised);
	border: 1px solid var(--border);
	border-radius: 999px;
	text-decoration: none;
	transition: border-color .18s ease;
}
body.kc-rd.home .kc-hsoc-pill:hover {
	border-color: var(--border-strong);
	text-decoration: underline;
}

/* Keyboard users get a ring the theme does not supply on these. Tabbing into
   the phone scroller relies on it — without a visible focus target you cannot
   tell which card the browser just scrolled to. */
body.kc-rd.home .kc-hsoc-card:focus-visible,
body.kc-rd.home .kc-hsoc-pill:focus-visible,
body.kc-rd.home .kc-hsoc-all:focus-visible {
	outline: 2px solid var(--trust);
	outline-offset: 2px;
}

/* ==========================================================================
   Phone / narrow — the row becomes a snap scroller.
   Three image cards can carry a horizontal scroller because a peeking third
   card is its own affordance; the pill row below deliberately does NOT, because
   a row of small text chips gives the reader nothing to notice. That split is
   the whole lesson from the reference site's chips being sent back once.
   ========================================================================== */
@media (max-width: 900px) {
	/* padding stays at the desktop 40px through this band: the neighbouring
	   .kc-hm-picks only drops to 18px at 768, and a section that changes
	   gutter at a different breakpoint than the one above it is visibly
	   crooked between 769 and 900. */
	body.kc-rd.home .kc-hsoc-head {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--kc-space-3);
	}
	body.kc-rd.home .kc-hsoc-grid {
		display: flex;
		grid-template-columns: none;
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		-webkit-overflow-scrolling: touch;
		gap: 12px;
		/* full-bleed the scroller inside the padded container: the negative
		   margin and the padding cancel, so the page itself never scrolls
		   sideways — only this strip does. */
		margin: 0 -40px;
		padding: 2px 40px 6px;
		/* Without this the snap engine treats the scrollport's border edge as
		   the snap position, ignores the 40px left padding, and auto-scrolls by
		   exactly that much on load — card 1 ends up hanging off the left of
		   the viewport while every other line of the section starts at the
		   text column. Measured: card1.x was -15 against a text column of 3. */
		scroll-padding-left: 40px;
		scrollbar-width: none;
	}
	body.kc-rd.home .kc-hsoc-grid::-webkit-scrollbar { display: none; }
	body.kc-rd.home .kc-hsoc-card {
		flex: 0 0 72vw;         /* < 100vw on purpose: card 3 must peek */
		max-width: 300px;
		scroll-snap-align: start;
	}
	body.kc-rd.home .kc-hsoc-card:hover { transform: none; }
	body.kc-rd.home .kc-hsoc-title { -webkit-line-clamp: 2; line-clamp: 2; }
}

@media (max-width: 768px) {
	body.kc-rd.home .kc-hsoc-bleed { margin: 0 -15px; }
	/* 18px, not 16 — that is what kc-redesign.css gives .kc-hm-picks below 768
	   ("keep card borders off the viewport edge"). Ink-scanned: matching the
	   number is what puts my H2 on the neighbour's pixel column. */
	body.kc-rd.home .kc-hsoc { padding: 26px 18px 36px; }
	body.kc-rd.home .kc-hsoc-grid { margin: 0 -18px; padding: 2px 18px 6px; scroll-padding-left: 18px; }
	body.kc-rd.home .kc-hsoc h2 { font-size: 28px; }
	body.kc-rd.home .kc-hsoc-guides { gap: var(--kc-space-2); }
	body.kc-rd.home .kc-hsoc-pill { font-size: 12px; padding: 0 12px; }
}

@media (prefers-reduced-motion: reduce) {
	body.kc-rd.home .kc-hsoc-card,
	body.kc-rd.home .kc-hsoc-pill { transition: none; }
	body.kc-rd.home .kc-hsoc-card:hover { transform: none; }
}

/* ===== 97-home-seo2.css ===== */
/* ==========================================================================
   97-home-seo2 — homepage SEO block v2 (owner-approved copy, artifact 7b4ae5bb)
   Appended by inc-home-seo2.php at the_content priority 15 (last block before
   the footer). Kill switch: wp option update kc_home_seo2 0

   SCOPE RENAME: the artifact shipped these rules on the same class name as
   inc-home.php's page wrapper — unprefixed they would repaint the whole
   homepage (17px body font, green ::before/::after bars, link underlines).
   Wrapper and every selector renamed to `.kc-seo2`; the artifact's bare
   selectors (.kc-masthead, .kc-eyebrow, …) are additionally prefixed so
   nothing leaks onto the hero's own .kc-eyebrow spans.

   PALETTE: the artifact's own hexes (#164728 / #DC9814 / #282828 …), owner-
   approved. None of them appear in build_kc_css.py's forbidden token list —
   deliberately NOT converted to the site's pine/amber tokens.
   ========================================================================== */

.kc-seo2{
  --kc-ink:#282828; --kc-ink-soft:#5C5C55;
  --kc-green:#164728; --kc-green-dark:#0B2F19;
  --kc-amber:#DC9814;
  --kc-paper:#FFFFFF; --kc-cream:#F6F5F0; --kc-oat:#EFEDE3;
  --kc-rule:#E2E1D9;
  position:relative; max-width:1400px; margin:56px auto; padding:52px 24px;
  color:var(--kc-ink); font-size:17px; line-height:1.6;
  font-family:var(--kc-font-body);
}
.kc-seo2::before,.kc-seo2::after{content:"";position:absolute;left:0;right:0;height:12px;background:var(--kc-green);}
.kc-seo2::before{top:0}.kc-seo2::after{bottom:0}
.kc-seo2 *{box-sizing:border-box}
.kc-seo2 h2,.kc-seo2 h3,.kc-seo2 h4,.kc-seo2 h5{letter-spacing:-.02em;line-height:1.12;margin:0;font-weight:700;font-family:var(--kc-font-body);color:var(--kc-ink)}
.kc-seo2 p{margin:0 0 1.15em}
.kc-seo2 p:last-child{margin-bottom:0}
.kc-seo2 strong{font-weight:700;color:var(--kc-ink)}
.kc-seo2 a{color:var(--kc-green);text-decoration:none;border-bottom:1.5px solid rgba(22,71,40,.35);padding-bottom:1px;transition:all .15s ease}
.kc-seo2 a:hover{color:var(--kc-green-dark);background:var(--kc-oat);border-bottom-color:var(--kc-green-dark)}

/* masthead */
.kc-seo2 .kc-masthead{display:grid;grid-template-columns:auto 1fr;gap:32px;align-items:end;padding-bottom:32px;border-bottom:2px solid var(--kc-ink);margin-bottom:22px}
.kc-seo2 .kc-eyebrow{font-size:.8rem;letter-spacing:.22em;text-transform:uppercase;color:var(--kc-green);border-left:3px solid var(--kc-amber);padding-left:14px;align-self:start;padding-top:4px;line-height:1.5;font-weight:600;background:none;border-radius:0}
.kc-seo2 .kc-masthead h2{font-size:clamp(1.9rem,4.2vw,3rem);letter-spacing:-.03em;line-height:1.05}
.kc-seo2 .kc-lede{font-size:1.05rem;max-width:66ch;color:var(--kc-ink-soft);margin-top:6px}

/* section head */
.kc-seo2 .kc-sec{margin:84px 0 28px;padding-bottom:16px;border-bottom:1px solid var(--kc-rule);display:flex;align-items:baseline;justify-content:space-between;gap:24px;flex-wrap:wrap}
.kc-seo2 .kc-sec h3{font-size:clamp(1.55rem,3vw,2.3rem);letter-spacing:-.025em;flex:1;min-width:0}
.kc-seo2 .kc-sec .kc-label{font-size:.8rem;letter-spacing:.22em;text-transform:uppercase;color:var(--kc-amber);white-space:nowrap;font-weight:600}

/* callout */
.kc-seo2 .kc-callout{font-weight:400;font-size:clamp(1.02rem,1.8vw,1.22rem);line-height:1.4;background:var(--kc-oat);border-left:5px solid var(--kc-green);padding:22px 28px 22px 34px;margin:30px 0;position:relative;letter-spacing:-.01em}
.kc-seo2 .kc-callout::before{content:"\201C";position:absolute;left:8px;top:-6px;color:var(--kc-green);font-family:Georgia,serif;font-size:3rem;line-height:1;opacity:.28}

/* accordions */
.kc-seo2 .kc-stack{margin:26px 0 30px;border-top:1px solid var(--kc-rule)}
.kc-seo2 .kc-stack details{border-bottom:1px solid var(--kc-rule);background:var(--kc-paper);transition:background .2s ease}
.kc-seo2 .kc-stack details[open]{background:var(--kc-cream)}
.kc-seo2 .kc-stack summary{list-style:none;cursor:pointer;padding:26px 30px 26px 92px;position:relative;display:grid;grid-template-columns:1fr auto;gap:14px 24px;align-items:baseline}
.kc-seo2 .kc-stack summary::-webkit-details-marker{display:none}
.kc-seo2 .kc-stack summary::before{content:attr(data-num);position:absolute;left:28px;top:20px;font-size:2.5rem;font-weight:700;color:var(--kc-amber);line-height:1;letter-spacing:-.04em;font-variant-numeric:tabular-nums}
.kc-seo2 .kc-stack summary::after{content:"+";font-size:1.9rem;color:var(--kc-green);line-height:1;transition:transform .25s ease;align-self:center;grid-column:2;grid-row:1/span 2}
.kc-seo2 .kc-stack details[open] summary::after{transform:rotate(45deg)}
.kc-seo2 .kc-stack summary h4{font-size:clamp(1.3rem,2.4vw,1.8rem);grid-column:1;grid-row:1;transition:color .15s ease}
.kc-seo2 .kc-stack summary:hover h4{color:var(--kc-green)}
.kc-seo2 .kc-stack summary .kc-tag{font-size:.68rem;letter-spacing:.22em;text-transform:uppercase;color:var(--kc-ink-soft);grid-column:1;grid-row:2;margin-top:-6px;font-weight:600}
.kc-seo2 .kc-body{padding:6px 30px 30px 92px;border-top:1px dashed var(--kc-rule)}
.kc-seo2 .kc-body p:first-child{margin-top:22px;color:var(--kc-ink-soft);max-width:66ch;font-size:1rem}
.kc-seo2 .kc-note{font-size:.95rem;color:var(--kc-ink-soft);margin-top:20px;max-width:66ch}

/* hairline product grid */
.kc-seo2 .kc-prods{list-style:none;padding:0;margin:22px 0 0;display:grid;grid-template-columns:repeat(2,1fr);gap:1px;background:var(--kc-rule);border:1px solid var(--kc-rule)}
.kc-seo2 .kc-prods li{padding:16px 20px;background:var(--kc-cream);display:flex;align-items:center;justify-content:space-between;gap:14px;transition:background .2s ease;margin:0}
.kc-seo2 .kc-prods li:hover{background:var(--kc-oat)}
.kc-seo2 .kc-prods h5{font-size:1.02rem;font-weight:600;line-height:1.25;margin:0}
.kc-seo2 .kc-prods h5 a{color:var(--kc-ink);border-bottom:2px solid transparent;padding-bottom:2px}
.kc-seo2 .kc-prods h5 a:hover{color:var(--kc-green);background:transparent;border-bottom-color:var(--kc-green)}
.kc-seo2 .kc-prods .kc-price{font-size:.76rem;letter-spacing:.14em;text-transform:uppercase;color:var(--kc-green);border-bottom:1px solid var(--kc-green);padding-bottom:1px;white-space:nowrap;font-weight:600;font-variant-numeric:tabular-nums}
.kc-seo2 .kc-prods .kc-price::after{content:" \2192"}

/* directory tiles */
.kc-seo2 .kc-dir{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;margin:26px 0 30px;background:var(--kc-rule);border:1px solid var(--kc-rule)}
.kc-seo2 .kc-dir-item{padding:24px 20px;background:var(--kc-paper);transition:background .2s ease;display:flex;flex-direction:column;justify-content:space-between;min-height:124px}
.kc-seo2 .kc-dir-item:hover{background:var(--kc-oat)}
.kc-seo2 .kc-dir-num{font-size:.7rem;letter-spacing:.2em;color:var(--kc-amber);margin-bottom:14px;font-weight:600;font-variant-numeric:tabular-nums}
.kc-seo2 .kc-dir-item h4{font-size:1.12rem;font-weight:600;line-height:1.22}
.kc-seo2 .kc-dir-item--more{background:var(--kc-green)}
.kc-seo2 .kc-dir-item--more:hover{background:var(--kc-green-dark)}
.kc-seo2 .kc-dir-item--more .kc-dir-num{color:#E8C97A}
.kc-seo2 .kc-dir-item--more h4,.kc-seo2 .kc-dir-item--more h4 a{color:#FFF;border-bottom-color:rgba(255,255,255,.5)}
.kc-seo2 .kc-dir-item--more h4 a:hover{color:#FFF;background:transparent;border-bottom-color:#FFF}

/* 3-col cards */
.kc-seo2 .kc-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;margin:30px 0;background:var(--kc-rule);border:1px solid var(--kc-rule)}
.kc-seo2 .kc-card{padding:30px 26px 32px;background:var(--kc-paper);transition:background .2s ease}
.kc-seo2 .kc-card:hover{background:var(--kc-cream)}
.kc-seo2 .kc-card h4{font-size:clamp(1.15rem,2vw,1.4rem);margin:0 0 16px;padding-top:13px;border-top:2px solid var(--kc-green);letter-spacing:-.02em;line-height:1.16}
.kc-seo2 .kc-card h4 a{color:inherit;border-bottom:2px solid transparent;padding-bottom:2px}
.kc-seo2 .kc-card h4 a:hover{color:var(--kc-green);border-bottom-color:var(--kc-green);background:transparent}
.kc-seo2 .kc-card p{font-size:.95rem;line-height:1.62}
.kc-seo2 .kc-cards--two{grid-template-columns:repeat(2,1fr)}

/* FAQ */
.kc-seo2 .kc-faq{margin:26px 0;border-top:1px solid var(--kc-rule);counter-reset:kcfaq}
.kc-seo2 .kc-faq details{border-bottom:1px solid var(--kc-rule);background:var(--kc-paper);transition:background .2s ease}
.kc-seo2 .kc-faq details[open]{background:var(--kc-cream)}
.kc-seo2 .kc-faq summary{list-style:none;cursor:pointer;position:relative}
.kc-seo2 .kc-faq summary::-webkit-details-marker{display:none}
.kc-seo2 .kc-faq summary h4{font-size:clamp(1.02rem,1.7vw,1.2rem);font-weight:600;padding:24px 60px 24px 76px;margin:0;line-height:1.3;transition:color .15s ease}
.kc-seo2 .kc-faq summary:hover h4{color:var(--kc-green)}
.kc-seo2 .kc-faq summary::before{content:counter(kcfaq,decimal-leading-zero);counter-increment:kcfaq;position:absolute;left:28px;top:26px;font-size:.8rem;color:var(--kc-amber);letter-spacing:.1em;font-weight:700;font-variant-numeric:tabular-nums}
.kc-seo2 .kc-faq summary::after{content:"+";position:absolute;right:28px;top:50%;transform:translateY(-50%);font-size:1.9rem;color:var(--kc-green);line-height:1;transition:transform .25s ease}
.kc-seo2 .kc-faq details[open] summary::after{transform:translateY(-50%) rotate(45deg)}
.kc-seo2 .kc-faq details>div{padding:0 76px 26px;color:var(--kc-ink-soft);font-size:.97rem;line-height:1.68}

@media (max-width:900px){
  .kc-seo2 .kc-masthead{grid-template-columns:1fr;gap:16px;align-items:start}
  .kc-seo2 .kc-dir{grid-template-columns:repeat(2,1fr)}
  .kc-seo2 .kc-cards,.kc-seo2 .kc-cards--two{grid-template-columns:1fr}
  .kc-seo2 .kc-prods{grid-template-columns:1fr}
  .kc-seo2 .kc-sec{margin-top:58px}
  .kc-seo2 .kc-stack summary{padding-left:76px}
  .kc-seo2 .kc-stack summary::before{font-size:1.9rem;left:24px;top:24px}
  .kc-seo2 .kc-body{padding-left:76px}
}
@media (max-width:560px){
  .kc-seo2{padding:36px 18px}
  .kc-seo2 .kc-dir{grid-template-columns:1fr}
  .kc-seo2 .kc-stack summary{padding:22px 54px 22px 62px}
  .kc-seo2 .kc-stack summary::before{font-size:1.6rem;left:18px;top:20px}
  .kc-seo2 .kc-stack summary::after{right:18px;font-size:1.6rem}
  .kc-seo2 .kc-body{padding:6px 18px 26px 62px}
  .kc-seo2 .kc-prods li{flex-direction:column;align-items:flex-start;gap:7px}
  .kc-seo2 .kc-faq summary h4{padding:20px 50px 20px 58px}
  .kc-seo2 .kc-faq summary::before{left:18px;top:22px}
  .kc-seo2 .kc-faq summary::after{right:16px}
  .kc-seo2 .kc-faq details>div{padding:0 18px 22px}
}
@media (prefers-reduced-motion:reduce){.kc-seo2 *{transition:none!important}}

/* ===== 99-pdp-zoom.css ===== */
/* ==========================================================================
   99 · PDP: tapping the product photo opens the zoom          (NN lead, 2026-09-24)
   --------------------------------------------------------------------------
   Clarity (09-21→24): the Phoenix 2g pen PDP had dead clicks in 50% of its
   sessions (23 in 6) — shoppers tap the pack to read the strain names.
   Two layers made every tap on the main photo do nothing:
     · Savoy `product_image_zoom` was 0, so PhotoSwipe never loaded;
     · savoy-child/assets/css/main.css `.woocommerce-product-gallery a
       {pointer-events:none}` — the image link cannot receive a tap at all, so
       the tap lands on the slide <div> and WooCommerce's delegated
       `.woocommerce-product-gallery__image a` handler never matches
       (measured: elementFromPoint at the photo centre = the slide div).
   Savoy adds `.lightbox-enabled` to the gallery only when zoom is on, so this
   rule is inert the moment zoom is switched off again — it can never turn the
   photo into a link to the raw 2560px file.
   ========================================================================== */
body.kc-rd.single-product .woocommerce-product-gallery.lightbox-enabled .woocommerce-product-gallery__image a {
	pointer-events: auto;
}
/* The lightbox sits at PhotoSwipe's z-index 1500; the fixed marquee (11112), the
   gift teaser (2147483500) and the WhatsApp bubble all drew on top of it.
   Measured on staging 390×844: elementFromPoint(195,20) = .kc-marquee-text. */
body.kc-rd.single-product .pswp {
	z-index: 2147483600;
}

/* ===== 2026-09-25 hotfix: sale flag + gift popup (see parts/99-pdp-zoom.css, 99b-gift-popup-fit.css) ===== */
/* 2026-09-25 fix: Savoy shop.css moves the sale flag LEFT when the gallery is
   `.lightbox-enabled` (`left:15px; right:auto`, to clear its zoom trigger), but
   main.css `.single-product .nm-single-product-showcase .onsale {right:30px}`
   still pins the right edge → the "−22%" flag stretched across the whole photo
   (owner screenshot, Pink Tuna). KC hides the trigger anyway, so the flag goes
   back where it was before zoom was switched on. */
body.kc-rd.single-product .woocommerce-product-gallery.lightbox-enabled .onsale {
	left: auto;
}

/* ===== 99b-gift-popup-fit.css ===== */
/* ==========================================================================
   99b · Mystery-gift popup is always closable                (NN lead, 2026-09-25)
   --------------------------------------------------------------------------
   Customer report: the pop-up form could not be dismissed. kc-mystery-gift.php
   centres the card in a fixed flex overlay (`align-items:center`) that cannot
   scroll, and the page behind is scroll-locked. The card is ~614px tall; on an
   iPhone SE/8 in Safari (≈375×548 visible) centring pushed its top 33px above
   the screen — the × sat at y −23…17, i.e. off-screen. `margin:auto` on the card
   is "safe" centring: centred when it fits, starts at the top when it does not;
   the overlay scrolls so the rest of the form stays reachable.
   `html body` outranks the plugin's inline <style>, which prints later in the
   document. The durable fix belongs in kc-mystery-gift.php (next quiet window).
   ========================================================================== */
html body #kcmg-ov {
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
	overscroll-behavior: contain;
}
html body #kcmg-ov #kcmg {
	margin: auto;
	flex-shrink: 0;
}
