/* ==========================================================================
   COMPONENTS
   Each block is self-contained and themed through local custom properties,
   so variants are declared by re-pointing a variable rather than by
   overriding individual declarations.
   ========================================================================== */

/* --------------------------------------------------------------------------
   BUTTON
   Variants set --btn-bg / --btn-color / --btn-border and nothing else.
   -------------------------------------------------------------------------- */

/* Every button carries TWO classes: a size (--primary / --secondary / --icon)
   and a colour (--black / --white / --grey / --frost-black / --frost-white).
   The two axes are independent and always both present, so there is exactly
   one way to write a button:

       <button class="button button--primary button--black">
         <span class="button__text">Add to cart</span>
       </button>

   Size variants only re-point the offset tokens; colour variants only set
   background/colour/border. Neither reaches into the other's territory.

   Note the base is `width: 100%` — buttons fill their container by design and
   are sized by whatever wraps them. Only --icon pins its own dimensions. */
.sklad-button {
  --button-flow-x: 8px;

  position: relative;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  gap: var(--button-flow-x);

  width: 100%;
  padding: var(--button-offset-y) var(--button-offset-x);

  /* The UA sheet centres text in <button> but not in <a>. Flex centring hides
     that until a label wraps, and then the anchor version goes ragged-left.
     Setting it here makes the element a free choice: navigate with <a>, act
     with <button>, identical rendering either way. */
  text-align: center;

  border: 1px solid transparent;
  border-radius: var(--space-xsmall);
  box-sizing: border-box;
  overflow: hidden;
  cursor: pointer;

  transition:
    background-color var(--duration-fast) var(--ease-out),
    box-shadow       var(--duration-fast) var(--ease-out),
    color            var(--duration-fast) var(--ease-out);
}

/* A disabled button has to LOOK disabled. Woo's password-strength meter
   disables the register submit until the password is strong enough, and with
   only a cursor change the press read as "nothing happens" rather than as a
   control that is not available yet. */
.sklad-button[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Because the base is full-width, it is the CONTAINER that opts a button down
   to its content size — the same `> *` idiom the reference uses. Anywhere a
   button should sit inline rather than span, say so here rather than adding a
   width modifier to the button itself. */
.diptych-media__content > .sklad-button,
.input-group > .sklad-button,
.cluster > .sklad-button,
.section-header .sklad-button { width: fit-content; }

/* Hero children shrink-wrap AND take the hero's alignment. The two travel
   together — a centred hero whose children are full-width would left-align
   its own text. Mobile can override the alignment on its own. */
.hero__content > * {
  width: fit-content;
  text-align: var(--hero-text-align-mobile, var(--hero-text-align, center));
}

@media (min-width: 1024px) {
  .hero__content > * { text-align: var(--hero-text-align, center); }
}

/* The label is a child, not the button itself — that keeps the type styling
   off the box and lets an icon sit beside it without inheriting it.

   Type on a button comes from the SIZE modifier and nowhere else. --primary is
   14px, --secondary 13px, both medium weight, sentence case. Four places had
   grown their own: the sticky bar set 15px bold uppercase, the cart set
   uppercase bold, .cart-empty existed only to undo the cart's rule, and the
   cart total restated --primary's own size. A context does not get to restyle
   a button's label — if it needs a different one, it needs a different size. */
.sklad-button__text { font-weight: var(--font-weight-medium); }

.sklad-button__icon { display: flex; }
.sklad-button__icon .svg { width: 1rem; height: 1rem; }

/* --- Size ------------------------------------------------------------- */

.sklad-button--primary { --button-offset-x: 12px; --button-offset-y: 9px; }

.sklad-button--primary .sklad-button__text {
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
}

.sklad-button--secondary { --button-offset-x: 10px; --button-offset-y: 7px; }

.sklad-button--secondary .sklad-button__text {
  font-family: var(--font-sans-serif);
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
}

/* Square-ish, fixed 32px — not a circle, and it hides its label. */
.sklad-button--icon {
  --button-offset-x: 8px;
  --button-offset-y: 8px;
  width: 32px;
  height: 32px;
  min-width: 32px;
  min-height: 32px;
}

.sklad-button--icon .sklad-button__text { display: none; }
.sklad-button--icon .sklad-button__icon .svg { width: 1.125rem; height: 1.125rem; }

/* --- Colour ----------------------------------------------------------- */

.sklad-button--black {
  background-color: var(--ui-950);
  color: var(--ui-00);
}
.sklad-button--black:not([disabled]):hover { background-color: var(--ui-700); }
.sklad-button--black:not([disabled]):focus {
  border-color: var(--ui-800);
  background-color: var(--ui-600);
}
.sklad-button--black[disabled] { background-color: var(--ui-200); color: var(--ui-700); }

.sklad-button--white {
  background-color: var(--ui-00);
  color: var(--ui-950);
}
.sklad-button--white:not([disabled]):hover { box-shadow: var(--shadow-level-2); }
.sklad-button--white:not([disabled]):focus {
  box-shadow: var(--shadow-level-2);
  border-color: var(--ui-200);
}
.sklad-button--white[disabled] { background-color: var(--ui-200); color: var(--ui-600); }

/* Same white fill, but outlined — for use on white surfaces. */
.sklad-button--grey {
  background-color: var(--ui-00);
  color: var(--ui-950);
  border-color: var(--ui-200);
}
.sklad-button--grey:not([disabled]):hover { box-shadow: var(--shadow-level-2); }

/* The dark counterpart to --grey: outlined, no fill, for dark surfaces. Unlike
   the frosted pair it has no blur, because it sits on a flat colour rather than
   on imagery. */
.sklad-button--outline-white {
  background-color: transparent;
  color: var(--ui-00);
  border-color: var(--ui-00-40);
}
.sklad-button--outline-white:not([disabled]):hover {
  background-color: var(--ui-00-20);
  border-color: var(--ui-00-80);
}
.sklad-button--outline-white:not([disabled]):focus {
  background-color: var(--ui-00-20);
  border-color: var(--ui-00);
}
.sklad-button--outline-white[disabled] { color: var(--ui-00-40); border-color: var(--ui-00-20); }

/* Frosted variants sit on top of imagery — translucent + backdrop blur. */
.sklad-button--frost-black {
  background-color: var(--ui-950-20);
  color: var(--ui-00);
  backdrop-filter: var(--blur-16);
}
.sklad-button--frost-black:not([disabled]):hover { background-color: var(--ui-950-32); }
.sklad-button--frost-black:not([disabled]):focus {
  background-color: var(--ui-950-32);
  border-color: var(--ui-950-20);
}
.sklad-button--frost-black[disabled] { background-color: var(--ui-950-20); }

.sklad-button--frost-white {
  background-color: var(--ui-00-88);
  color: var(--ui-950);
  backdrop-filter: var(--blur-16);
}
/* Goes fully opaque on hover, so the blur is dropped rather than wasted. */
.sklad-button--frost-white:not([disabled]):hover {
  background-color: var(--ui-00);
  box-shadow: var(--shadow-level-2);
  backdrop-filter: unset;
}
.sklad-button--frost-white:not([disabled]):focus { border-color: var(--ui-200); }

/* --------------------------------------------------------------------------
   LINK
   -------------------------------------------------------------------------- */

.link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xsmall);
  width: fit-content;
  height: fit-content;
  font-weight: var(--font-weight-medium);
  /* Counterpart to the centring on .sklad-button: a text link reads as prose,
     so it start-aligns when it wraps. Stated here so a <button class="link">
     does not inherit the UA's centring and diverge from the <a>. */
  text-align: start;
  color: var(--link-color, var(--ui-950));
  cursor: pointer;
  z-index: 1;
  transition: color var(--duration-medium) var(--ease-out);
}

/* The other half of the promise the comment above makes. A <button class="link">
   still arrives with the UA's chrome — a grey face, a border and padding — so
   without this it reads as a button wearing a link's colour. Stated once here
   rather than a third time beside each use. */
button.link {
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
}

.link:hover { color: var(--link-hover-color, var(--ui-600)); }

/* The underline is painted as a background gradient on the label, not a
   pseudo-element — which is what lets it animate by `background-size` and
   retract to the right on hover rather than sweeping in. */
/* .link__text is the canonical carrier, but plenty of links are their own
   component and never had one — the "View" in the orders table, breadcrumbs,
   the drawer's navigation, product card titles, the mini cart's product names.
   They join the selector list rather than having their markup rewritten: one
   mechanism, one place to change it.

   Each entry points at whatever actually HUGS the text. Where the component
   has an inner span, that is the target; where the element is inline it paints
   on itself; the two that are block-level and span their whole row get
   `width: fit-content` below, because a gradient on a 432px box would draw a
   432px rule under a 72px word.

   `will-change` used to sit here. With ~950 .link__text alone it was already
   asking the compositor for a layer per link; across this list it would be
   thousands, so it is gone — the transition is a background-size tween and
   does not need it. */
.link__text,
.card-product__title > span,
.panel-main-navigation__item__title,
.panel-navigation__footer__nav-link,
.legal__index-link__label,
.breadcrumb__link,
.facets__reset-block,
.woocommerce-button,
.woocommerce-orders-table__cell-order-number a,
.woocommerce-mini-cart-item a,
.mini-cart__title a,
.line-item__infos__title a,
.woocommerce-table__product-name a,
.product-summary__collection a,
.tile__link,
.reset_variations {
  background-image: linear-gradient(
    transparent calc(100% - 1px),
    var(--link-underline-color, var(--ui-200)) 1px
  );
  background-repeat: no-repeat;
  background-size: 0% 100%;
  text-decoration: none;
  transition:
    background-size var(--duration-fast) var(--ease-out),
    color           var(--duration-fast) var(--ease-out);
}

/* Block-level and as wide as its row. */
.panel-navigation__footer__nav-link { width: fit-content; }

/* --- The two link types ----------------------------------------------------
   There are exactly two, and they differ by whether the underline is there
   before you touch it:

     .link--underlined       always underlined  — a link sitting INSIDE a
                             sentence, where the underline is what marks it as
                             a link at all ("Log out", "Edit", "View details")
     .link--underline-hover  underlined on hover — a standalone call to action
                             that already reads as one from its position
                             ("View all", "View collection")

   These were `--underlined` and `--underline`: one letter apart, opposite
   behaviour. The longer name now says what it does.

   There is no third option. --plain used to be one — colour-only, no underline,
   used by the footer menus and the drawer's language switcher — and it has been
   folded into type 2: those are standalone navigation items, so they take the
   underline on hover like every other standalone link.

   The header is the one exception, and it needs no exception: it never used
   .link at all. .header__nav-link and .header__button are its own components.

   --chevron and --uppercase are orthogonal and combine with either type.
   --------------------------------------------------------------------------- */

/* Type 1 — underline shown by default, retracting rightward on hover. */
.link--underlined .link__text {
  /* Was a hard-coded --ui-950. That single value was the only thing stopping a
     text link from working on a dark surface, so it reads the contract now. */
  color: var(--link-color, var(--ui-950));
  background-size: 100% 100%;
}

.link--underlined:hover .link__text {
  color: var(--link-hover-color, var(--ui-950));
  background-size: 0% 100%;
  background-position: right;
}

/* Type 2 — the inverse: no underline until hover, drawing in from the left.
   The component links listed above are all type 2: they are standalone, not
   words inside a sentence. */
.link--underline-hover:hover .link__text,
.card-product__title:hover > span,
.panel-main-navigation__item:hover .panel-main-navigation__item__title,
.panel-navigation__footer__nav-link:hover,
.legal__index-link:hover .legal__index-link__label,
.breadcrumb__link:hover,
.woocommerce-button:hover,
.woocommerce-orders-table__cell-order-number a:hover,
.woocommerce-mini-cart-item a:hover,
.mini-cart__title a:hover,
.line-item__infos__title a:hover,
.woocommerce-table__product-name a:hover,
.product-summary__collection a:hover,
.tile__link:hover,
.reset_variations:hover {
  background-size: 100% 100%;
  background-position: left;
}

/* Arrow slides out and a second slides in — the theme's signature link. */
.link--chevron .link__icon {
  display: inline-block;
  transition: transform var(--duration-medium) var(--ease-out-cubic);
}
.link--chevron:hover .link__icon { transform: translateX(4px); }

/* The mirrored version, for going back. Same rule, opposite direction — the
   chevron leads the label instead of following it. */
.link--chevron-back .link__icon {
  display: inline-block;
  transition: transform var(--duration-medium) var(--ease-out-cubic);
}
.link--chevron-back:hover .link__icon { transform: translateX(-4px); }

.link--uppercase {
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-size: var(--text-xsmall-size);
}

/* --------------------------------------------------------------------------
   DARK SURFACES

   Re-points the link contract once, rather than re-colouring each component
   with its own descendant override — which is what the footer had been doing,
   a new line per component dropped into it. .link already reads --link-color,
   so it needs nothing else. Buttons name a literal colour, so they take the
   --outline-white modifier instead of being silently remapped here.
   -------------------------------------------------------------------------- */

.on-dark {
  --link-color: var(--ui-00);
  --link-hover-color: var(--ui-300);
  --link-underline-color: var(--ui-00-40);

  /* Same hues, lifted for a dark ground — see the note in tokens.css. */
  --status-error: #f64343;
  --status-success: #8fc511;
  --status-muted: var(--ui-300);
}

/* --------------------------------------------------------------------------
   BADGE
   -------------------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 3px var(--space-xxsmall);
  border-radius: var(--space-xxxsmall);
  /* No default fill. The reference's .badge sets none, and the variants each
     supply their own — .badge--grey and .badge--outline-grey deliberately have
     none at all, so a black default turned both into filled chips. */
  background-color: var(--badge-bg, transparent);
  color: var(--badge-color, inherit);
  font-size: var(--text-ui-small-size);
  /* Line-height is 1, not --text-ui-small-lh: the badge is a pill sized to its
     cap height, so the box must not carry the label's leading. Tracking is
     left to .text-ui-small (0.07em) — do not restate it here as --tracking-wide
     (0.06em), which is the eyebrow's value and 0.1px tighter. */
  line-height: 1;
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
}

/* Colour variants, straight from the reference. */
.badge--white       { color: var(--ui-950); background-color: var(--ui-00); }
.badge--black       { color: var(--ui-00);  background-color: var(--ui-800); }
.badge--grey        { color: var(--ui-600); }
.badge--outline-grey{ color: var(--ui-600); border: 1px solid var(--ui-200); }
.badge--ui-100      { background-color: var(--ui-100); color: var(--ui-950); }
.badge--ui-800      { background-color: var(--ui-800); color: var(--ui-00); }
.badge--beige-100   { background-color: var(--beige-100); color: var(--beige-600); }
.badge--lime        { background-color: var(--lime-100);     color: var(--ui-950); }
.badge--lavender    { background-color: var(--lavender-100); color: var(--ui-950); }
.badge--peach       { background-color: var(--peach-100);    color: var(--ui-950); }

/* A price cut is the one badge that is not a tint — it is the only thing on a
   card that is time-limited, so it takes a filled, saturated ground.

   CONTRAST: white on --error-light measures 4.35:1, and this label is 10px
   bold, which WCAG counts as normal text needing 4.5:1. It is 2% of lightness
   short. Left on the token as specified rather than quietly substituted —
   darkening --error-light to rgb(199 81 30) takes it to 4.54:1 if that matters
   more than the token matching elsewhere. */
.badge--sale        { background-color: var(--error-light);   color: var(--ui-00); }

/* Alignment variants — --right is what pushes a badge to the far edge of
   the card overlay. */
.badge--left   { text-align: left; }
.badge--center { text-align: center; }

@media (min-width: 1024px) {
  .badge { padding: var(--space-xxsmall) 5px; }
}

/* --------------------------------------------------------------------------
   FORM FIELDS
   -------------------------------------------------------------------------- */

/* Every field on this site puts its label inside the control.
   -------------------------------------------------------------------------
   This used to be a flex column with the label stacked above the input. That
   costs a line of its own, and across a dozen fields it is a dozen repeats down
   a column that is mostly air. Inside, the rhythm halves without the name going
   away — which is the difference from a placeholder, and the reason not to use
   one: a placeholder disappears the moment you start typing, exactly when you
   might want to check what you are filling in.

   There is no --float modifier. This IS the field. */
.field {
  /* One height for every text control on the site, named once. The footer
     newsletter had it first — 15px of block padding on an 18px line box inside a
     1px border — and everything else was 44. Named here because the floating
     label has to centre on the CONTROL, and the control's height is not
     something the wrapper can be asked for: .field also holds the hint and the
     error, so a percentage measures those too. */
  --field-control-height: 3.125rem;
  position: relative;
  display: block;
}

/* Except when what it wraps is a tick. A checkbox has no room to float a label
   over and nothing to float it off; those rows stay a label beside a box. The
   theme's own checkbox rows use .checkbox__wrapper and never come through here,
   but WooCommerce puts .field on every row it renders, so the guard is real. */
.field:has(> input[type="checkbox"]),
.field:has(> input[type="radio"]),
.field:has(> .woocommerce-input-wrapper > input[type="checkbox"]),
.field:has(> .checkbox__wrapper) {
  display: flex;
  flex-direction: column;
  gap: var(--space-xsmall);
}

/* Woo prints its fields as <p class="form-row"><label>…, and we author ours as
   .field > .field__label. Two markups, one label: listed together so the
   account and checkout forms cannot drift from login and register, which is
   exactly what had happened — theirs were sentence-case 14px while these were
   uppercase 12px. */
.field__label,
.form-row > label,
.woocommerce-form-row > label {
  font-size: var(--text-xsmall-size);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--text-secondary);
}

.input {
  width: 100%;
  /* 15px of block padding on an 18px line box inside a 1px border: 50px.
     Literal because the height is the contract — every control that has to
     stand beside one of these is matched against that number, and a token
     expression would hide it. */
  /* 16px across, not 20. At 20 the value stood further from its own border than
     the type on it is tall, and wider inside than the label floated above it.
     One step down the same scale; the 15px block padding is untouched, so the
     50px height contract above still holds. */
  padding: 15px var(--space-medium);
  /* --ui-200, a step up from --ui-100. On white that moves the outline from
     1.19:1 to 1.41:1 — measured, not eyeballed. Worth knowing where that
     leaves us: WCAG 1.4.11 asks 3:1 of a control's boundary, and nothing in
     this palette reaches it until --ui-500 at 3.95:1, which would read as a
     drawn box rather than a field. This is the step that was asked for and it
     is a real improvement; the standard is a separate decision about how heavy
     the forms should look. */
  border: 1px solid var(--ui-200);
  border-radius: var(--radius-control);
  background-color: var(--ui-00);
  color: var(--ui-800);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  transition:
    box-shadow var(--duration-fast) var(--ease-out),
    color      var(--duration-fast) var(--ease-out);
}

.input::placeholder { color: var(--text-tertiary); }
.input:hover  { box-shadow: var(--shadow-level-1); }
.input:focus  { outline: none; border-color: var(--ui-950); }
.input[aria-invalid="true"] { border-color: var(--error); }

/* --- Floating label ---------------------------------------------------------
   The label lives inside the field and gets out of the way when there is
   something to read.

   A label stacked above its input costs a line of its own and, once a form runs
   to a dozen fields, that line is repeated a dozen times down a column that is
   mostly air. Putting it inside halves the vertical rhythm without taking the
   name away — which is the difference from a placeholder, and the reason not to
   use one: a placeholder is gone the moment you start typing, exactly when you
   might want to check what you are filling in.

   Measured off the reference form rather than guessed: 46px tall, asymmetric
   padding so the shrunk label has somewhere to sit, and the resting label
   vertically centred. Ours keeps our own type, palette and radius — the shape
   is borrowed, the material is not.

   :has() rather than a class toggled by script, so the field is correct before
   any JavaScript runs and stays correct if none does. It is already used in
   eleven places in this file.
   --------------------------------------------------------------------------- */

.field > .field__label,
.field > label {
  position: absolute;
  /* Half the control, not half the wrapper. With a hint under the input, 50%
     of .field is 10px lower than the middle of the box the label belongs to —
     measured on the register form, where "Lozinka" sat visibly below its
     neighbours. .field carries no padding or border, so the control's top edge
     and the wrapper's are the same line and a length is exact. */
  top: calc(var(--field-control-height) / 2);
  left: var(--space-medium);
  transform: translateY(-50%);
  transform-origin: left center;
  /* Sentence case at rest: it is standing in for the placeholder, and an
     uppercase 12px word does not read as one. It becomes the small uppercase
     label this site uses everywhere else once it floats. */
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-tertiary);
  pointer-events: none;
  transition: transform var(--duration-fast) var(--ease-out),
              color     var(--duration-fast) var(--ease-out);
}

/* Focused, or holding something. The placeholder is a single space precisely so
   :placeholder-shown means "empty" — see sklad_float_label_placeholder(). */
/* Descendant, not child. WooCommerce wraps its controls in a span more often
   than not — .woocommerce-input-wrapper around checkout fields, .password-input
   around anything with a show/hide eye — and a child combinator here quietly
   skipped every one of them. Measured on the account form: the two password
   fields kept symmetric padding and an unfloated label while their neighbours
   floated. A select is included unconditionally because it always holds a
   value, so its label is always up. */
.field:focus-within > .field__label,
.field:focus-within > label,
.field:has(.input:not(:placeholder-shown)) > .field__label,
.field:has(.input:not(:placeholder-shown)) > label,
.field:has(select) > .field__label,
.field:has(select) > label {
  transform: translateY(calc(-50% - var(--space-small-plus))) scale(0.8);
  color: var(--text-secondary);
}

/* The room the floated label needs, bought at the top of the field only. */
/* Same 50px, with the room the floated label needs bought at the top. 23 and 7
   rather than 15 and 15 — the sum is what keeps the height, the split is what
   keeps the label off the value. */
.field .input,
.field select {
  padding-block: 23px 7px;
}

.field textarea.input {
  padding-block: 23px var(--space-small);
}

/* The select is drawn by us, not by the browser.
   -------------------------------------------------------------------------
   With appearance: auto the UA adds inset padding of its own on top of ours, so
   the value sat further right than the value in the text field beside it while
   both declared the same padding-left. It also imposes its own content height:
   the select measured 53px against the input's 50, in a row where that height
   is the contract.

   appearance: none drops both, and the arrow becomes an image we place — 16px
   wide, 16px from the edge, the same gap the value keeps on the left. The
   padding-inline-end is that gap plus the arrow plus eight, so the longest
   country name stops before the arrow rather than under it. */
.field select {
  appearance: none;
  padding-inline-end: 40px;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none'%3e%3cpath stroke='%231e1e1e' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='m4 6.5 4 4 4-4'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right var(--space-medium) center;
}

/* The hint used to be spaced by the wrapper's flex gap, which the block layout
   above no longer provides. */
.field > .field__hint,
.field > .field__error { display: block; margin-block-start: var(--space-xsmall); }

.field__error { font-size: var(--text-xsmall-size); color: var(--error); }
.field__hint  { font-size: var(--text-xsmall-size); color: var(--ui-500); }

/* WooCommerce's password-strength meter injects itself straight after the
   password input — inside our .field — with no classes of ours on it, so the
   Woo selector is the component here. Untouched it rendered as unstyled body
   text saying the password was too weak, while the submit it had just disabled
   still looked pressable; the pair read as "nothing happens". */
.woocommerce-password-strength,
.woocommerce-password-hint {
  display: block;
  margin-block-start: var(--space-xsmall);
  font-size: var(--text-xsmall-size);
  line-height: var(--text-xsmall-lh);
}

.woocommerce-password-hint { color: var(--ui-500); }
.woocommerce-password-strength.short,
.woocommerce-password-strength.bad  { color: var(--error); }
/* "good" is strength 3, which is the threshold Woo enforces — the submit is
   live at that point, so it is not an error colour. */
.woocommerce-password-strength.good { color: var(--status-muted); }
.woocommerce-password-strength.strong { color: var(--status-success); }

/* The meter says the same thing the static hint does, only live. One or the
   other, never both stacked — but the meter is created once and then merely
   hidden when the field is emptied, so keying off its presence alone left an
   empty field with no hint at all. Key off the field having content instead. */
.field:has(.input:not(:placeholder-shown)):has(.woocommerce-password-strength)
  > .field__hint { display: none; }

/* Input paired with an inline submit button. */
.input-group { display: flex; }
.input-group .input { border-inline-end: none; }

/* --- Select -------------------------------------------------------------
   Not a bordered field like .input — it is a borderless inline control that
   reads as a text label with a caret, used for sorting and for the mobile
   collection switcher. The caret is a background SVG rather than an element
   so the control stays a bare <select> and keeps native keyboard behaviour.
   ------------------------------------------------------------------------ */

.sklad-select {
  width: 100%;
  padding-left: var(--space-medium);
  border: none;
  appearance: none;
  color: currentcolor;
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  font-weight: var(--font-weight-medium);
  text-align: left;

  /* The control takes the surface it sits on. Without this it takes the UA's
     colour for a <select> instead, and appearance:none does not clear that:
     Chrome's is white, which happened to match the button behind it, and iOS
     Safari's is a light grey — so on a phone the category dropdown showed a
     grey rectangle inset inside its own white pill. Reproduced by feeding
     Chrome the same grey, and gone when the colour is stated here. */
  background-color: transparent;

  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none'%3e%3cpath fill='currentColor' d='m11 10-3 3-3-3zM5 6l3-3 3 3z'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: left 0 center;

  /* Resting at 70% is what makes it read as secondary next to the filter
     button; hover is the only thing that brings it to full strength. */
  opacity: 0.7;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.sklad-select--icon-right {
  padding-left: 0;
  padding-right: var(--space-medium);
  background-position: right 0 center;
}

.sklad-select:not([disabled], [aria-disabled]) { cursor: pointer; }
.sklad-select:hover:not([disabled], [aria-disabled]) { opacity: 1; }

.sklad-select[disabled],
.sklad-select[aria-disabled] { opacity: 0.5; cursor: not-allowed; }

.sklad-select__label { display: inline-block; margin-bottom: var(--space-xsmall); }

/* --- Checkbox and radio ---------------------------------------------------
   One element covers both: the only differences are border-radius and what
   the ::after does. Both draw from `currentcolor`, so setting a colour on
   .checkbox__wrapper moves the box AND its label together — which is how the
   whole row highlights on hover without a second selector.
   ------------------------------------------------------------------------- */

/* Every control the site draws itself is below; these are the ones it does
   not. WooCommerce generates its own checkboxes and radios — create account,
   the legal-entity toggle, payment method, billing address, terms — and they
   arrived in the browser's blue while the delivery radios two sections above
   were black, because that one group had an accent-color of its own and
   nothing else did.
   
   Stated once, for both types, rather than per screen: a control that turns
   up in markup this theme does not own still belongs to the same page. The
   custom-drawn ones are untouched by it — accent-color paints the UA widget,
   and appearance:none means there is no UA widget to paint. */
input[type="checkbox"],
input[type="radio"] { accent-color: var(--ui-950); }

.checkbox {
  --checkbox-size: 14px;
  position: relative;
  width: var(--checkbox-size);
  height: var(--checkbox-size);
  flex-shrink: 0;
  appearance: none;
  border: 1px solid currentcolor;
  border-radius: 3px;
  color: currentcolor;
  overflow: hidden;
  transition: color var(--duration-x-fast) var(--ease-out);
}

/* Two stacked layers: ::before is the fill, ::after is the glyph. Both scale
   from 0, so checking animates rather than snapping. */
.checkbox::before,
.checkbox::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: scale(0);
  transition: transform var(--duration-x-fast) var(--ease-out);
}

.checkbox::before { background-color: currentcolor; }

.checkbox:checked { color: var(--checkbox-color-active); }
.checkbox:checked + label { color: var(--checkbox-color-active); }

.checkbox[type="checkbox"]::after {
  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='transparent' viewBox='0 0 14 14'%3e%3cpath stroke='white' stroke-linecap='square' stroke-linejoin='round' stroke-width='1.4' d='M3.745 7 6.09 9.344l4.687-4.688'/%3e%3c/svg%3e") center no-repeat;
}

.checkbox[type="checkbox"]:checked::before,
.checkbox[type="checkbox"]:checked::after { transform: scale(1); }

.checkbox[type="radio"],
.checkbox[type="radio"]::before,
.checkbox[type="radio"]::after { border-radius: 50%; }

/* Everything above describes an <input>: 14px square, appearance:none, its own
   border, overflow hidden. WooCommerce puts the same class name on the <label>
   that wraps the input together with its text — so the label became a 14px box
   that clipped its own words, and "Napravi nalog?" and the terms line rendered
   as a bare tick with nothing beside it. A label wearing this class is a row,
   not a box. */
label.checkbox {
  --checkbox-size: auto;
  display: flex;
  align-items: center;
  gap: var(--space-small);
  width: auto;
  height: auto;
  appearance: none;
  border: 0;
  border-radius: 0;
  overflow: visible;
}

label.checkbox::before,
label.checkbox::after { content: none; }

/* WooCommerce hands these labels the field label class as well, which is the
   10px uppercase treatment meant for the word above an input. On a checkbox
   the label is the sentence itself — "Prihvatam uslove korišćenja" — and it
   was setting in micro caps next to a tick while the shipping toggle beside
   it sat at 20px. They read as sentences. */
label.checkbox.field__label,
label.woocommerce-form__label-for-checkbox {
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  text-transform: none;
  letter-spacing: normal;
  color: var(--ui-800);
}

/* The radio's dot is a white disc over the fill, not a hole in it. */
.checkbox[type="radio"]::after { background-color: var(--ui-00); z-index: 1; }
.checkbox[type="radio"]:checked::before { transform: scale(1); }
.checkbox[type="radio"]:checked::after { transform: scale(0.5); }

.checkbox[disabled],
.checkbox[readonly] { color: var(--checkbox-color-disabled); cursor: not-allowed; }

.checkbox[disabled] + label,
.checkbox[readonly] + label { color: var(--checkbox-color-disabled); cursor: not-allowed; }

.checkbox__wrapper {
  display: flex;
  align-items: center;
  /* The box itself is 14px, which is the mark, not the target. The label wraps
     the input, so the whole row is clickable — but at the label's natural 18px
     it was a wide, short strip. 24px is the WCAG 2.5.8 minimum and costs
     nothing visually, because the box and text stay centred in it. */
  min-height: 24px;
  /* The box and its text were touching wherever the text was a plain label
     rather than a .checkbox__label — the register form's marketing row, the
     checkout's four ticks. The gap belongs on the row, not on one of the two
     things it separates. */
  gap: var(--space-small);
  color: var(--checkbox-color-default);
}

.checkbox__wrapper:hover { color: var(--checkbox-color-active); }

.checkbox__label {
  display: flex;
  align-items: center;
  color: currentcolor;
  transition: color var(--duration-x-fast) var(--ease-out);
}

.checkbox__label > * + * { margin-left: var(--space-small); }

/* --- Textarea -------------------------------------------------------------
   Inherits the .input box; only the resize behaviour and height differ. */
.textarea {
  min-height: calc(var(--space-xxlarge-plus) + var(--space-large));
  resize: vertical;
}

/* --- Focus without zoom -----------------------------------------------------
   iOS Safari zooms the whole page onto any focused control set below 16px,
   and the viewport is left alone on purpose — maximum-scale would stop the
   zoom by taking pinch-zoom away from everyone. So the controls themselves
   go to 16px below the breakpoint; the 14px scale is untouched on desktop,
   where no browser zooms a field.

   Literal 16px, not a token: the number is Safari's threshold, not a step on
   our type scale. The line-heights on these controls are fixed rems, so the
   50px field-height contract holds; the quantity input sits in a fixed-height
   box, so nothing moves but the glyphs. The search sheet's field is already
   display-size and its two-class selector outranks this one. */
@media (max-width: 1023px) {
  .input,
  .sklad-select,
  .field select,
  .sklad-quantity input.sklad-quantity__value,
  input.qty {
    font-size: 16px;
  }
}

/* --------------------------------------------------------------------------
   HEADER

   Three zones on the 24-column grid — nav left, logo CENTRED, nav + icons
   right (9 / 6 / 9 on desktop, 6 / 6 / 6 below). Theming runs through four
   custom properties that children read, so the transparent and solid states
   re-point tokens instead of restating colours.

   The header is `position: sticky` — the browser pins it; JS only swaps the
   colour state via .is-sticky / .is-transparent.
   -------------------------------------------------------------------------- */

.header {
  --background-color: var(--ui-00);
  --border-color: var(--ui-100);
  --link-color: var(--ui-950);
  --link-color-reverse: var(--ui-00);
  --link-hover-color: var(--ui-600);
  --highlight-background-color: var(--beige-100-50);
  --highlight-hover-background-color: var(--beige-100);

  /* Sticky, not JS-positioned. The browser pins it the instant its own top
     edge reaches 0, which is scroll position = announcement-bar height — not a
     number JS has to know. The old approach fixed the wrapper past a hardcoded
     threshold of --header-height, so once the bar pushed the header down to
     y=32 it would scroll 24px off-screen and then snap back. */
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  height: var(--header-height);
}

.header__wrapper {
  height: 100%;
  width: 100%;
  background: var(--background-color);
  border-bottom: 1px solid var(--border-color);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    background   var(--duration-fast) var(--ease-out);
}

/* Everything inside inherits the swap at the same rate, so the header reads as
   one element inverting rather than a background changing under static text. */
.header__wrapper * { transition: color var(--duration-fast) var(--ease-out); }

/* .is-sticky no longer moves anything. It marks that the header has pinned, so
   .is-transparent can be lifted and the colours swap back to solid. It carries
   no styling of its own — the reference gives the pinned header no shadow, only
   the wrapper's border-bottom — but it stays as a hook. */

.header__grid {
  --padding-inline: var(--gutters-side);
  height: 100%;
}

.header__left,
.header__right {
  grid-column: span 6;
  display: flex;
  align-items: center;
}

/* The left zone carries the desktop nav from 1024 up. Below that the nav is
   hidden and the burger sits at the end of the icon row on the right, so on
   mobile this zone is empty — a balancing track for the centred wordmark. */
.header__left { display: flex; }

.header__center {
  grid-column: span 6;
  display: flex;
  align-items: center;
}

.header__right {
  justify-content: flex-end;
  gap: var(--space-large);
}

/* Below 1024 the header stops using the page grid and sizes itself to its
   contents. The burger lives at the END of the icon row (search, wishlist,
   cart, burger — the side the drawer slides in from), so the left zone is
   empty and collapses to 0. The logo sits flush left at the gutter, with the
   icon row anchored to the right; the flexible track between them absorbs all
   slack. */
@media (max-width: 1023px) {
  .header__grid {
    grid-template-columns: 0 auto 1fr;
    /* The page gutter is for columns of content. Here it is holding a button
       away from a wordmark, and the button already carries its own padding. */
    column-gap: var(--space-xxsmall);
  }

  .header__left,
  .header__center,
  .header__right { grid-column: auto; }

  .header__center {
    justify-self: start;
    margin-left: calc(-1 * var(--space-xxsmall));
  }
}

@media (min-width: 1024px) {
  .header__left,
  .header__right { grid-column: span 9; }
  .header__center { justify-content: center; }
  .header__right { gap: var(--space-medium); }
}

@media (min-width: 1280px) {
  .header__right { gap: var(--space-large); }
}

/* Structural: the header sits over the next section rather than above it.
   Kept separate from the .is-transparent visual state, which JS toggles. */
.header--overlay + * { margin-block-start: calc(var(--header-height) * -1); }

/* On the pages whose hero the header floats over, the announcement bar and
   the header are both glass, so the hero has to reach the very top of the
   page — otherwise the bar's frost sits on the body's white and the two
   strips read as different materials. Keyed to the static
   data-header-transparent attribute rather than .is-transparent: this is
   layout, and JS lifts the class the moment the header pins. The
   :not([hidden]) leg gives the space back when the bar is dismissed, the
   same reflow dismissing it causes today. */
.announcement-bar:not([hidden]) + .header--overlay[data-header-transparent] + * {
  margin-block-start: calc((var(--header-height) + var(--announcement-bar-height)) * -1);
}

/* Over a hero: no fill, a 20%-white hairline, and white links. */
.header.is-transparent:not(.is-search-open) {
  --background-color: transparent;
  --border-color: var(--ui-00-20);
  --link-color-reverse: var(--ui-950);
}

/* The transparent header stays truly transparent. It briefly wore the frost
   button's glass here; the owner pulled it — over the hero only the
   announcement bar is glass, and the header is nothing but its white marks. */
.header.is-transparent:not(.is-search-open) .header__nav-link.is-highlighted {
  --highlight-background-color: var(--ui-950-6);
  backdrop-filter: var(--blur-16);
}

.header.is-transparent:not(.is-search-open) .header__nav-link,
.header.is-transparent:not(.is-search-open) .header__button,
.header.is-transparent:not(.is-search-open) .header__logo { --link-color: var(--ui-00); }

/* --- Over a LIGHT hero ------------------------------------------------------
   Transparent means white links, which vanish over a pale image. Measured on
   the homepage hero: white text scored 2.47:1 on average and 1.0:1 against its
   brightest areas — invisible, not merely low.

   The reference solves this with .is-black-and-white, which flips the links to
   --link-color-reverse (which .is-transparent has already set to --ui-950) and
   adds a hairline so the header still has an edge. It was never ported; this
   is that rule, extended to the left nav and the logo, which the reference
   leaves white because its own light heroes have dark subjects on the left.

   Add the class alongside data-header-transparent on any page whose hero is
   pale. A scrim was the alternative and was rejected: to bring white to 4.5:1
   over a pure-white pixel needs ~82% black, which is a black bar, not a scrim.
   ---------------------------------------------------------------------------- */

.header.is-black-and-white.is-transparent:not(.is-search-open) .header__nav-link,
.header.is-black-and-white.is-transparent:not(.is-search-open) .header__button,
.header.is-black-and-white.is-transparent:not(.is-search-open) .header__logo {
  --link-color: var(--link-color-reverse);
}

.header.is-black-and-white.is-transparent:not(.is-search-open) .header__nav-link:not(.is-highlighted):hover,
.header.is-black-and-white.is-transparent:not(.is-search-open) .header__button:not(.is-highlighted):hover {
  --link-color: var(--ui-600);
}

.header.is-black-and-white.is-transparent:not(.is-search-open) .header__count {
  color: var(--link-color-reverse);
}

.header.is-black-and-white.is-transparent:not(.is-search-open) .header__wrapper {
  position: relative;
  border-bottom: 1px solid rgb(192 192 192 / 20%);
}

.header__nav { display: none; }

@media (min-width: 1024px) { .header__nav { display: flex; } }

/* Spacing between links belongs to the LIST, not to the links — the link
   itself carries no padding at all. It narrows at 1024 and widens again
   at 1280, where there is room. */
.header__nav-list {
  display: flex;
  align-items: center;
  gap: var(--space-large);
}

@media (min-width: 1024px) { .header__nav-list { gap: var(--space-medium); } }
@media (min-width: 1280px) { .header__nav-list { gap: var(--space-large); } }

.header__nav-item {
  position: relative;
  display: inline-flex;
}

/* Primary nav is sentence case — uppercase is for buttons and small labels. */
.header__nav-link {
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  font-weight: var(--font-weight-medium);
  color: var(--link-color);
  white-space: nowrap;
  transition:
    color var(--duration-fast) var(--ease-out),
    background var(--duration-fast) var(--ease-out);
}

/* Hover was scoped to :not(.is-transparent), so an overlay header had no
   hover state at all — the links simply did not respond. It applies in both
   states now; only the target colour differs, and on a transparent header
   that is white stepping back to 80%, since there is no darker white. */
.header__nav-link:not(.is-highlighted):hover {
  color: var(--link-hover-color);
}

.header.is-transparent:not(.is-search-open) { --link-hover-color: var(--ui-00-80); }

/* The desktop menu opener: the "Shop" link carries a two-rule hamburger that
   splits apart on hover. The negative margin pulls the icon into the gutter so
   the LABEL still lines up with where it would sit without one. */
.header__nav-link--menu {
  --icon-width-with-offset: calc(var(--space-medium-plus) + var(--space-xsmall));
  margin-left: calc(var(--icon-width-with-offset) * -1);
  display: inline-flex;
  align-items: center;
}

.header__nav-link--menu .link__text {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xsmall);
}

.header__nav-link-icon {
  position: relative;
  width: var(--space-medium-plus);
  height: var(--space-medium-plus);
  display: inline-flex;
}

/* Two 1px rules rather than an icon file. */
.header__nav-link-icon::before,
.header__nav-link-icon::after {
  content: "";
  position: absolute;
  left: 4px;
  right: 4px;
  height: 1px;
  background: var(--link-color);
  transition:
    transform var(--duration-x-fast) var(--ease-out),
    background var(--duration-fast) var(--ease-out);
}

.header__nav-link-icon::before { top: 7px; transform: translateY(0); }
.header__nav-link-icon::after  { bottom: 7px; transform: translateY(0); }

/* They fly apart on hover. The mobile burger is the SAME element, not a second
   icon — it used to be Lucide's three-line #i-menu, which sat next to a
   two-rule desktop burger and read as a different mark at a different weight. */
.header__nav-link--menu:hover .header__nav-link-icon::before,
.header__button--menu:hover .header__nav-link-icon::before { transform: translateY(-100%); }
.header__nav-link--menu:hover .header__nav-link-icon::after,
.header__button--menu:hover .header__nav-link-icon::after  { transform: translateY(100%); }

.header__nav-link--menu:hover .header__nav-link-icon::before,
.header__nav-link--menu:hover .header__nav-link-icon::after,
.header__button--menu:hover .header__nav-link-icon::before,
.header__button--menu:hover .header__nav-link-icon::after {
  background: var(--link-hover-color);
}

/* A pill — the only nav link that has padding of its own. */
.header__nav-link.is-highlighted {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-xxsmall) var(--space-small-plus);
  background: var(--highlight-background-color);
  border-radius: var(--space-xsmall);
}

.header:not(.is-transparent) .header__nav-link.is-highlighted:hover {
  background: var(--highlight-hover-background-color);
}

.header__icons {
  display: flex;
  align-items: center;
  gap: var(--space-xxsmall);
}

.header__logo {
  --header-logo-height: 16px;
  position: relative;
  display: inline-flex;
  align-items: center;
  z-index: 1;
  color: var(--link-color);
  transition: color var(--duration-fast) var(--ease-out);
}

/* The wordmark is inlined rather than an <img> so its paths can take
   `currentColor` — .header.is-transparent re-points --link-color to white, and
   a linked file could not follow that. Height drives it; width follows the
   369:96 viewBox. */
.header__logo-svg {
  display: block;
  height: var(--header-logo-height);
  width: auto;
}

@media (min-width: 1024px) {
  .header__logo { --header-logo-height: 18px; }
}

.header__button {
  /* Positioning context for .header__count, which used to rely on an inline
     style="position:relative" sitting in the markup on one button only. */
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  color: var(--link-color);
  transition: color var(--duration-fast) var(--ease-out);
}

.header__button:hover { color: var(--link-hover-color); }

/* The burger is the last icon on mobile and disappears once the desktop nav
   appears at 1024 — the same breakpoint .header__left uses, so the two can
   never both be hidden. */
.header__button--menu { display: inline-flex; }

/* The rules paint from --link-color so they follow the transparent-header
   state exactly like the desktop burger does. */
.header__button--menu .header__nav-link-icon::before,
.header__button--menu .header__nav-link-icon::after { background: currentColor; }

/* Wider on its own, without touching the desktop burger, which is the same
   element at a smaller size beside a word.

   The mark was 12px of drawn line: the icon box is 20px and the rules are inset
   4px at each end, which reads as a small glyph adrift in a large tap target.
   Dropping the inset takes it to the full 20px of the box — the box itself is
   left alone, since 24px overshot and this is the width the icon was drawn at.
   The 36px button around it is untouched: that is the tap target, and it is
   already the smallest a target should be. */
.header__button--menu .header__nav-link-icon::before,
.header__button--menu .header__nav-link-icon::after {
  left: 0;
  right: 0;
}

@media (min-width: 1024px) {
  .header__button--menu { display: none; }
}

/* The account icon leaves the row below 1024 — the mobile header keeps
   search, wishlist and cart beside the burger. The destination is not lost:
   the drawer's footer carries the "Moj nalog" link. Desktop keeps the icon. */
@media (max-width: 1023px) {
  .header__button--account { display: none; }
}

/* Both counters, on the bag and on the heart. Anchored to the button's own
   corner rather than left at the span's static position and nudged there by a
   transform — that combination meant the badge's placement depended on where
   the inline span happened to land after the icon, which is why it sat a
   little off. 2px in from the corner puts it on the glyph rather than hanging
   off the button's edge. */
.header__count {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  display: grid;
  place-items: center;
  padding-inline: 4px;
  border-radius: var(--radius-pill);
  background-color: var(--link-color);
  color: var(--background-color, var(--ui-00));
  font-size: var(--text-ui-small-size);
}

/* An empty basket has nothing to announce, so the badge is not drawn at all
   rather than drawn saying nothing. */
.header__count[data-count="0"] { display: none; }

/* On mobile the badge says only *that* there is something, not how much: at
   this size the digit is smaller than the dot it sits in and the count is one
   tap away in the drawer. The number stays in the button's aria-label, so it is
   still announced. */
@media (max-width: 1023px) {
  .header__count {
    top: 4px;
    right: 4px;
    min-width: 0;
    width: 7px;
    height: 7px;
    padding-inline: 0;
    /* The digits are still in the DOM for the fragment to replace; this hides
       them without removing them. */
    font-size: 0;
    line-height: 0;
    overflow: hidden;
    text-indent: -9999px;
  }
}

.header.is-transparent:not(.is-search-open) .header__count {
  background-color: var(--ui-00);
  color: var(--ui-950);
}

/* --------------------------------------------------------------------------
   ANNOUNCEMENT BAR
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   ANNOUNCEMENT BAR — a thin strip above the header.

   Three zones: a slide counter, the rotating message, and a mirrored counter
   so the message stays optically centred rather than being pushed off-centre
   by a single counter. Slides cross-fade in place — they are stacked
   absolutely inside the viewport, so the bar's height never moves.

   Height is a token because other things have to clear it.
   -------------------------------------------------------------------------- */

/* `display: flex` on the class beats the UA's `[hidden] { display: none }`, so
   the dismissed bar would keep its 32px. State it explicitly. */
.announcement-bar[hidden] { display: none; }

.announcement-bar {
  position: relative;
  /* Pages with a transparent header pull their hero up underneath the bar
     (see the .header--overlay rules), and the hero's own layers reach z2.
     Without a level of its own the bar — earlier in the DOM — would paint
     under the photograph it is supposed to frost. */
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-medium);
  height: var(--announcement-bar-height);
  padding-inline: var(--gutters-side);
}

.announcement-bar__viewport {
  position: relative;
  flex: 1 1 auto;
  height: 100%;
  min-width: 0;
}

.announcement-bar__text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-family: var(--font-sans-serif);
  font-size: var(--text-xsmall-size);
  line-height: var(--text-xsmall-lh);
  font-weight: var(--font-weight-regular);

  /* A flex container makes every text run and every link its own item and
     throws away the whitespace between them — the contact line rendered as
     "Pomoć pri izboru?info@skladliving.comili+381 63 7200 820". The spaces are
     in the markup; this puts one back between the items. */
  column-gap: 0.25em;

  /* Every slide is in the DOM at once; only the active one is visible and
     reachable. aria-hidden on the rest keeps them out of the a11y tree. */
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-medium) var(--ease-out);
}

.announcement-bar__text.is-active { opacity: 1; pointer-events: auto; }

/* .link paints itself --ui-950 by design; on the bar that is near-invisible
   against the glass. The bar's own colour is already correct for both the lime
   and the frosted state, so the link takes it rather than a second pair of
   values that would have to be kept in step with them. --link-underline-color
   is part of the same contract and was missing here: without it the rule
   falls back to --ui-200, a pale grey that all but disappears against the
   lime tint — which is why the message read as unlinked even once .link was
   applied. */
.announcement-bar__text .link {
  --link-color: currentColor;
  --link-hover-color: currentColor;
  --link-underline-color: currentColor;
}

/* On a phone the row can still shatter — .announcement-bar__text is a flex
   row, so each text run and each link is its own flex item, and a message
   that cannot fit on one line comes apart at every item boundary rather than
   wrapping as a whole. flex-wrap keeps that as a last resort; the font-size
   step below is what actually keeps ordinary messages on one line so the
   fixed height holds. See tokens.css for why the height itself dropped back
   to a single line's worth.

   Centring used to be attempted with padding-inline-start on
   .announcement-bar__viewport, reserving the counter's old width against the
   close button on the right. Two things were wrong with it: it measured only
   the close button (--announcement-bar-side) and not the flex `gap` next to
   it, so the reservation undercounted by --space-medium; and .announcement-
   bar__text is position:absolute with inset:0 inside that viewport, so it is
   sized against the viewport's PADDING box and the padding never reached it
   at all — verified via getBoundingClientRect, where the text's rect matched
   the viewport's exactly regardless of the padding. The message sat 24px left
   of the bar's true centre on a 375px screen. Moving the reservation onto
   .announcement-bar__text itself (which does apply its own padding to its own
   content) and sizing it to the close button plus the gap fixes both. */
@media (max-width: 1023px) {
  .announcement-bar {
    --announcement-bar-side: 2rem;
  }

  .announcement-bar__counter { display: none; }

  .announcement-bar__text {
    flex-wrap: wrap;
    align-content: center;
    font-size: var(--text-ui-small-size);
    line-height: var(--text-ui-small-lh);
    padding-inline-start: calc(var(--announcement-bar-side) + var(--space-medium));
  }
}

/* The counter and the close button flank the message. They reserve the SAME
   width so the message stays optically centred — that job used to be done by a
   mirrored counter on the right; now the close button sits there instead, and
   the two sides would not otherwise balance. */
.announcement-bar__counter,
.announcement-bar__close {
  flex: 0 0 auto;
  min-width: var(--announcement-bar-side, 3.25rem);
  font-family: var(--font-sans-serif);
  font-size: var(--text-xsmall-size);
  line-height: var(--text-xsmall-lh);
  color: inherit;
}

.announcement-bar__counter {
  /* Tabular figures so the bar does not twitch as the index changes. */
  font-variant-numeric: tabular-nums;
  text-align: left;
}

.announcement-bar__close {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  align-self: stretch;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.announcement-bar__close:hover,
.announcement-bar__close:focus-visible { opacity: 1; }

/* The glyph is small; the button is not. The hit area fills the bar's height
   and reaches the gutter, so it is comfortably tappable without the mark
   itself growing. */
.announcement-bar__close::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  width: calc(var(--gutters-side) + var(--announcement-bar-side, 3.25rem));
}

/* Variants — ported from the reference, plus the lime tint. */
.announcement-bar--lime { background-color: var(--lime-100); }
.announcement-bar--lime .announcement-bar__text,
.announcement-bar--lime .announcement-bar__counter { color: var(--ui-800); }

/* The site's own bar is glass, not paint — the model is fermliving.com,
   whose strip is a 15% tint over an 18px blur.

   Same glass as .sklad-button--frost-black: a black tint over --blur-16
   with --ui-00 on it, at 32% — close to the button's own 20%, and light
   enough that the photograph still reads through the strip.

   This is a deliberate trade, made by the client, and the numbers behind it
   should not be rediscovered from scratch next time. The simulation blurs
   the band at 16px, composites the tint, and takes the WORST 8px block
   rather than the average, on all four pages whose header is transparent.
   Text is 12px, so the AA line is 4.5:1.

                    home   o nama   proizvedeno   pomoć     worst
     black 32%      3.10      —         3.82       6.51      3.10   <- here
     black 40%      3.89      —         4.74       7.73      3.89
     black 50%      5.28    11.36       6.30       9.60      5.28
     black 60%      7.33    13.49       8.49      11.88      7.33

   So the strip is under AA on the two brightest heroes and comfortably over
   it on the other two. The bar carries a marketing line that is repeated in
   the page below it, never the only place something is said, and it is
   dismissible — which is the ground the trade stands on. Do not move body
   copy or anything load-bearing into it at this tint.

   Blur offers no way out: 16px and 24px measure identically, and even 72px
   only lifts 40% to 4.26. White was measured too, since the bar was once 40%
   white with dark text — cheaper on a bright hero (4.88 on the homepage)
   but worse on a dark one (3.92 on proizvedeno-u-srbiji), because whitening
   a dark hero is what strands dark text. Only 80% white clears every page,
   and at 80% nothing of the photograph is left.

   If this ever has to clear AA, 50% is the lightest tint that does it on
   every current hero. Re-run the simulation when a hero image changes.

   The solid lime above stays as the fallback: without backdrop-filter a tint
   over a photograph is a mist with unreadable text in it. */
/* Glass only where there is a photograph to see through it: the bar turns
   frosted solely when it stands directly above a transparent header, i.e. a
   hero page. Everywhere else — shop, cart, content pages on white — it keeps
   its own lime. Browsers without :has() simply keep lime everywhere, which is
   the safe end of the degradation. */
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .announcement-bar--lime:has(+ .header--overlay[data-header-transparent]) {
    background-color: var(--ui-950-32);
    color: var(--ui-00);
    -webkit-backdrop-filter: var(--blur-16);
    backdrop-filter: var(--blur-16);
  }

  /* The bar's own parts carry their colour, so the pane going dark is not
     enough on its own — .announcement-bar--lime sets --ui-800 on both a few
     lines up, and that is what would have stayed. */
  .announcement-bar--lime:has(+ .header--overlay[data-header-transparent]) .announcement-bar__text,
  .announcement-bar--lime:has(+ .header--overlay[data-header-transparent]) .announcement-bar__counter,
  .announcement-bar--lime:has(+ .header--overlay[data-header-transparent]) .announcement-bar__close {
    color: var(--ui-00);
  }
}

.announcement-bar--beige { background-color: var(--beige-100); }
.announcement-bar--beige .announcement-bar__text,
.announcement-bar--beige .announcement-bar__counter { color: var(--ui-800); }

.announcement-bar--black-transparent {
  background-color: var(--ui-950-20);
  backdrop-filter: var(--blur-16);
}
.announcement-bar--black-transparent .announcement-bar__text,
.announcement-bar--black-transparent .announcement-bar__counter { color: var(--ui-00-88); }

.announcement-bar--white-transparent {
  background-color: var(--ui-00-20);
  backdrop-filter: var(--blur-16);
}
.announcement-bar--white-transparent .announcement-bar__text,
.announcement-bar--white-transparent .announcement-bar__counter { color: var(--ui-800); }

/* --------------------------------------------------------------------------
   HERO
   Full-bleed media with text overlaid. `--hero-min-height` controls size.
   -------------------------------------------------------------------------- */

/* Heroes are sized by aspect ratio, never by viewport height — the ratios are
   struck against the 1448px base design viewport (see --base-design-viewport),
   so the crop stays as art-directed at every width instead of stretching with
   the browser window. */
.hero {
  position: relative;
  display: grid;
  aspect-ratio: 392 / 620;
  overflow: hidden;
  isolation: isolate;
}

@media (min-width: 1024px) {
  .hero { aspect-ratio: 1448 / 780; }
}

/* The homepage billboard is measured against the screen, not against a ratio.

   At 85svh the section stops short of the fold on purpose: what shows beneath
   it is the top of the next section, which is the only thing that tells a
   visitor the page continues. A hero that fills the screen exactly reads as the
   whole page. Same figure at every width — the reason for it does not change
   with the viewport.

   svh rather than vh: on a phone vh is the height with the browser chrome
   retracted, so a hero sized in vh is taller than the screen until you scroll,
   which is the opposite of the point.

   Both axes are stated, so the ratio above has nothing left to decide here.
   That matters: given only a ratio and a height, the box keeps the ratio by
   taking width off instead — 200px of it at 1440, a full-bleed hero with a
   white margin down one side. */
.hero--billboard {
  aspect-ratio: auto;
  block-size: 85svh;
  inline-size: 100%;
}

/* Centred, and sitting on the bottom edge: when the frame is shorter than the
   photograph, the crop is taken off the top rather than off both ends, so the
   floor stays where it was drawn and the furniture keeps standing on it. */
.hero__media img,
.hero__media video { object-position: 50% 100%; }

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -1;
}

.hero__media img,
.hero__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Scrim so overlaid text stays legible over any photograph. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    var(--ui-950-32) 0%,
    transparent 45%
  );
}

/* Top scrim, ported from the reference. Distinct from .hero__media::after
   above: that one darkens the FOOT of the hero so bottom-aligned copy stays
   readable, this one darkens the HEAD so the transparent header does.

   The sixteen stops are not decoration — a two-stop ramp bands visibly across
   500px of near-black, so the reference eases the alpha curve by hand. Kept
   verbatim. Relative colour syntax means it tracks --ui-950 rather than
   pinning a hex; where it is unsupported the whole gradient drops out, which
   is harmless at 12% opacity. */
.hero--has-media::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  z-index: 1;
  height: 33%;
  min-height: 320px;
  pointer-events: none;
  opacity: 0.12;
  background: linear-gradient(
    0deg,
    rgb(from var(--ui-950) r g b / 0%) 0%,
    rgb(from var(--ui-950) r g b / 1%) 6.67%,
    rgb(from var(--ui-950) r g b / 4%) 13.33%,
    rgb(from var(--ui-950) r g b / 8%) 20%,
    rgb(from var(--ui-950) r g b / 15%) 26.67%,
    rgb(from var(--ui-950) r g b / 23%) 33.33%,
    rgb(from var(--ui-950) r g b / 33%) 40%,
    rgb(from var(--ui-950) r g b / 44%) 46.67%,
    rgb(from var(--ui-950) r g b / 56%) 53.33%,
    rgb(from var(--ui-950) r g b / 67%) 60%,
    rgb(from var(--ui-950) r g b / 77%) 66.67%,
    rgb(from var(--ui-950) r g b / 85%) 73.33%,
    rgb(from var(--ui-950) r g b / 92%) 80%,
    rgb(from var(--ui-950) r g b / 96%) 86.67%,
    rgb(from var(--ui-950) r g b / 99%) 93.33%,
    var(--ui-950) 100%
  );
}

@media (min-width: 1024px) {
  .hero--has-media::before { min-height: 400px; }
}

/* A grid whose alignment is driven by two custom properties rather than by
   flex rules, so a variant re-points one value instead of restating the
   layout. Rows are max-content so content clusters instead of spreading. */
.hero__content {
  /* Spans the wrapper grid. Without this the content box takes a single
     column and every child collapses to ~80px. */
  grid-column: 1 / -1;
  position: relative;
  z-index: var(--z-base);

  display: grid;
  grid-auto-rows: max-content;

  height: 100%;
  justify-items: var(--hero-alignment-horizontal, center);
  align-content: var(--hero-alignment-vertical, center);

  /* Inset by the gutters as a BOX, not by padding — the .big-link fills this
     element, so its bounds are the hero's click target and they should stop
     at the gutter the way the reference's does. */
  width: calc(100% - var(--gutters-side) * 2);
  margin-inline: auto;
  padding-block-end: var(--space-xxlarge);
  color: var(--ui-00);
}

/* Bottom-left — the treatment both homepage heroes use. */
.hero--flex-end {
  --hero-alignment-horizontal: start;
  --hero-alignment-vertical: end;
}

/* --- Billboard hero ----------------------------------------------------------
   Eyebrow, title, one line of copy and a button, stacked and centred at the
   foot of the image — the Westwing arrangement, in our type.

   White, over a scrim. Measured on this hero: the bottom band averages 0.181
   luminance, which gives white 4.54:1 and black 4.63:1 — both marginal, and the
   band runs from near-black to near-white, so neither survives on its own. The
   scrim settles it: it takes the average to 0.10 and white to 7:1.

   This is the opposite call to the header, where a scrim was rejected. The
   difference is where it sits: a gradient at the foot of a photograph is an
   ordinary editorial device, while one behind a header is a black bar.
   ---------------------------------------------------------------------------- */

.hero--billboard {
  --hero-text-align: center;
  --hero-alignment-horizontal: center;
  --hero-alignment-vertical: end;
}

.hero__scrim {
  position: absolute;
  inset: auto 0 0;
  height: 62%;
  background: linear-gradient(to top, rgb(0 0 0 / 55%), rgb(0 0 0 / 0%));
  pointer-events: none;
  z-index: 1;
}

/* The content box stretches to the hero's full height as a grid item, so the
   stack is pushed down from inside rather than by aligning the box. */
.hero--billboard .hero__content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-medium);
  padding-block-end: var(--space-xxlarge);
  color: var(--ui-00);
}

.hero--billboard .hero__content > * { width: fit-content; }

.hero__eyebrow {
  margin: 0;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.hero__title { margin: 0; max-width: 18ch; }

.hero__lead {
  margin: 0;
  max-width: 46ch;
  color: var(--ui-00-80);
}

.hero__cta { margin-block-start: var(--space-small); }

/* The billboard's stack, tightened.
   -------------------------------------------------------------------------
   With the eyebrow gone the hero is three things — title, lead, button — and
   they were spaced for four: 16px between each, plus 24px more above the
   button, which measured 40px between the sentence and the thing it asks you
   to press. That reads as three separate blocks rather than one address.

   The title also steps down a level, from .title-h1 to .title-h2, and a
   smaller title wants less air around it or the gaps start to outweigh it.

   Scoped to --billboard so the other heroes — the centred one, the media-less
   collection header — keep the rhythm they were drawn with; only the billboard
   loses a row.

   The button is reached through the child selector rather than through
   .hero__cta, and that is not a matter of taste. Something outside this file
   puts 24px of block-start margin on .sklad-button here: removing that one
   class from the element in the page drops the margin to zero, and .hero__cta
   at two classes could not outrank it — set to 0 there, the rendered gap did
   not move. It is in none of the theme's stylesheets, none of the page's
   inline <style> blocks, and the browser's own CSSOM could not be made to name
   it. So the value is stated at the specificity where it demonstrably holds,
   and this note stands in for the source, which is still unaccounted for.

   Measured at 1440, before and after:

     before  title 114px, then 16px, lead, then 40px, button
     after   title 68px,  then 12px, lead, then 24px, button
   */
.hero--billboard .hero__content {
  gap: var(--space-small-plus);

  /* And the ground under the block. .hero__content pays --space-xxlarge in the
     breakpoint-agnostic rule above; 56px was drawn under a 114px title and four
     rows, and the block now has three rows and a 68px title.

     Down the scale in steps, each one looked at: 56 was drawn for the old
     block, 40 and 32 both still read as too much under a title this size, and
     --space-large is where it stops.

     It now sits well below what the billboard uses on a phone, where 40px
     still applies — which is the right way round rather than an oversight. The
     desktop hero is 765px tall and the phone's is not, so the same number is
     not the same amount of room in the two places. */
  padding-block-end: var(--space-large);
}

.hero--billboard .hero__content > .sklad-button { margin-block-start: var(--space-small-plus); }

@media (max-width: 1023px) {
  .hero--billboard .hero__content {
    gap: var(--space-small-plus);
    padding-block-end: var(--space-xlarge);
  }
  .hero__scrim { height: 72%; }
}

.hero--center {
  --hero-alignment-horizontal: center;
  --hero-alignment-vertical: center;
}

/* `--full` is the default ratio above. `--medium` steps from square on
   mobile to a wide banner on desktop. */
.hero--medium { aspect-ratio: 1; }

/* --- Media-less hero -------------------------------------------------------
   A collection header is type on a plain ground: no image, height driven by
   its content. --auto releases the aspect ratio; --black re-points the text
   colour and the breadcrumb's resting opacity; --spacing-top clears the
   header, which is in flow on these pages.

   The padding rules stack in this order deliberately — --wysiwyg is last and
   wins, matching the reference's own cascade.
   --------------------------------------------------------------------------- */

.hero--has-no-media,
.hero--auto {
  aspect-ratio: auto;
  overflow: visible;
}

.hero__background {
  position: absolute;
  inset: 0;
  z-index: 0;
  height: 100%;
  background-color: var(--hero-background-color, transparent);
}

.hero__content-wrapper { height: 100%; }

.hero--black {
  --text-opacity: 0.6;
  --link-underline-color: var(--ui-950);
}

.hero--black .hero-navigation,
.hero--black .hero__content { color: var(--ui-950); }

.hero--white .hero-navigation,
.hero--white .hero__content { color: var(--ui-00); }

/* Clears the header only. The reference nests its announcement bar inside an
   absolutely-positioned header group, so its hero has to clear both; here the
   bar is in normal flow above the header and already occupies its own space. */
.hero--spacing-top .hero__content-wrapper {
  padding-top: var(--header-height);
}

.hero--wysiwyg {
  --link-hover-color: var(--ui-700);
  --link-underline-color: var(--ui-200);
}

.hero.hero--center .hero__content { padding-block: var(--space-xlarge); }
.hero.hero--auto .hero__content { padding-block: var(--space-xxxlarge); }

.hero.hero--wysiwyg .hero__content {
  padding-block: unset;
  padding-top: var(--space-xxlarge);
}

/* The title takes the design-ratio pair, so its measure tracks the layout
   rather than being pinned to one width. */
/* The empty .top-title is a spacer in the reference: it carries the only gap
   in the hero stack, between the title and the category nav. */
.top-title { margin-bottom: var(--space-medium); }

.hero--fill .hero__content > .title,
.hero--fill .hero__content > .top-title { width: fit-content; }

/* CMS body copy in a hero. It takes the same design-ratio pair as the title
   so the measure tracks the layout, and the reference's one-step gap from
   whatever sits above it — the same gap a hero button or link gets. */
.hero--fill .hero__content > .block-richtext {
  width: calc(344 * var(--design-ratio-fluid));
  max-width: calc(344 * var(--design-ratio));
}

.hero .sklad-button,
.hero .link,
.hero .block-richtext { margin-top: var(--space-large); }

@media (min-width: 1024px) {
  .hero.hero--center .hero__content { padding-block: var(--space-xxlarge); }
  .hero.hero--auto .hero__content { padding-block: 88px; }
  /* Must re-clear padding-block here: --auto sets it to 88px inside this same
     media block, which would otherwise re-introduce a bottom pad that the
     reference does not have (the hero closes on the category nav). */
  .hero.hero--wysiwyg .hero__content {
    padding-block: unset;
    padding-top: var(--space-xxxlarge);
  }

  .hero--fill .hero__content > .title,
  .hero--fill .hero__content > .top-title,
  .hero--fill .hero__content > .block-richtext {
    width: calc(984 * var(--design-ratio-fluid));
    max-width: calc(984 * var(--design-ratio));
  }
}

/* Statement hero, after the reference's narrow editorial blocks: the column
   is capped at a reading measure and set left inside a centred box, so a
   quote wraps in three lines instead of running the viewport. The body drops
   to the secondary colour — the quote is the voice, the body the aside.
   Declared after the --fill widths above so the cap wins at every width. */
.hero--statement { --hero-text-align: start; }

/* --wysiwyg clears the content's bottom padding for heroes that close on a
   category nav; a statement closes on the next section, so the air returns.
   Three classes, because the clearing rule is .hero.hero--wysiwyg and this
   must outweigh it, not just follow it. */
.hero.hero--statement .hero__content { padding-block-end: var(--space-xxlarge); }

@media (min-width: 1024px) {
  .hero.hero--statement .hero__content { padding-block-end: 88px; }
}

.hero--statement .hero__content > .title,
.hero--statement .hero__content > .top-title,
.hero--statement .hero__content > .block-richtext {
  width: 100%;
  max-width: 620px;
}

.hero--statement .top-title { font-weight: 600; }

.hero--statement .richtext { color: var(--text-secondary); }

/* --- Toggle ----------------------------------------------------------------
   A switch, not a checkbox: used for the "Fast delivery" filter in the
   listing toolbar. Green is the one place this palette uses a hue for state.
   --------------------------------------------------------------------------- */

.toggle {
  --hover-check-offset: 2px;
  --checked-toggle-transform: calc(100% - 2px);
  --unchecked-toggle-transform: 0;
  position: relative;
  width: 34px;
  height: 22px;
  margin: auto;
  appearance: none;
  vertical-align: text-bottom;
  color: inherit;
  font-size: inherit;
  border: 1px solid var(--ui-300);
  border-radius: var(--space-medium-plus);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}

.toggle::before {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--ui-600);
  transform: translate(var(--unchecked-toggle-transform));
  transition:
    transform var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}

.toggle:checked {
  background-color: var(--green-active);
  border-color: var(--green-active);
}

.toggle:checked::before {
  transform: translate(var(--checked-toggle-transform));
  background-color: var(--ui-00);
}

.toggle:checked:not([disabled]):hover {
  background-color: var(--green-hover);
  border-color: var(--green-hover);
}

/* The knob leans toward the direction it would travel on hover. */
.toggle:checked:not([disabled]):hover::before {
  transform: translate(calc(var(--checked-toggle-transform) - var(--hover-check-offset)));
}

.toggle:not(:checked, [disabled]):hover { border-color: var(--ui-400); }
.toggle:not(:checked, [disabled]):hover::before { transform: translate(var(--hover-check-offset)); }

.toggle:not([disabled]) { cursor: pointer; }
.toggle:disabled { opacity: 0.5; cursor: not-allowed; }
.toggle:disabled + label { cursor: not-allowed; }

.toggle__wrapper { display: flex; align-items: center; }
.toggle + label { margin-left: var(--space-xsmall); }

/* --- SEO text --------------------------------------------------------------
   The editorial block that closes a collection page. Its content is authored
   in the CMS, so this is the one place on the page where arbitrary heading
   and list markup lands — see .richtext.
   --------------------------------------------------------------------------- */

.text-seo__wrapper {
  padding-top: var(--space-xxlarge-plus);
  row-gap: var(--space-xxlarge);
  border-top: 1px solid var(--ui-100);
}

.text-seo__left,
.text-seo__right { grid-column: span 12; }

.text-seo__title {
  margin-bottom: var(--space-large);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.text-seo__content { position: relative; color: var(--ui-500); }

/* The richtext island sizes an h2 at 1.75rem, which is the right step under a
   page title. These are subheadings inside a block of body copy, and at 28px
   over 16px text they read as five separate articles rather than one. Scoped
   here rather than changed in .richtext, which every CMS field shares — and
   compounded with .richtext, because the rule it has to beat sits later in
   the file at the same specificity. */
.text-seo__content.richtext h2 {
  font-size: var(--title-h6-size);
  line-height: var(--title-h6-lh);
}

/* --- Rich text -------------------------------------------------------------
   The escape hatch from base.css's reset.

   Everywhere else in this system we author every tag, so stripping list
   markers, heading sizes and link underlines is safe. Inside a CMS field it
   is not: someone types an <h2> and a <ul> in the admin and expects them to
   look like a heading and a list. This island puts those defaults back, and
   is the only place they exist.
   --------------------------------------------------------------------------- */

.richtext > * + * { margin-block-start: var(--space-medium); }

.richtext h1,
.richtext h2,
.richtext h3 {
  font-family: var(--font-serif-condensed);
  font-weight: var(--font-weight-regular);
  color: var(--text-primary);
  letter-spacing: var(--tracking-display);
  font-variant-ligatures: no-contextual;
}

.richtext h1 { font-size: var(--title-h3-size); line-height: var(--title-h3-lh); }
.richtext h2 { font-size: var(--title-h5-size); line-height: var(--title-h5-lh); }
.richtext h3 { font-size: var(--title-h6-size); line-height: var(--title-h6-lh); }

.richtext h1:not(:first-child),
.richtext h2:not(:first-child),
.richtext h3:not(:first-child) { margin-block-start: var(--space-large); }

.richtext ul,
.richtext ol {
  padding-inline-start: var(--space-large);
  list-style: revert;
}

.richtext li + li { margin-block-start: var(--space-xxsmall); }

.richtext p,
.richtext ul,
.richtext ol,
.richtext span { font-weight: var(--font-weight-regular); }

.richtext strong { font-weight: var(--font-weight-medium); color: currentcolor; }

/* Links carry the same retracting underline as .link__text — a gradient, not
   text-decoration, so it can animate. */
.richtext a {
  background-image: linear-gradient(
    transparent calc(100% - 1px),
    var(--link-underline-color, var(--ui-200)) 1px
  );
  background-repeat: no-repeat;
  background-size: 100% 100%;
  text-decoration: none;
  transition: background-size var(--duration-fast) var(--ease-out);
}

.richtext a:hover {
  color: var(--link-hover-color, inherit);
  background-size: 0% 100%;
  background-position: right;
}

.richtext ul { list-style: disc; }
.richtext ol { list-style: decimal; }

.richtext .text-large { color: var(--ui-950); }
.richtext .text-large + p:not([class]) { margin-top: var(--space-medium); }
.richtext em { font-style: italic; }

/* --- Rich text: table, quote, note -------------------------------------------
   Three things a guide article needs that no other CMS field on the site ever
   asked for. They live inside .richtext rather than beside it because they
   arrive in the same editor-authored HTML as the prose around them, and the
   reset in base.css strips all three back to nothing.
   ----------------------------------------------------------------------------- */

/* A measurements table is most of why these articles exist — "40–50 cm",
   "75–90 cm" — so it has to survive a 375px screen. It scrolls inside its own
   box rather than widening the article: the prose measure is the page's spine
   and a table is not allowed to bend it. */
.richtext .table-scroll { overflow-x: auto; }

.richtext table {
  width: 100%;
  min-width: 20rem;
  border-collapse: collapse;
  text-align: start;
}

.richtext th,
.richtext td {
  padding: var(--space-small-plus) var(--space-medium) var(--space-small-plus) 0;
  border-block-end: 1px solid var(--border-subtle);
  vertical-align: top;
  text-align: start;
}

.richtext th { font-weight: var(--font-weight-medium); color: var(--ui-950); }
.richtext td { color: var(--ui-800); }

/* Without this the last row's rule lands on the next paragraph's first line.
   Both cells, not only the value: with td alone the last label kept its border
   and the table ended in a stub of a line under the left column. */
.richtext tbody tr:last-child th,
.richtext tbody tr:last-child td { border-block-end: 0; }

/* A pull quote, not a citation: these are the article's one-sentence
   conclusions, so they take the condensed serif and a rule rather than
   quotation marks. The sources bold them, which at this size would be a second
   emphasis on top of the first — the quote is already the emphasis. */
.richtext blockquote {
  margin-inline: 0;
  padding-inline-start: var(--space-large);
  border-inline-start: 1px solid var(--ui-950);
  font-family: var(--font-serif-condensed);
  letter-spacing: var(--tracking-display);
  font-size: var(--title-h6-size);
  line-height: 1.35;
  color: var(--ui-950);
}

.richtext blockquote > * + * { margin-block-start: var(--space-small); }
.richtext blockquote p,
.richtext blockquote strong { font-weight: var(--font-weight-regular); }

/* The summary that opens a guide, and the occasional aside inside one. A tint
   rather than a border: these sit in the middle of the measure, and a boxed
   rule at that width reads as a form field rather than as an aside. */
.article-note { padding: var(--space-large); background-color: var(--ui-50); }

.article-note > * + * { margin-block-start: var(--space-medium); }
.article-note > :last-child { margin-block-end: 0; }

.article-note__title {
  margin: 0;
  color: var(--ui-500);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

/* The title is a label for what follows, not a paragraph above it. */
.article-note__title + * { margin-block-start: var(--space-small-plus); }

@media (min-width: 1024px) {
  .text-seo__wrapper { padding-top: var(--space-xxlarge); row-gap: 0; }
  .text-seo__left { grid-column: 1 / 11; }
  /* Text with no image beside it used to take the full 24 columns, which at
     1440 is a 1312px line — far past the measure anything is comfortable to
     read at. Fourteen columns puts it back around 75 characters. */
  .text-seo__left.is-only-text { grid-column: 1 / 15; }
  .text-seo__right { grid-column: 13 / -1; }
  .text-seo__title { margin-bottom: var(--space-large-plus); }
}

@media (min-width: 768px) { .hero--medium { aspect-ratio: 5 / 3; } }
@media (min-width: 1024px) { .hero--medium { aspect-ratio: 11 / 4; } }

/* An invisible full-bleed click target. The hero's visible CTA is an ordinary
   button; this sits over the whole section so the entire image is clickable,
   which is why it carries no styling of its own and stays at opacity 0. Give
   it an aria-label — there is no visible text to name it. */
.big-link {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
}

.hero__title { max-width: calc(640 * var(--design-ratio)); }
.hero--center .hero__title { max-width: calc(760 * var(--design-ratio)); }

.hero__subtitle {
  max-width: calc(560 * var(--design-ratio));
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
}

/* Text-only hero — a centred statement on a plain ground, no media.
   Height is driven by its content rather than the viewport. */
.hero--text {
  aspect-ratio: auto;
  background-color: var(--surface);
  text-align: center;
}

.hero--text .hero__content {
  align-items: center;
  justify-content: center;
  gap: var(--space-large);
  color: var(--text-primary);
  text-align: center;
  padding-block: var(--space-xxxlarge) 0;
}

/* 984px in the 1448 design — the figure the original uses here.

   Note the pair: the width tracks the viewport while the max-width caps it
   against the container, both derived from the same design measurement. That
   is what holds the statement to three lines at any width, instead of letting
   it reflow to two on a wide screen. Below the display breakpoint it simply
   shrink-wraps. */
.hero--text .hero__statement {
  width: fit-content;
  margin-inline: auto;
}

@media (min-width: 1024px) {
  .hero--text .hero__statement {
    width: calc(984 * var(--design-ratio-fluid));
    max-width: calc(984 * var(--design-ratio));
  }
}

/* --------------------------------------------------------------------------
   MINI SLIDER — category navigation: a Products/Rooms toggle above a
   horizontally scrolling row of category tiles.
   -------------------------------------------------------------------------- */

.mini-slider {
  /* Declared here, not on the item, so the arrows can be centred on the media
     without restating the expression. */
  --mini-slider-item-width: calc(200 * var(--design-ratio-fluid));

  display: flex;
  flex-direction: column;
  gap: var(--space-large);
  padding-block: var(--space-xsmall);
}

/* The title sits on the page gutter, left, above the row. It replaced a
   centred Products/Rooms tab control: two tabs over one row of categories is
   a control asking to be operated, when the row already says what it holds. */
.mini-slider__header { padding-inline: var(--gutters-side); }

.mini-slider__title { margin: 0; }

/* The gap above the first section after the hero. 72px is the standard step
   between sections; under a hero that already stops short of the fold to show
   what follows, that much air argues with the thing it is showing. Set as
   --flow-gap on the element that owns the gap above it, which is how base.css
   says to make an exception. */
.hero--billboard + * { --flow-gap: var(--space-xlarge); }

/* One step down the display scale, at each width's own base: the heading
   labels a row rather than opening a section, and at 40px it was competing
   with the hero above it. */
@media (min-width: 1024px) {
  .mini-slider__title {
    font-size: var(--title-h5-size);
    line-height: var(--title-h5-lh);
  }
}

/* The same slider inside the basket drawer, where there is no page gutter to
   sit on and a fifth of the width to work with. One custom property drives the
   item width and, through it, the arrows' height — so the whole component
   scales from a single number rather than from a second set of rules. */
.mini-slider--compact {
  --mini-slider-item-width: calc(116 * var(--design-ratio-fluid));
  gap: var(--space-small-plus);
  padding-block: 0;
}

.mini-slider--compact .mini-slider__header,
.mini-slider--compact .mini-slider__track { padding-inline: 0; }

.mini-slider--compact .mini-slider__nav { inset-inline: 0; }

/* The same min-*: auto trap as the drawer's own list, on the other axis. Inside
   a flex column the viewport is free to grow to its content, so the track was
   476px wide in a 343px drawer: the row could not scroll, and the panel scrolled
   sideways instead — which is also what put a scrollbar under the row. */
.mini-slider--compact,
.mini-slider--compact .mini-slider__viewport,
.mini-slider--compact .mini-slider__track { min-width: 0; max-width: 100%; }

/* Room between the way out of the empty basket and the way further into it.
   The column's own gap is 16px, which reads as one list of three things; this
   separates the button from the row it is not part of. Added to the gap rather
   than replacing it, and set on the slider so the heading travels with the row
   it belongs to. Same at every width — the drawer is the same drawer. */
/* Pinned to the bottom of the drawer. `auto` needs the empty state to own the
   leftover height, hence the flex line: without it the block is only as tall
   as its content and there is no leftover to push into. */
.mini-cart__empty { flex: 1 1 auto; }
.mini-cart__empty .mini-slider--compact {
  width: 100%;
  margin-block-start: auto;
  padding-block-start: var(--space-xlarge);
  text-align: start;
  /* Three and a half cards, not three. A row that ends flush with the drawer
     looks like the whole row; half a card at the edge is the only thing that
     says there is more to the right. Derived from the track rather than fixed,
     because the drawer is one width on a phone and another on a desktop and
     the fraction is what has to hold, not the pixel. Each item carries an 8px
     right margin, so the unit being divided is card + gap. */
  --mini-slider-item-width: calc(100% / 3.5 - var(--space-small));
}
/* Serif and sentence case, the same heading the homepage row carries. It was
   uppercase DM Sans at 28px, which in a 380px drawer read as the loudest
   thing in an otherwise quiet empty state. */
.mini-cart__empty .mini-slider__title {
  font-size: var(--title-h6-size);
  line-height: var(--title-h6-lh);
}

@media (min-width: 1024px) {
  /* The drawer is a fixed width at every viewport, so the item cannot track
     the viewport the way it does on the homepage. */
  .mini-slider--compact { --mini-slider-item-width: 116px; }
}

/* A segmented control: pills on a tinted ground, with the active segment
   lifted out in white. Not underlined tabs. */
.menu-toggle {
  position: relative;
  display: flex;
  justify-content: stretch;
  gap: var(--space-xsmall);
  padding: var(--space-xsmall);
  border-radius: var(--radius-control);
  background-color: var(--ui-50);
}

/* flex:1 so both segments are equal width regardless of label length. */
.menu-toggle-item {
  flex: 1;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-xsmall);
  padding: var(--space-xsmall) 10px;
  border-radius: var(--space-xxsmall);
  color: var(--ui-500);
  transition: color var(--duration-x-fast) var(--ease-out),
              background-color var(--duration-x-fast) var(--ease-out);
}

.menu-toggle-item__label {
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}

/* The icon is what makes each segment wider than its label alone. */
.menu-toggle-item__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
}

.menu-toggle-item:hover { color: var(--ui-950); }

.menu-toggle-item.is-active {
  color: var(--ui-950);
  background-color: var(--ui-00);
  box-shadow: var(--shadow-level-1);
}

/* Only the active panel is shown; the toggle swaps them. */
.mini-slider__viewport { position: relative; width: 100%; }

/* The arrows sit over the row, vertically centred on the media rather than on
   the viewport, so the labels beneath do not drag them off-centre. */
.mini-slider__nav {
  position: absolute;
  inset-inline: var(--space-small);
  top: 0;
  /* The media is 4:5 of the item width, so this is exactly its height. */
  height: calc(var(--mini-slider-item-width) * 5 / 4);
  display: none;
  align-items: center;
  justify-content: space-between;
  pointer-events: none;
}

.mini-slider__nav .sklad-button {
  width: fit-content;
  pointer-events: auto;
  box-shadow: var(--shadow-level-2);
}

/* Revealed on hover, like the product card's arrows — the standard here is
   that a pointer affordance stays out of the way until there is a pointer on
   it. A touch row is scrolled by dragging, so the arrows never appear there. */
@media (hover: hover) and (pointer: fine) {
  .mini-slider__nav { display: flex; }

  .mini-slider__nav .sklad-button {
    opacity: 0;
    transition: opacity var(--duration-fast) var(--ease-out);
  }

  .mini-slider:hover .mini-slider__nav .sklad-button,
  .mini-slider:focus-within .mini-slider__nav .sklad-button { opacity: 1; }

  /* At an end there is nothing to page to, so it stays hidden even on hover. */
  .mini-slider:hover .mini-slider__nav .sklad-button[disabled],
  .mini-slider:focus-within .mini-slider__nav .sklad-button[disabled] {
    opacity: 0;
    pointer-events: none;
  }
}

/* A flex row: each item sets its own fluid flex-basis from the design ratio
   and spaces itself with margin-right, matching the reference. */
/* Snapped, like every other row on the site. This used to scroll freely on the
   argument that stopping anywhere always leaves an item part-shown, which is
   what says "this continues" — but the row now runs off the screen edge on a
   phone, so that job is done by the bleed and the odd behaviour was the only
   thing left. Consistency is worth more than the distinction was. */
.mini-slider__track {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-inline: var(--gutters-side);
  /* Otherwise `snap-align: start` snaps to the border edge, the track scrolls
     its own padding away, and the first item sits flush to the screen instead
     of on the gutter its heading uses — the same trap .stories-teaser__grid
     documents. */
  scroll-padding-inline: var(--gutters-side);
  scrollbar-width: none;
}

.mini-slider__track::-webkit-scrollbar { display: none; }

.mini-slider-item {
  --width: var(--mini-slider-item-width);
  flex: 0 0 var(--width);
  scroll-snap-align: start;
  /* 8px, matching the reference — a tighter gap makes the row read as one
     continuous strip rather than a set of separate cards. */
  margin-right: var(--space-small);
  display: block;
  text-align: center;
}

.mini-slider-item__media {
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background-color: var(--surface-muted);
}

.mini-slider-item__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-slow) var(--ease-out-cubic);
}

.mini-slider-item:hover .mini-slider-item__media img { transform: scale(1.05); }

.mini-slider-item__label {
  display: block;
  margin-block-start: var(--space-small-plus);
  text-align: center;
  font-size: var(--text-medium-size);
  color: var(--ui-600);
  transition: color var(--duration-fast) var(--ease-out);
}

.mini-slider-item:hover .mini-slider-item__label { color: var(--ui-950); }

/* --------------------------------------------------------------------------
   SPACER & DIVIDER — the theme uses these as explicit layout sections
   rather than relying on margins between blocks.
   -------------------------------------------------------------------------- */

.spacer {
  display: block;
  width: 100%;
  height: var(--space-mobile-height, var(--space-height, var(--space-xxlarge)));
  background-color: var(--background-color, transparent);
}

@media (min-width: 1024px) {
  .spacer { height: var(--space-height, var(--space-xxlarge)); }
}

.divider {
  width: 100%;
  height: 1px;
  border: none;
  background: var(--ui-100);
}

/* --------------------------------------------------------------------------
   COMMUNITY (flowbox) — UGC grid with a centred intro.
   -------------------------------------------------------------------------- */

/* The original renders this slot with a third-party UGC widget, so its
   internal spacing can't be inspected — this padding is fitted to the
   653px the section occupies on desktop. Mobile carries none. */
.community { text-align: center; }

@media (min-width: 1024px) {
  .community { padding-block: var(--space-xlarge); }
}

.community__header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-small);
  margin-block-end: var(--space-xxlarge);
}

.community__subtitle { max-width: calc(560 * var(--design-ratio)); }

.community__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--gutters);
}

@media (min-width: 768px) {
  .community__grid { grid-template-columns: repeat(4, 1fr); }
}

/* --------------------------------------------------------------------------
   SECTION HEADER — shared heading block above carousels/grids.
   -------------------------------------------------------------------------- */

.section-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-large);
  margin-block-end: var(--space-large-plus);
}

.section-header__text { display: flex; flex-direction: column; gap: var(--space-small); }

/* --------------------------------------------------------------------------
   PRODUCT CARD

   Media is a 4:5 carousel with an absolutely-positioned overlay for badges.
   Below it, a header whose ACTIONS row (swatches + wishlist) sits ABOVE the
   title — not below — and whose infos block is a grid with named areas so
   title and price sit on one row and the description drops beneath.
   -------------------------------------------------------------------------- */

.card-product {
  --card-product-spacing-flow: var(--space-medium);
  --card-product-padding-overlay: var(--space-small-plus);
  position: relative;
}

@media (min-width: 1024px) {
  .card-product { --card-product-padding-overlay: var(--space-medium); }
}

.card-product__medias {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
}

.card-product__carousel,
.card-product .carousel { height: 100%; }

.carousel__viewport { overflow: hidden; }

/* The card holds a real scroll-snap track, not a cross-fading pair: the images
   are browsable rather than a single hover flourish, which is the whole point
   of the kavehome behaviour. It is the same scroll-snap machinery the product
   gallery uses on mobile, driven by the same two initialisers — the arrows by
   initCarousels, the progress bar by initGalleryDots. */
.card-product__gallery {
  display: flex;
  height: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  overscroll-behavior-y: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
}

.card-product__gallery::-webkit-scrollbar { display: none; }

/* Each slide is a link to the product. Wrapping the images rather than laying a
   stretched link over the media matters on touch: an overlay would swallow the
   swipe that pages the gallery, whereas a link inside the scroller lets the
   browser tell a drag from a tap itself.

   The add-to-bag button, the arrows and the heart sit above these on z-index 2,
   so they stay separate actions rather than becoming part of the link. */
.card-product__slide {
  display: block;
  /* A pixel wider than the frame, so the next photograph cannot show at the
     edge of this one.
     -----------------------------------------------------------------------
     The card is laid out by a grid and its width comes out fractional —
     301.773px in an article's product row, 330.219px in the one under a
     product. The hover preview ends by scrolling the gallery on by exactly
     that width, but a browser stores scrollLeft as a whole number and rounds
     it: 302 against a slide that starts at 301.773. The slide sits a quarter
     of a pixel too far left, and a quarter of a pixel of the NEXT one is left
     showing down the right edge — which, when that photograph starts dark,
     renders as a thin dark line that snaps into existence at the end of every
     hover. Sweeping a row of cards makes it read as flicker, and a line
     appearing under the cursor is enough to make people doubt the button they
     were about to press.
     Measured on staging: slide 3 poked 0.227px into the frame; with this it
     starts 0.773px past it. The extra pixel is clipped by the frame at rest
     and never seen. Snapping does not fix it — a programmatic scroll is not
     re-snapped, measured. */
  flex: 0 0 calc(100% + 1px);
  width: calc(100% + 1px);
  height: 100%;
  scroll-snap-align: start;
  /* A flick used to carry past two or three images before the snap caught it,
     so a thumb aiming at the next photo landed on the last one. `always`
     makes every slide a stop the scroll cannot pass in one gesture. */
  scroll-snap-stop: always;
  -webkit-tap-highlight-color: transparent;
}

.card-product__slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

/* The second image, laid over the first and faded in on hover. JS builds it on
   the first hover; everything it does is here. Behind the arrows, the heart
   and the add button (z-index 2) and deaf to the pointer, so hovering it can
   never be what ends the hover. */
.card-product__hover {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-medium) var(--ease-out);
}

.card-product__hover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.card-product__hover.is-visible { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .card-product__hover { transition: none; }
}

/* --- Arrows and the add button ---------------------------------------------
   Both are hover affordances, so both are gated on a device that actually has
   hover. On touch they would be permanent clutter over the image, and the
   swipe already does what the arrows do.
   ---------------------------------------------------------------------------- */

.card-product__nav,
.card-product__add {
  display: none;
  position: absolute;
  z-index: 2;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.card-product__nav {
  inset: 0 var(--space-small);
  align-items: center;
  justify-content: space-between;
  pointer-events: none;
}

.card-product__nav .sklad-button { pointer-events: auto; }

/* Disabled rather than hidden at the ends, so the row does not reflow as you
   page — same as the reference's own carousels. */
.card-product__nav .sklad-button[disabled] { opacity: 0; pointer-events: none; }

/* A card inside a carousel is not itself a carousel.

   .card-product__medias carries data-carousel of its own, so a card sitting in
   a row that is also a carousel offered two sets of controls over the same
   gesture: the card's arrows and dots over its three photographs, and the row's
   arrows over the cards. Dragging either one was a guess at which would answer.

   Inside a carousel the card keeps one image and the row does the paging. The
   photographs stay in the DOM, so the hover swap still has something to swap
   to — it moves the strip with scrollLeft, which still works on a container
   that has been closed to the reader. Where a card is not inside a carousel —
   the category grid, search results — nothing here applies and the card keeps
   its own arrows.

   On a touch screen this leaves one photograph per card, which is the point:
   there is no hover to reveal the others and no gesture left to confuse. */
[data-carousel] .card-product .card-product__nav,
[data-carousel] .card-product .carousel-dots { display: none; }

[data-carousel] .card-product .card-product__gallery {
  overflow: hidden;
  scroll-snap-type: none;

  /* A trackpad swipe over a card has to reach the row underneath it.

     The gallery carries overscroll-behavior-x: contain so that paging a card's
     own photographs never drags the page sideways with it. Closed to the reader
     it cannot scroll at all, so `contain` stopped being a boundary rule and
     became a wall: the gesture landed on the card, found a scroller that would
     not move and would not pass it on, and died there. The arrows worked, the
     trackpad did nothing.

     `contain` still applies to a card in a grid, where the gallery is the thing
     being paged. Here it is not, so the gesture belongs to the row.

     overflow stays `hidden` rather than `clip`: hidden keeps a scrollport, and
     the hover swap moves this strip with scrollLeft. */
  overscroll-behavior-x: auto;
}

.card-product__add {
  inset: auto var(--space-small) var(--space-small);
  justify-content: center;

  /* .sklad-button is width:100% by default. On an absolutely positioned button
     that also sets left and right, the box is over-constrained: the width wins,
     `right` is dropped, and the button overhangs by exactly the left inset —
     then gets clipped by the media's overflow. Letting the insets size it is
     the fix; do not swap them for a width. */
  width: auto;
}

@media (hover: hover) and (pointer: fine) {
  .card-product__nav { display: flex; }
  .card-product__add { display: inline-flex; }

  .card-product:hover .card-product__nav,
  .card-product:focus-within .card-product__nav,
  .card-product:hover .card-product__add,
  .card-product:focus-within .card-product__add { opacity: 1; }

  /* The bar only reports position, so it is noise until you are interacting. */
  .card-product .carousel-dots--line { opacity: 0; transition: opacity var(--duration-fast) var(--ease-out); }
  .card-product:hover .carousel-dots--line,
  .card-product:focus-within .carousel-dots--line { opacity: 1; }
}

/* A card inside a carousel must not be a second horizontal scroller inside the
   first — the outer track owns the gesture. */
.carousel__slide.card-product .card-product__gallery {
  overflow-x: hidden;
  scroll-snap-type: none;
}

.carousel__slide.card-product .card-product__nav,
.carousel__slide.card-product .carousel-dots { display: none; }

.card-product__medias img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Sits over the media purely to place badges — never intercepts clicks, so
   the card's own link still covers the whole tile. */
.card-product__overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  display: flex;
  flex-flow: row nowrap;
  align-items: flex-start;
  justify-content: space-between;
  transition: opacity var(--duration-fast) var(--ease-out);
}

/* Direct children only — a badge inside .card-product__badges takes its
   position from that stack, not from the overlay. */
.card-product__overlay > .badge { margin: var(--card-product-padding-overlay); }
.card-product__overlay > .badge--right { margin-left: auto; }

/* Labels sit next to each other. They wrap if a card ever carries enough of
   them to need it, but the row is the default — stacked, they read as a list
   of separate claims rather than as one line of flags. */
.card-product__badges {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  /* Two pills two pixels apart read as one shape with a seam down it — NOVO
     and -15% have nothing to do with each other and have to look separate. */
  gap: var(--space-xsmall);
  margin: var(--card-product-padding-overlay);
}

.card-product__header {
  position: relative;
  padding-top: var(--space-medium);
}

/* Kept for anything still rendering the older card shape; the loop card now
   puts the heart on the image and the swatches beside the name. */
.card-product__actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* Row 1: the collection name, with its colour options on the right. The name
   is allowed to run to two lines and the swatches hold their width, so a long
   name shortens the gap rather than pushing the swatches out of the card. */
.card-product__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-small);
}

.card-product__name { margin: 0; min-width: 0; }
.card-product__row .swatch-colors { flex: 0 0 auto; margin-block-start: 2px; }

/* --- The product summary's repeats -----------------------------------------
   WooCommerce fills .single_variation with the resolved variation's price and
   availability. Both are already on the page: the price sits above the finish
   row and again inside the buy button, and stock is not something this shop
   states — everything is made to order, so "in stock" answers a question
   nobody asked. The element stays for Woo to write into; it just does not show.
   -------------------------------------------------------------------------- */

/* The price and the availability, not the block that holds them. WooCommerce's
   variation script calls .show() on .single_variation once a variation
   resolves, which writes display:block straight onto the element — so hiding
   the parent worked right up until the moment the page became useful, and
   then quietly stopped. Woo writes no inline style on these two. */
.product-summary .woocommerce-variation-price,
.product-summary .woocommerce-variation-availability { display: none; }


/* Always visible, unlike the arrows and the add button — saving something is
   not a hover affordance, and on touch there is no hover to reveal it with. */
.card-product__wishlist {
  position: absolute;
  /* The badge next to it is 16px tall sitting at the overlay padding, while
     this is a 48px touch target around a 20px icon. Anchoring both boxes at
     the same offset put the heart 12px below the badge, which read as two
     rows rather than one. The target keeps its size — it is the whole point
     of it — and is pulled up until the icon sits level with the badge. */
  top: calc(var(--card-product-padding-overlay) - 16px);
  right: calc(var(--card-product-padding-overlay) - 14px);
  z-index: 2;
  width: var(--space-large-plus);
  height: var(--space-large-plus);
  color: var(--ui-950);
}

.card-product__wishlist .icon { --icon-size: 20px; }
.card-product__wishlist:hover { color: var(--ui-600); }

.card-product__infos {
  display: grid;
  gap: var(--space-xxsmall);
  margin-top: var(--space-medium);
}

/* The reference carries padding-top on the header AND margin-top on the infos
   because something sits between the two there. Ours has nothing between, so
   they stacked into 32px of dead air under the image. Collapse the second
   when the infos is the first thing in the header, and leave both declared
   for any context where something does come first. */
.card-product__header > .card-product__infos:first-child { margin-top: 0; }

/* Three rows: name and swatches, then what it is, then what it costs. The
   price reads last because it only means something once you know what for. */
.card-product__infos > .sklad-price { justify-self: start; }

/* One column, three rows. The old desktop layout put the price in a second
   column beside the title with named areas; the name now shares its row with
   the colour swatches instead, and the price has a row of its own. */
.card-product__infos { grid-template-columns: minmax(0, 1fr); }

.card-product__title {
  position: relative;
  font-weight: var(--font-weight-medium);
  /* The collection name reads in sentence case, as authored — the markup
     pairs this class with .text-medium, one rung below the .text-ui-large
     label it used to wear. All-caps at 15px made the name the loudest thing
     on the card; the references (Westwing, Kave Home) set it quiet and let
     the photograph carry the tile. The bold weight is what still separates
     it from the description below. The bold read too black in DM Sans at 14px;
     medium is the Ferm-style step. */
  padding-right: var(--space-small);
  /* Two lines maximum, so a long name can't shunt the row out of line. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card-product__desc {
  /* --ui-500 measured 4.17:1 on the white card ground — under the 4.5:1 a
     13px/0.8125rem string needs, since it is neither 18px nor 14px-bold and
     so gets no "large text" allowance. --ui-600 is the next step up the same
     grey ramp and clears it at 6.01:1. Scoped to this class, not the ramp
     itself: other --ui-500 text sits on different, lighter or larger,
     contexts that already pass. */
  color: var(--ui-600);
  margin-top: var(--space-xxxsmall);
  font-size: var(--text-small-size);
  line-height: 18px;
  /* Card steps it below the title while markup keeps its .text-medium utility class. */
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@media (min-width: 1024px) { .card-product__desc { margin-top: 0; } }

/* The price sits under the description on its own row, left-aligned with
   everything above it — it used to be pushed right to sit beside the title. */
.card-product .sklad-price { text-align: left; }

.card-product__add-to-cart { margin-top: var(--space-small-plus); }

/* Stretched link so the whole tile is clickable. */
.card-product__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* --------------------------------------------------------------------------
   PRICE
   -------------------------------------------------------------------------- */

.sklad-price { display: inline-flex; align-items: baseline; gap: var(--space-small); }

.card-product .sklad-price__regular {
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-medium-lh);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  font-weight: var(--font-weight-medium);
}

/* --- Sale ------------------------------------------------------------------
   WooCommerce emits <del> for the old price and <ins> for the new one, with
   none of our classes on them, so the elements themselves carry the styling.
   __was / __sale are the hand-authored equivalents and resolve identically.

   This used to be the other way round: .sklad-price__sale carried the small
   grey treatment, i.e. it was styled as the OLD price, while the styleguide
   used it as the new one. Only one instance existed, so nothing shipped wrong,
   but the two readings could not both be right. __was is the old price now.
   --------------------------------------------------------------------------- */

.sklad-price del,
.sklad-price__was,
.line-item__saved {
  color: var(--ui-500);
  font-size: var(--text-xsmall-size);
  font-weight: var(--font-weight-regular);
  text-decoration: line-through;
}

/* <ins> is underlined by the UA sheet — which would read as a link here.
   The reduced price takes the same ground colour as the .badge--sale flag on
   the image above it, so the tag and the number it refers to are visibly the
   same fact rather than two unrelated marks on one card. */
.sklad-price ins,
.sklad-price__sale {
  text-decoration: none;
  color: var(--error-light);
}

/* Old price after the new one, and set smaller: the number that applies today
   leads, the one it replaced follows as the reference. */
.sklad-price { display: inline-flex; align-items: baseline; gap: var(--space-xsmall); }

.card-product .sklad-price ins,
.card-product .sklad-price__sale {
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-medium-lh);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  font-weight: var(--font-weight-medium);
}

/* Card pricing override: 13px for both regular and sale prices. */
.card-product .sklad-price__regular,
.card-product .sklad-price__sale {
  font-size: var(--text-small-size);
  /* Regular, not the price component's medium: on the card the amount
     whispers under the 500-weight title, per the approved quiet-card scale. */
  font-weight: var(--font-weight-regular);
}

/* Woo wraps every amount in these; they must not introduce their own box. */
.woocommerce-Price-amount { white-space: nowrap; }
.sklad-price bdi { unicode-bidi: isolate; }

/* --------------------------------------------------------------------------
   SWATCH COLOURS — the in-card colour row. 14px chips, not the large tiles
   used on a product page. They are static spans inside the card's own link
   rather than individual controls, so they read as an indication of what the
   piece comes in, not as a set of targets — which is why they can sit below
   the 24px minimum a real control would owe.
   -------------------------------------------------------------------------- */

.swatch-colors {
  --swatch-colors-gap: var(--space-xxsmall);
  --swatch-item-size: 14px;
  display: flex;
  align-items: center;
  z-index: 1;
  font-size: 0;
  line-height: normal;
  letter-spacing: 0;
}

.swatch-colors__list {
  position: relative;
  display: flex;
  align-items: center;
}

.swatch-colors__item { padding-left: 2px; }
.swatch-colors__item + .swatch-colors__item { margin-left: var(--swatch-colors-gap); }

.swatch-colors__link { display: inline-block; font-size: 0; }

/* The finish links on a card are 10x10 CSS px — the swatch is the whole
   control. That is under the 24x24 this project already holds itself to
   (see .footer-reassurance__contact-link), so it is a miss against our own
   bar, not only against Apple's. 24 rather than 44 here on purpose: the
   swatches sit in a row of three and a 44px target would reach across its
   neighbours, which trades one wrong tap for another. */
.swatch-colors__link { position: relative; }

.swatch-colors__link::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 24px;
  height: 24px;
  transform: translate(-50%, -50%);
}

.swatch-item {
  display: inline-block;
  position: relative;
  width: var(--swatch-item-size, 15px);
  height: var(--swatch-item-size, 15px);
  border-radius: 50%;
  background-color: var(--swatch-color, var(--ui-200));
  /* Near-white swatches would vanish against the sheet, so each chip can opt
     into a hairline via --swatch-outline. Transparent by default. */
  outline: thin solid var(--swatch-outline, transparent);
  transform: scale(1);
  transition: transform var(--duration-fast) var(--ease-out);
}

/* The selected chip shrinks and reveals a ring, rather than growing. */
.swatch-item::after {
  content: "";
  position: absolute;
  inset: -2px;
  border: 1px solid var(--ui-950);
  border-radius: 50%;
  opacity: 0;
  transform: scale(0.9);
  transition: opacity var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}

.swatch-colors__link:hover .swatch-item,
.swatch-colors__link.is-selected .swatch-item { transform: scale(0.8); }

.swatch-colors__link:hover .swatch-item::after,
.swatch-colors__link.is-selected .swatch-item::after { opacity: 1; transform: scale(1); }

.swatch-colors__more {
  pointer-events: none;
  margin-left: var(--swatch-colors-gap);
  display: flex;
  justify-content: center;
  align-items: center;
  color: var(--ui-600);
  font-size: var(--text-ui-small-size);
}

/* On a card the chips are a colour count beside the name, not a picker —
   at 14px they sat as tall as the name's own x-height and read as a second
   row of controls. Two-thirds size keeps them legible while letting the
   name lead. They are static spans inside the card's link (see above), so
   no tap target shrinks with them. */
.card-product .swatch-colors { --swatch-item-size: 10px; }

/* Only three chips fit on a narrow card. */
@media (max-width: 1023px) {
  .card-product .swatch-colors__item:nth-child(n + 4) { display: none; }
}

/* --------------------------------------------------------------------------
   WISHLIST BUTTON
   -------------------------------------------------------------------------- */

.button-wishlist {
  width: 3rem;
  height: 3rem;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ui-400);
  transition: color var(--duration-fast) var(--ease-out);
}

.button-wishlist:hover { color: var(--ui-800); }

/* A thumb misses this one, and Niko kept losing the first tap on it on an
   iPhone (06.09.2026) — measured at 32x32 CSS px on a 390px viewport, where
   the product page sizes it to --space-large-plus so it sits tight against
   the title. That passes WCAG 2.5.8's 24x24, which is why the audit never
   said anything, but it is about half the area Apple asks for, and the
   misses are intermittent in exactly the way a small target produces.

   The box keeps its size — the look is deliberate. What grows is the area
   under the finger: a transparent 44x44 centred on the button. Nothing moves,
   because the pseudo-element is out of flow, and a mouse is unaffected. */
.button-wishlist { position: relative; }

.button-wishlist::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}
.button-wishlist--small { width: 2.25rem; height: 2.25rem; }

/* --------------------------------------------------------------------------
   CAROUSEL — CSS scroll-snap; JS only drives the arrows.
   -------------------------------------------------------------------------- */

.carousel { position: relative; }

.carousel__track {
  /* The default lives in the fallback, not in a declaration: a custom property
     set on this element wins over one inherited from the row around it, so
     declaring it here meant a container could never change how many slides
     show. .product-row asked for 4.5 and got 2.5. */
  --slide-width: calc(
    (100% - (var(--slides-visible, 2.5) - 0.5) * var(--grid-products-gap-x))
    / var(--slides-visible, 2.5)
  );

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: var(--slide-width);
  gap: var(--grid-products-gap-x);

  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  overscroll-behavior-y: auto;

  /* Bleed to the page edge so slides run off-screen. */
  padding-inline: var(--gutters-side);
  margin-inline: calc(var(--gutters-side) * -1);

  scrollbar-width: none;
}

.carousel__track::-webkit-scrollbar { display: none; }

/* Every slider in the system shows a half card at the end, so the track always
   reads as continuing. --slides-visible carries the count and the width falls
   out of it, accounting for the gaps between.

   This must sit HERE, with the component, not at the end of the file: it is the
   generic default, and anything more specific declared later has to be able to
   override it. Placed last it silently beat every per-component value. */
.carousel__slide { scroll-snap-align: start; }

@media (min-width: 768px)  { .carousel { --slide-width: 40%; } }
@media (min-width: 1024px)  { .carousel { --slide-width: 24%; } }
@media (min-width: 1280px) { .carousel { --slide-width: 19%; } }

.carousel__nav { display: flex; gap: var(--space-small); }

.carousel__nav-button:disabled {
  opacity: 0.3;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   DIPTYCH — two equal panels side by side.

   Each panel is either a `.diptych-media` (full-bleed image with the title
   and CTA overlaid) or a `.packshots-carousel`. At 1440 the container is
   1312px wide and each panel is 644×805, i.e. a ~4:5 portrait.
   -------------------------------------------------------------------------- */

/* The section wrapper. Carries the rhythm above it and an optional tinted
   ground; --fill butts its panels to the container edge, --narrow opens a
   gutter between them, --with-background pads the tint away from the panels. */
.diptych {
  background-color: var(--diptych-background-color, transparent);
  margin-top: var(--space-xxlarge);
}

.diptych--with-background { padding-block: var(--space-xxlarge); }

/* --fill runs its panels to the page edge (.grid--full zeroes the inset);
   anything else sits inside the gutters. In the reference .grid is inset by
   default and --full opts out; this system is the other way round — .grid is
   full-bleed and components opt in — so the inset is stated here. */
.diptych .grid:not(.grid--full) { --padding-inline: var(--gutters-side); }

@media (min-width: 1024px) {
  .diptych { margin-top: var(--space-xxxlarge); }
  .diptych--with-background { padding-block: var(--space-xxxlarge); }
}

/* --------------------------------------------------------------------------
   DIPTYCH TEXT — the copy half of a text/media pair. Its measure is the
   design-ratio pair off 298 (mobile) / 424 (desktop), so the column keeps its
   proportion instead of filling the panel.
   -------------------------------------------------------------------------- */

.diptych-text {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* A ratio SPACER, not aspect-ratio. The text panel must match its media
   sibling's height when the copy is short, but grow past it when the copy is
   long — a floor, not a cap. (The media panel wants the opposite, which is why
   it uses aspect-ratio instead.) Both sides are 4:5 at every width. */
.diptych-text::before {
  content: "";
  /* 125% is 4:5 — the same ratio the media panel carries, so a short-copy text
     panel lands exactly level with its sibling. Change one and change both. */
  padding-bottom: 125%;
}

/* The banded diptych pairs its text with a --small media panel on purpose, so
   the 4:5 floor would tower over its sibling and pad the band with empty
   ground. A band is as tall as what it says. */
.diptych--with-background .diptych-text::before { display: none; }

/* The media panel carries the same 4:5 as a ratio of its own — a --small card
   inside it still left the full-ratio panel standing empty underneath. In a
   band the panel is as tall as the card it shows. */
.diptych--with-background .diptych-media { aspect-ratio: auto; }

/* A diptych straight after a band closes the seam: the band's own ground is
   the breathing room, so the white strip between them goes. */
.diptych--with-background + .diptych { margin-top: 0; }

.diptych-text__inner {
  /* The positioning root for the glow, which hangs off the bottom of the copy
     rather than off a percentage of the panel — see "Light off a photograph". */
  position: relative;
  width: calc(298 * var(--design-ratio-fluid));
  max-width: calc(298 * var(--design-ratio));
  padding-block: var(--space-xxlarge);
}

.diptych-text--black { color: var(--ui-950); --link-underline-color: var(--ui-950); }
.diptych-text--white { color: var(--ui-00);  --link-underline-color: var(--ui-00); }

.diptych-text .sklad-button { width: max-content; }

@media (min-width: 1024px) {
  .diptych-text__inner {
    width: calc(424 * var(--design-ratio-fluid));
    max-width: calc(424 * var(--design-ratio));
    padding-block: 0;
  }
}

.diptych__container {
  --diptych-container-grid-columns: 1;
  /* No gap on mobile — the panels butt against each other full-bleed.
     A length, not a bare 0: the glow divides this by two inside a calc() that
     also carries a percentage, and `50% - 0` with a unitless zero is invalid,
     which drops the whole declaration and leaves the light on the wrong edge
     of the page. gap: accepts either, so nothing else notices. */
  --diptych-container-gap: 0px;

  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: repeat(var(--diptych-container-grid-columns), 1fr);
  gap: var(--diptych-container-gap, 0px);
  align-content: center;
}

.diptych__container > * {
  grid-column: span 1;
  align-self: center;
  order: 1;
}

/* Text reads first on mobile, whatever the source order. */
@media (max-width: 1023px) {
  .diptych__container > .diptych-text { order: 0; }
}

@media (min-width: 1024px) {
  .diptych__container { --diptych-container-grid-columns: 2; }

  /* Only --narrow opens a gutter between the panels; --fill butts them. */
  .diptych--narrow .diptych__container { --diptych-container-gap: var(--gutters); }
}

/* The ratio is what gives a panel its height, and it CAPS it — a spacer
   pseudo-element only sets a floor, which lets a panel with tall content (the
   packshots carousel) run away. 4:5 is both 644:805 and 720:900. */
.diptych-media {
  position: relative;
  display: flex;
  width: 100%;
  aspect-ratio: 4 / 5;
  color: var(--ui-00);
}

/* Full-bleed on mobile, where the crop is taller. --small opts out: it is a
   centred card, not a bleed. */
@media (max-width: 1023px) {
  .diptych-media:not(.diptych-media--small) {
    width: var(--viewport-width);
    margin-inline: calc(50% - var(--viewport-width) / 2);
  }
}

.diptych-media--white { color: var(--ui-00); }

/* --small is a floated panel rather than a full bleed: a fixed-ratio card
   centred in its half, sized off the design board. */
.diptych-media--small .diptych-media__inner {
  width: calc(298 * var(--design-ratio-fluid));
  max-width: calc(298 * var(--design-ratio));
  height: auto;
  margin: auto;
  aspect-ratio: 0.8;
}

@media (min-width: 1024px) {
  .diptych-media--small .diptych-media__inner {
    width: calc(424 * var(--design-ratio-fluid));
    max-width: calc(424 * var(--design-ratio));
  }
}

/* The image layer, behind the sticky content. */
.diptych-media__background {
  position: absolute;
  inset: 0;
  z-index: 0;
  height: 100%;
}

.diptych-media__background img,
.diptych-media__background video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.diptych-media__background::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-color: var(--ui-950);
  opacity: var(--diptych-media-overlay-opacity, 0);
  pointer-events: none;
}

/* Content sits at the bottom, centred. The generous top padding is a
   percentage so the text block keeps its position as the panel scales. */
.diptych-media__inner {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  width: 100%;
  height: 100%;
}

/* NOTE: nothing between this element and the scroll root may carry
   `overflow: hidden` or a `transform` — either creates a containing block and
   silently cancels the sticky. That rules out putting a `data-reveal`
   animation on any diptych ancestor.

   Sticks to the bottom of the viewport so the title and CTA ride along while
   the tall panel scrolls past behind them, rather than sitting fixed at the
   panel's foot. The percentage top padding is what gives it room to travel. */
.diptych-media__content {
  position: sticky;
  bottom: 0;

  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-medium-plus);

  text-align: center;
  padding-inline: var(--space-large-plus);
  padding-block: 24% var(--space-xlarge);
}

/* The clip lives here, not on .diptych-media — an `overflow: hidden` ancestor
   would cancel the sticky positioning of .diptych-media__content. */
.diptych-media__image {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
}

.diptych-media__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-x-slow) var(--ease-out-cubic);
}

.diptych-media:hover .diptych-media__image img { transform: scale(1.03); }

/* Scrim keeps the overlaid title legible over any photograph. */
.diptych-media__image::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, var(--ui-950-32) 0%, transparent 55%);
}

.diptych-media__title { text-shadow: 0 1px 12px rgb(0 0 0 / 25%); }

/* Dark-text variant for panels shot on a light background. */
.diptych-media--black { color: var(--ui-950); }
.diptych-media--black .diptych-media__image::after { background: none; }
.diptych-media--black .diptych-media__title { text-shadow: none; }

/* Full-panel click target layered over the artwork — the visible CTA is an
   ordinary frost-black button underneath it. */
.diptych-media__link {
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
}

/* --------------------------------------------------------------------------
   PACKSHOTS CAROUSEL — the product slider that fills one half of a diptych.
   Cut-out product shots on a tinted ground, no pricing.
   -------------------------------------------------------------------------- */

.packshots {
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* aspect-ratio is a floor here: the slides' own content can push it
     taller, which is why mobile runs over the reference's 480px.

     width and min-width are what keep it a floor. Without them the ratio
     resolves the other way — content sets the height, the ratio derives a
     width from it, and the block grows past its grid track (455px inside 343
     on a phone), which also drags the next full-bleed sibling off-centre,
     because that sibling's margin-inline: calc(50% - ...) is measured against
     the stretched track. */
  width: 100%;
  min-width: 0;
  aspect-ratio: 644 / 805;
  padding: var(--space-xlarge) 0;
  background-color: var(--surface-highlight);
}

.packshots__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 60%;
  gap: var(--space-medium);

  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-inline: var(--space-xlarge);
  scrollbar-width: none;
}

.packshots__track::-webkit-scrollbar { display: none; }

.packshots__slide {
  scroll-snap-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--space-small-plus);
  text-align: center;
}

.packshots__slide .media { background: none; }

.packshots__name {
  font-size: var(--text-medium-size);
  font-weight: var(--font-weight-medium);
}

.packshots__desc {
  font-size: var(--text-xsmall-size);
  line-height: var(--text-small-lh);
  color: var(--text-secondary);
}

.packshots__nav {
  display: flex;
  justify-content: center;
  gap: var(--space-small);
  margin-block-start: var(--space-large);
}

/* --------------------------------------------------------------------------
   THREE-UP — three linked editorial tiles.
   -------------------------------------------------------------------------- */

/* 4:5, matching the diptych and mini-slider. This was 421x459 (0.917) measured
   off the original; the system now uses one image ratio throughout.

   The markup used to carry `.media--square` as well, which this outranked, so
   the class named a 1:1 crop that never happened — measured on the homepage at
   421x527, a ratio of 0.800. It is off the markup now, here and in the static
   pages, so the tile says what it is. */
.three-up .media { aspect-ratio: 4 / 5; }

/* On mobile this is a peeking carousel, not a stack — slides are 85% of the
   container so the next one shows. It only becomes a 3-up grid at 1024. */
.three-up__carousel {
  display: flex;
  gap: var(--space-small-plus);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
}

.three-up__carousel::-webkit-scrollbar { display: none; }

.three-up-item__wrapper {
  flex: 0 0 85%;
  scroll-snap-align: start;
}

@media (min-width: 1024px) {
  .three-up__carousel {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--gutters);
    overflow: visible;
  }

  .three-up-item__wrapper { flex: initial; }
}

/* Unused: the three-up markup uses `.media`, not this. Kept in step with the
   real rule above so it cannot drift if the markup ever adopts it. */
.three-up-item__media { aspect-ratio: 4 / 5; }




/* ==========================================================================
   DIPTYCH — THE DARK PASSAGE

   The production band on O nama enters dark: the section starts on the
   page's own ground and, on the shared [data-reveal] observer's cue, fades
   to the footer's dark (--ui-800) while its type lifts to white — one
   crossing, once, because the observer lets go after it fires. Reduced
   motion gets the final state immediately, with nothing to sit through.
   ========================================================================== */

.diptych--fade-dark {
  background-color: transparent;
  transition: background-color 1100ms ease;
}

.diptych--fade-dark .diptych-text--black { transition: color 1100ms ease; }

.diptych--fade-dark.is-revealed { background-color: var(--ui-800); }

.diptych--fade-dark.is-revealed .diptych-text--black {
  color: var(--ui-00);
  --link-underline-color: var(--ui-00);
}

@media (prefers-reduced-motion: reduce) {
  .diptych--fade-dark,
  .diptych--fade-dark .diptych-text--black { transition: none; }
}


/* --- Light off a photograph -------------------------------------------------
   Warm light spilling out of a photograph into the copy beside it — or, on a
   phone, above it. A plain rectangle of colour, blurred until nothing of the
   rectangle is left. The photograph is untouched and nothing here reads what is
   in it: the colour is the page's own beige, so what the eye gets is the ground
   warming up near the picture rather than a coloured shape.

   Two components take it, the story intro and the diptych, and the light has
   one rule to obey — it must never be ON TOP of the text. Under it is fine, and
   measured: black reads 21.00 against white and 18.60 against the glow, both
   far past the 7:1 that AAA asks for. That is the whole reason the phone works
   at all; see below.

   Desktop, where the row has two columns and empty ground beside the picture:

     - The story sets its copy at the top of its column, so light hung at the
       picture's own edge, at its midpoint, already clears the last line by
       179px. It is a ::before on the row, so it takes its place among the row's
       children and paints under both panels on DOM order alone.

     - A diptych centres its copy, so no percentage of the panel can promise
       the same — the longer the copy grows the lower it reaches. Its glow hangs
       off the bottom of the copy instead and starts 120px below it, which is
       the blur's own reach with room to spare, for any length of copy.

   Phone, where the row stacks to copy above picture:

     - The only ground left is the seam between them, 23-65px across the six
       components — less than the blur can travel. So the light is no longer
       kept off the text; it sits at the seam and is allowed to bleed up behind
       the last lines, which the contrast above says costs nothing.

   Both of the pseudo-elements hung off a copy box need z-index. An absolutely
   positioned pseudo-element paints in the positioned layer, ABOVE the inline
   text of the element it belongs to — hit-tested, the glow answered over the
   words. -1 puts it under, and it stays visible because the first ancestor
   painting an opaque background is <body>, which is white. It also means the
   part that runs past the seam is covered by the picture rather than tinting
   it, which is why the phone glow may be as wide as it likes.

   Deliberately NOT applied to .diptych--with-background: its text panel is
   exactly as tall as its copy, and --fade-dark already animates that surface
   to --ui-800 on scroll. Two effects, one surface.

   Nor to a container holding two media panels, which is what the homepage runs
   — there is no copy for the light to fall beside, so :has() asks for both.

   The rectangles are one-off geometry, answering to a photograph's edge and to
   a blur radius rather than to the spacing scale, so they are written in px.
   ---------------------------------------------------------------------------- */

.article__intro { position: relative; }

/*
 * Appearance only, and deliberately without `content` — a pseudo-element with
 * no content is never generated, so this rule is inert until one of the two
 * breakpoints below turns it on with the placement that belongs to it.
 */
.article__intro::before,
.article__intro-text::after,
.diptych:not(.diptych--with-background) .diptych__container:has(> .diptych-media) > .diptych-text > .diptych-text__inner::after {
  position: absolute;
  background-color: var(--beige-150);
  opacity: 0.6;
  filter: var(--blur-72);
  pointer-events: none;
}

/* See the note above: only the two hung off a copy box need this. */
.article__intro-text::after,
.diptych:not(.diptych--with-background) .diptych__container:has(> .diptych-media) > .diptych-text > .diptych-text__inner::after { z-index: -1; }

@media (min-width: 1024px) {
  /* Anchored to the picture's inner edge: two equal columns with a gap between
     them put it half a gap off the middle of the row. */
  .article__intro::before {
    content: "";
    top: 50%;
    right: calc(50% - (var(--gutters) / 2));
    width: 500px;
    height: 300px;
  }

  /* Exactly the width of the copy — the one width that cannot put the box past
     the panel's edge, since the copy sits inside the panel by construction. A
     generous 140% was tried and overflowed the one panel on the site narrow
     enough to matter. The blur spreads the light wide enough anyway. */
  .diptych:not(.diptych--with-background) .diptych__container:has(> .diptych-media) > .diptych-text > .diptych-text__inner::after {
    content: "";
    top: calc(100% + 120px);
    left: 0;
    width: 100%;
    height: 260px;
  }
}

@media (max-width: 1023px) {
  .article__intro-text { position: relative; }

  /*
   * At the seam, hung off the bottom of the copy in both components.
   *
   * The desktop numbers do not carry over, and the first attempt proved it: a
   * 150px band at blur(72px) starting at the copy's bottom puts its strongest
   * part 75px down, which on a 23-65px seam is already behind the picture.
   * What showed in the gap was the faint tail, and the effect read as nothing.
   *
   * So the band is cut to the height of the seam, and the blur with it. A
   * tighter radius keeps the light where there is room to see it; the higher
   * opacity is what a smaller blur needs to arrive at the same weight.
   */
  .article__intro-text::after,
  .diptych:not(.diptych--with-background) .diptych__container:has(> .diptych-media) > .diptych-text > .diptych-text__inner::after {
    content: "";
    top: 100%;
    left: 0;
    width: 100%;
    height: 80px;
    opacity: 0.85;
    filter: blur(38px);
  }
}


.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-small-plus);
}

.tile__content {
  display: flex;
  flex-direction: column;
  gap: var(--space-xsmall);
}

/* Overlay variant — text sits on the image instead of below it. */
.tile--overlay .tile__content {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  padding: var(--space-large);
  color: var(--ui-00);
  background: linear-gradient(to top, var(--ui-950-60), transparent);
}

.tile__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* --------------------------------------------------------------------------
   MARQUEE — infinite horizontal ticker. The track is duplicated in markup
   and translated by exactly -50%, so the loop is seamless.
   -------------------------------------------------------------------------- */

.marquee {
  overflow: hidden;
  padding-block: var(--space-medium);
  border-block: 1px solid var(--border-subtle);
}

.marquee__track {
  display: flex;
  width: max-content;
  gap: var(--space-xlarge);
  animation: marquee-scroll var(--marquee-duration, 30s) linear infinite;
}

.marquee:hover .marquee__track { animation-play-state: paused; }

.marquee__item {
  display: flex;
  align-items: center;
  gap: var(--space-xlarge);
  font-family: var(--font-serif-condensed);
  letter-spacing: var(--tracking-display);
  font-size: var(--title-h5-size);
  white-space: nowrap;
  font-variant-ligatures: no-contextual;
}

@keyframes marquee-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* --------------------------------------------------------------------------
   NEWSLETTER
   -------------------------------------------------------------------------- */

.newsletter { display: flex; flex-direction: column; }

/* Regular weight — unlike the .footer__heading beside it, which is medium. */
/* Medium, matching .footer-menu__title. In the footer this heading sits in a
   row of four column headings and was the only one at regular weight, which
   read as a different level rather than the same one. */
.newsletter__title { font-weight: var(--font-weight-medium); }

.newsletter__description { margin-top: var(--space-medium); }

/* Stacked, not an inline input group: full-width field above a full-width
   button, which is exactly what the button's `width: 100%` base gives you
   once no container opts it down. */
.newsletter__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-medium);
  margin-top: var(--space-medium);
}

/* The privacy line is information, not a gate — it sits under the status so
   the order reads: enter, submit, what happened, what it means. */
.newsletter__consent {
  margin-block-start: var(--space-small-plus);
  color: var(--status-muted);
}

/* --------------------------------------------------------------------------
   ACCORDION — <details>-based; collapses footer columns on mobile.
   -------------------------------------------------------------------------- */

.accordion {
  width: 100%;
  border-block-end: 1px solid var(--border-subtle);
}

/* Where the header draws the divider, the accordion must not draw a second
   one. .accordion__header carries a border-top — one rule per boundary, plus
   one above the first row — and this bottom border landed a second hairline
   directly beneath it, in a different grey. The <details> variant has no
   header and no border of its own, so that one keeps this rule and its
   dividers. */
.accordion:has(.accordion__header) { border-block-end: 0; }

.accordion__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-medium);
  padding-block: var(--space-medium);
  cursor: pointer;
  list-style: none;
  font-size: var(--text-medium-size);
}

.accordion__summary::-webkit-details-marker { display: none; }

.accordion__icon {
  flex-shrink: 0;
  transition: transform var(--duration-medium) var(--ease-out-cubic);
}

.accordion[open] .accordion__icon { transform: rotate(45deg); }

.accordion__panel {
  padding-block-end: var(--space-medium);
  transition-timing-function: var(--ease-out);
}

/* --- Button accordion ------------------------------------------------------
   The reference drives accordions from a <button> + panel pair with an
   .is-active class rather than <details>, because the panel animates its
   height and <details> cannot be transitioned. Both shapes share the
   .accordion root; this half is what the facets use.
   --------------------------------------------------------------------------- */

.accordion__header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding-block: var(--space-medium-plus);
  border-top: solid 1px var(--beige-200);
  cursor: pointer;

  /* This is a <button>, and the UA sheet centres a button's text. Nothing of
     ours asked for that and it is invisible on a question that fits one line —
     the title box shrink-wraps, so there is nothing for the centring to move
     it within. The moment a question wraps, the short line centres over the
     long one and that row alone reads as centred while every row around it is
     ranged left. Seen on the phone first, but it is not a width problem: the
     same happens on the desktop as soon as a question is long enough. */
  text-align: start;
}

.accordion__header__wrapper {
  display: flex;
  align-items: center;
  gap: var(--space-small);
}

/* A question is a sentence, not a label. .text-ui-large is the site's UI-label
   class — uppercase and tracked out, which is right on a button and wrong on
   "Kako se poručuje?". The size it carries is the one we want, so the caps are
   undone here rather than the class swapped in nineteen places; the inner
   selector is for the filter drawer, which puts the utility on a span inside a
   <legend> where the outer rule cannot reach it. */
.accordion__title,
.accordion__title .text-ui-large {
  text-transform: none;
  letter-spacing: normal;
}

.accordion__title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-small);
  margin: 0;
  font-weight: var(--font-weight-medium);
}

.accordion__inner {
  padding-top: var(--space-small);
  padding-bottom: var(--space-xlarge);
  color: var(--ui-500);
}

.accordion .accordion__icon {
  width: 18px;
  height: 18px;
  color: var(--ui-500);
  transition: color var(--duration-fast) var(--ease-out);
}

/* The caret points down when closed and up when open — one element rotated,
   not two swapped, so the movement reads as a hinge. */
.accordion .accordion__icon--caret {
  transform: rotate(90deg);
  transition: transform var(--duration-fast) var(--ease-out);
}

.accordion.is-active .accordion__icon--caret { transform: rotate(-90deg); }

/* Collapsed by default; JS sets the pixel height so it can transition.

   padding-block-end must be zeroed here: the <details> variant above sets it
   on .accordion__panel, and padding is NOT clipped by height:0 — a collapsed
   panel would keep 16px of it and the stack would never close flush. In this
   variant the padding belongs to .accordion__inner, which is inside the
   clipped box. */
.accordion__panel[data-accordion-panel] {
  overflow: hidden;
  height: 0;
  padding-block-end: 0;
  /* 250ms rather than --duration-medium's 350.

     Height is a layout property: every frame of this re-lays the panel and
     everything under it, so the longer it runs the more chances there are to
     drop one. Measured against nvgallery, whose filter this component is drawn
     from — same parent chain down to the class names, same ease-out, a panel
     with 110 nodes against our 18, and no containment on either. The one thing
     they do differently is run it in 250. */
  transition: height 250ms var(--ease-out);
}

@media (min-width: 768px) {
  .accordion__inner {
    padding-top: var(--space-small-plus);
    padding-right: var(--space-xlarge);
  }
}

/* --- Price range slider ----------------------------------------------------
   Two <input type=range> stacked in the same 4px track: the lower one paints
   the track and takes pointer events, the upper one is transparent and only
   its thumb is clickable. That is what allows two thumbs on one bar without
   a custom control.
   --------------------------------------------------------------------------- */

.sliders-input {
  --sliders-input-background: var(--ui-100);
  --sliders-input-info-color: var(--ui-600);
  --sliders-input-thumb-color: var(--ui-600);
  --sliders-input-track-height: 4px;
  --sliders-input-track-radius: var(--radius-pill);
  --sliders-input-thumb-size: 14px;
  --sliders-input-thumb-hit-padding: var(--space-small);
  --sliders-input-gap-y: var(--space-small-plus);
  --sliders-input-focus-ring-color: var(--ui-300);
  --sliders-input-focus-ring-width: 3px;
  --sliders-input-disabled-opacity: 0.5;
}

.sliders-input__info {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sliders-input-gap-y);
  color: var(--sliders-input-info-color);
}

.sliders-input__value-display {
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
}

.sliders-input__value-display.text-bold {
  font-weight: var(--font-weight-medium);
  color: var(--ui-950);
}

.sliders-input__wrapper {
  position: relative;
  height: var(--sliders-input-track-height);
  /* Room for what stands outside the track.
   *
   * The thumbs are 14px on a 4px rail, so they reach 5px past the wrapper top
   * and bottom, and the focus ring another 3px. Both inputs are absolutely
   * positioned, and an absolutely positioned child adds nothing to its
   * parent's scrollHeight — which is the number the facet accordion animates
   * its height to. So the panel measured this row as four pixels tall and its
   * overflow:hidden sliced the thumbs in half.
   *
   * Margin puts the missing height back into the measurement, so the panel
   * opens tall enough to contain the control it is holding. */
  margin-block: calc(
    (var(--sliders-input-thumb-size) - var(--sliders-input-track-height)) / 2
    + var(--sliders-input-focus-ring-width)
  );
}

.sliders-input__input {
  position: absolute;
  width: 100%;
  height: calc(var(--sliders-input-thumb-size) + var(--sliders-input-thumb-hit-padding) * 2);
  appearance: none;
  pointer-events: none;
  /* A range needs the gesture. Left to `auto` the compositor reads a drag that
     starts here as a scroll and the thumb never sees it, which is why the
     control could not be moved by touch at all. */
  touch-action: none;
}

/* The filled portion is a gradient on the lower input, repainted by JS. */
.sliders-input__input--to {
  top: 0;
  left: 0;
  height: 100%;
  border-radius: var(--sliders-input-track-radius);
  background: linear-gradient(to right, var(--sliders-input-background) 0%, var(--sliders-input-background) 100%);
}

.sliders-input__input--from {
  top: 0;
  left: 0;
  height: 100%;
  z-index: 1;
  background: transparent;
  pointer-events: none;
}

.sliders-input__input::-webkit-slider-runnable-track {
  height: var(--sliders-input-track-height);
  border-radius: var(--sliders-input-track-radius);
  background: transparent;
}

.sliders-input__input::-moz-range-track {
  height: var(--sliders-input-track-height);
  border-radius: var(--sliders-input-track-radius);
  background: transparent;
}

/* The thumb carries no shadow.
 *
 * It used to: two drop-shadows at 2% and 8% black. But the hit area is made by
 * giving the thumb 8px transparent borders top and bottom — a 14px circle
 * painted inside a 14x30 box, via background-clip — and a filter is cast by
 * that box, not by what is painted in it. So the shadow came out as a soft
 * 14x30 ellipse standing behind a 14px dot: a vertical smudge above and below
 * the handle, which is what it looked like on a phone. Measured by setting
 * `filter: none` on the live control, which removed it and left the circle
 * clean.
 *
 * box-shadow would do the same thing, for the same reason — it is cast by the
 * border box too. A shadow that follows the circle would have to be painted
 * into the background image. It is not worth that: the handle is --ui-600 on a
 * light rail and reads perfectly well flat. */
.sliders-input__input::-webkit-slider-thumb {
  appearance: none;
  pointer-events: all;
  width: var(--sliders-input-thumb-size);
  height: var(--sliders-input-thumb-size);
  border-radius: 50%;
  cursor: pointer;
  background-color: var(--sliders-input-thumb-color);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='6' fill='none' viewBox='0 0 8 10'%3e%3cpath stroke='white' stroke-width='1.5' d='M.934 0v10m6-10v10'/%3e%3c/svg%3e");
  background-position: center;
  background-repeat: no-repeat;
  box-sizing: content-box;
  border-width: var(--sliders-input-thumb-hit-padding) 0;
  border-style: solid;
  border-color: transparent;
  background-clip: padding-box;
  margin-top: calc((var(--sliders-input-track-height) - var(--sliders-input-thumb-size)) / 2 - var(--sliders-input-thumb-hit-padding));
  transition: transform var(--duration-x-fast) var(--ease-out);
}

.sliders-input__input::-moz-range-thumb {
  appearance: none;
  pointer-events: all;
  width: var(--sliders-input-thumb-size);
  height: var(--sliders-input-thumb-size);
  border-radius: 50%;
  cursor: pointer;
  background-color: var(--sliders-input-thumb-color);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='6' fill='none' viewBox='0 0 8 10'%3e%3cpath stroke='white' stroke-width='1.5' d='M.934 0v10m6-10v10'/%3e%3c/svg%3e");
  background-position: center;
  background-repeat: no-repeat;
  box-sizing: content-box;
  border-width: var(--sliders-input-thumb-hit-padding) 0;
  border-style: solid;
  border-color: transparent;
  background-clip: padding-box;
  transition: transform var(--duration-x-fast) var(--ease-out);
}

.sliders-input__input::-webkit-slider-thumb:hover { transform: scale(1.05); }
.sliders-input__input::-webkit-slider-thumb:active { transform: scale(0.97); }
.sliders-input__input::-moz-range-thumb:hover { transform: scale(1.05); }
.sliders-input__input::-moz-range-thumb:active { transform: scale(0.97); }

.sliders-input__input::-webkit-slider-thumb:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--sliders-input-focus-ring-width) var(--sliders-input-focus-ring-color);
}

.sliders-input__input:disabled { opacity: var(--sliders-input-disabled-opacity); cursor: not-allowed; }

/* --------------------------------------------------------------------------
   FOOTER REASSURANCE — four promises across the 24-column grid.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   FOOTER — one block. Reassurance, body and bottom are separate sections but
   must read as a single mass, so nothing between them carries a gap. The
   reference does the same with `.footer section { margin-top: 0 }`.
   -------------------------------------------------------------------------- */

.footer > * { margin-block: 0; }

.footer-reassurance {
  background-color: var(--footer-reassurance-background-color, var(--ui-800));
  border-bottom: 1px solid var(--footer-reassurance-divider-color, var(--ui-700));
}

.footer-reassurance__wrapper { --padding-inline: var(--gutters-side); }

/* One span serves both breakpoints because the column COUNT changes beneath
   it: span 6 of 12 is two across on mobile, span 6 of 24 is four on desktop.
   #264 (04.09.2026) took the phone row full-width instead, to fit --title-h6
   (24px) without wrapping every title — Niko's phone the next day, 05.09:
   "these titles are too big… we need two columns". Two columns are back; the
   title takes a size between the two rather than either extreme (see
   .footer-reassurance__item__title below for the arithmetic that size is
   built on). */
.footer-reassurance__item {
  grid-column: span 6;

  /* Three of these four are links and one is a plain div. .link is the TEXT
     link component — inline-flex, width/height fit-content — so it was
     shrink-wrapping three of the cells and leaving them a different height
     from the fourth. The item owns its own box so the element it happens to be
     stops showing through. */
  display: block;
  width: auto;
  height: auto;
  text-align: start;
  color: inherit;
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-out);
}

/* Three of the four are anchors. They used to carry .link for its hover colour
   and inherited its text-link layout with it; the hover lives here instead so
   the item needs nothing but its own class. */
a.footer-reassurance__item:hover .footer-reassurance__item__title { color: var(--ui-300); }

@media (min-width: 1024px) {
  .footer-reassurance__item { padding-block: var(--space-large); }
}

@media (max-width: 1023px) {
  .footer-reassurance__wrapper {
    gap: var(--space-large-plus) var(--space-large);
    padding-block: var(--space-xlarge);
  }
}

/* Icon beside the text on mobile, stacked above it on desktop. Aligned to the
   START, not centred: these four items have wildly different text lengths, and
   centring parked the truck in the middle of a seven-line paragraph while the
   one-line items kept theirs beside the title. Top alignment puts every icon
   on its own title regardless of how much text follows. */
.footer-reassurance__item__content {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xsmall);
}

/* Optical: the icon box is 20px and the title's line box is taller, so nudge
   the icon onto the title's centre line rather than its ascender. */
.footer-reassurance__item__icon { margin-block-start: 2px; }

@media (min-width: 1024px) {
  .footer-reassurance__item__content {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-medium);
  }
}

.footer-reassurance__item__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
}

/* One face at every width. The reference switches this to the sans below 1024
   and to the serif above it, which was ported faithfully — but a heading that
   changes typeface at a breakpoint reads as two different components rather
   than one at two sizes, so the display face carries it throughout. */
.footer-reassurance__item__title {
  font-family: var(--font-serif-condensed);
  font-size: var(--title-h6-size);
  line-height: var(--title-h6-lh);
  letter-spacing: var(--tracking-display);
  font-weight: var(--font-display-weight);
  font-variant-ligatures: no-contextual;
}

/* Below 1024 the item is back to span 6 (two per row, see
   .footer-reassurance__item above) — Niko, from his phone, 05.09.2026, the
   day after #264 shipped: "these titles are too big za mobile... treba dve
   kolone". #264's own --title-h6 (24px) does not fit two columns: simulating
   CSS's greedy word-wrap against the real glyph widths in fonts/sklad.woff2
   (fontTools, unitsPerEm 1000, no fallback glyphs) at the column's text width
   ((viewport - 2*16 gutter - 24 column-gap) / 2, minus the 20px icon and 6px
   icon-gap — 133.5px at 375, 141px at 390) puts all four titles onto 2 lines
   at every size from 18px up, "Pomoć pri izboru komada" onto 3. Neither
   candidate the task asked for (18/19/20px, 1.125rem/1.25rem) leaves three of
   the four on one line — the column is only ~9px wider than the 15px title
   already used. Narrowing the sweep to find where it still holds:

     15.5px and below: 3 of 4 hold to one line at 375 AND 390 ("Pomoć…" at 2).
     16px–16.3px:      3 of 4 hold at 390; at 375 "Dizajn i izrada u Srbiji"
                        also drops to 2 lines (never 3) — one more wrap than
                        the rule asks for, on the narrowest supported phone.
     16.4px and above: a second title starts slipping at 375 too.

   1rem (16px) is the pick: a clean step rather than the 15.5px the strict
   rule allows, one full rem above --text-large, --text-large-plus in
   tokens.css. It is a visible size increase over the old 15px, satisfies the
   rule outright at 390, and at 375 costs exactly one title an extra line
   (still 2, never 3) rather than the three-of-four the rule wants — a
   trade-off, not a clean pass, and worth a reviewer's eyes on a real 375
   phone before this ships. */
@media (max-width: 1023px) {
  .footer-reassurance__item__title {
    font-size: var(--text-large-plus-size);
    line-height: var(--text-large-plus-lh);
  }
}

.footer-reassurance__item__subtitle {
  margin-top: var(--space-xsmall);
  color: var(--ui-500);
}

@media (min-width: 1024px) {
  .footer-reassurance__item__subtitle { margin-top: var(--space-small); }
}

/* The mailto: and tel: links inside the contact reassurance item inherit
   .text-medium's 18px line height — 6px short of the 24x24 CSS px WCAG 2.5.8
   target size, which is what Lighthouse's touch-target audit flagged for
   both. padding-block makes up the difference; the matching negative
   margin-block cancels it back out of the document flow so the reassurance
   row's height and the footer beneath it do not move. Scoped to this class,
   not every anchor — the other three reassurance items are already full-tile
   links sized well past the minimum. */
.footer-reassurance__contact-link {
  display: inline-block;
  padding-block: 3px;
  margin-block: -3px;
  min-height: 24px;
}

/* --------------------------------------------------------------------------
   FOOTER BODY — newsletter on the left, three link columns on the right.
   -------------------------------------------------------------------------- */

.footer-body { background-color: var(--footer-body-background-color, var(--ui-800)); }

.footer-body__content {
  --padding-inline: var(--gutters-side);
  padding-block: var(--space-large-plus);
}

@media (min-width: 1024px) {
  .footer-body__content { padding-block: var(--space-large-plus) var(--space-medium-plus); }
}

.footer-body__left,
.footer-body__right { grid-column: span 12; }

.footer-body__right { display: flex; flex-direction: column; }

@media (min-width: 1024px) {
  .footer-body__left  { grid-column: span 8; }
  /* Columns 13–24, leaving 9–12 as breathing room against the newsletter. */
  .footer-body__right {
    grid-column: 13 / 25;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: min-content;
    gap: var(--space-large);
  }
}

/* The reference ships each menu twice and swaps them by display: a plain
   column on desktop, a collapsible accordion on mobile. */
.footer-menu--desktop { display: none; }

@media (min-width: 1024px) { .footer-menu--desktop { display: block; } }

.footer-menu--mobile { order: 2; }

@media (min-width: 1024px) { .footer-menu--mobile { display: none; } }

/* A column is a 15/22 medium-weight title over a 14/18 list of --ui-500
   links: 22 + 16 + (8 x 18) + (7 x 8) = 238px for an eight-item column. */
.footer-menu__title {
  margin-bottom: var(--space-medium);
  font-weight: var(--font-weight-medium);
}

.footer-menu .footer-menu__items {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  list-style: none;
}

.footer-menu__link {
  --link-hover-color: var(--ui-700);
  font-weight: var(--font-weight-regular);
  color: var(--ui-500);
}

/* --------------------------------------------------------------------------
   FOOTER BOTTOM — certification strip above a legal row.
   -------------------------------------------------------------------------- */

.footer-bottom {
  display: flex;
  flex-direction: column-reverse;
  gap: var(--space-xlarge-plus);
  background-color: var(--footer-bottom-background-color, var(--ui-800));
}

@media (min-width: 1024px) {
  .footer-bottom { flex-direction: column; gap: unset; }
}

.footer-bottom__wrapper__inner {
  --padding-inline: var(--gutters-side);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-medium);
}

/* The wrapper's padding is declared in the desktop block below, next to the
   divider it belongs with — so on a phone the last row of the footer, which is
   the cookie settings link, sat flush against the bottom of the page with
   nothing under it.

   The divider is desktop-only on purpose: below 1024 the bottom row is
   column-reversed and a rule above it would be a line in the middle of the
   footer. The breathing room is not desktop-only, and it takes the footer
   body's mobile step rather than the desktop 24, because on a phone this is the
   last thing on the page and there is nothing else holding it off the edge. */
.footer-bottom__wrapper { padding-block-end: var(--space-large-plus); }

/* Both inners are column-stacked on mobile and revert to the 24-column grid
   on desktop — the flex rules above would otherwise beat the .grid class. */
@media (min-width: 1024px) {
  .footer-bottom__wrapper__inner {
    display: grid;
    grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
    grid-auto-flow: row;
    gap: 0 var(--gutters);
    width: 100%;
    max-width: var(--max-width-container);
    margin-inline: auto;
  }  .footer-bottom__wrapper {
    padding-block: var(--space-large);
    border-top: 1px solid var(--footer-bottom-divider-color, var(--ui-700));
  }
}

.footer-bottom__left {
  grid-column: span 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-medium);
}

@media (min-width: 1024px) {
  .footer-bottom__left { align-items: flex-start; gap: var(--space-small); }
}

.footer-bottom__center {
  grid-column: span 12;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 18px;
}

@media (min-width: 1024px) {
  .footer-bottom__center { grid-column: 7 / span 12; gap: var(--space-medium); }
}

.footer-bottom__center .footer-bottom__icon--payment {
  display: inline-flex;
  height: var(--space-medium-plus);
  color: var(--beige-500);
}

@media (min-width: 1024px) {
  .footer-bottom__center .footer-bottom__icon--payment { height: var(--space-medium); }
}

.footer-bottom__right {
  grid-column: span 12;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: var(--space-large);
}

@media (min-width: 1024px) {
  .footer-bottom__right { grid-column: 19 / -1; }
}/* Small wordmark above the copyright line. */
.footer-bottom__logo {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  height: var(--space-medium);
  font-family: var(--font-serif-condensed);
  letter-spacing: var(--tracking-display);
  font-size: var(--text-ui-small-size);
  line-height: 1;
  font-variant-ligatures: no-contextual;
}

@media (min-width: 1024px) {
  .footer-bottom__logo { height: var(--space-small-plus); }
}

.footer-bottom .copyright {
  /* Was --text-ui-small, which left it at 10px while everything beside it in
     the same row — the payment marks, the cookie link — set at 14. The size
     now comes from the .text-medium the markup carries; only the colour, which
     was tuned for contrast on the dark ground, stays here. */
  color: var(--ui-500);
}

.footer-bottom__menu-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-small);
  row-gap: var(--space-xsmall);
}

.footer-bottom__menu-link .link__text {
  font-size: var(--text-ui-small-size);
  line-height: var(--text-ui-small-lh);
}

.footer-bottom__hyphen {
  font-size: var(--text-ui-small-size);
  line-height: var(--text-xsmall-lh);
  color: var(--beige-250);
}

.footer-bottom__hyphen:last-child { display: none; }

/* --------------------------------------------------------------------------
   FOOTER ON A DARK GROUND

   The three footer bands sit on --ui-800. The grounds themselves are the token
   defaults on each band above, so this section is only the consequence: the
   type was drawn for a beige footer, and almost none of it is legible once the
   ground inverts.

   Every value here is a contrast fix with a measured reason, not a preference.
   The pattern throughout is to re-point the hook a component already reads
   (--link-color, --link-hover-color) rather than to fight it with a more
   specific colour rule.
   -------------------------------------------------------------------------- */

.footer-reassurance,
.footer-body,
.footer-bottom { color: var(--ui-00); }

/* Subtitles were --ui-500 on beige; that is 2.6:1 on the dark ground. */
.footer-reassurance .footer-reassurance__item__subtitle { color: var(--ui-300); }

/* Three of the four reassurance items are <a class="sklad-link">-style links,
   so they take --link-color (default --ui-950) rather than inheriting the
   section colour. Point the hook instead of overriding it. */
.footer-reassurance .link {
  --link-color: var(--ui-00);
  --link-hover-color: var(--ui-300);
}

/* Headings and primary links go white; secondary text sits at --ui-300, which
   holds 7.4:1 on --ui-800. */
.footer-body .footer-menu__title,
.footer-body .newsletter__title,
.footer-bottom .footer-bottom__left,
.footer-bottom .footer-bottom__center,
.footer-bottom .footer-bottom__right { color: var(--ui-00); }

.footer-body .footer-menu__link,
.footer-body .newsletter__description,
.footer-bottom a,
.footer-bottom span { color: var(--ui-300); }

.footer-body .footer-menu__link,
.footer-bottom .footer-menu__link { --link-hover-color: var(--ui-00); }
.footer-body .footer-menu__link:hover,
.footer-bottom a:hover { color: var(--ui-00); }

/* The newsletter field keeps a light ground so it stays a usable input. */
.footer-body .newsletter__form .input {
  background-color: var(--ui-00);
  border-color: var(--ui-600);
  color: var(--ui-950);
}

/* The consent is muted prose with only the policy name linked. */
.footer-body .newsletter__consent { color: var(--ui-300); }

.footer-body .newsletter__consent .link {
  --link-color: var(--ui-300);
  --link-hover-color: var(--ui-00);
  --link-underline-color: var(--ui-600);
}

/* Checkbox and label follow the inverted ground. */
.footer-body .checkbox__wrapper { color: var(--ui-300); }
.footer-body .checkbox:checked { color: var(--ui-00); }

/* The rest of the low-contrast leftovers: the privacy label (--ui-600, 2.1:1),
   the mobile accordion icons and copyright (--ui-500, 3.0:1), and the payment
   and certification marks (--beige-500, 3.9:1). All go to --ui-300, 6.6:1. */.footer-body .text-muted,
.footer-body .accordion__icon,
.footer-bottom .copyright,
.footer-bottom .footer-bottom__icon--payment{ color: var(--ui-300); }

/* Divider hairlines were tuned for a beige ground. */.footer-body .accordion{ border-color: var(--ui-700); }

/* --------------------------------------------------------------------------
   PANEL — a full-screen fixed shell holding a right-anchored drawer.

   The shell is always full-viewport; the visible sheet is .sklad-panel__inner,
   whose width is the design-ratio pair again (568px in the 1448 mockup):
   fluid against the viewport, capped against the container.
   -------------------------------------------------------------------------- */

.sklad-panel {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: var(--z-panel);

  visibility: hidden;
  transition: visibility var(--duration-medium);

  /* A closed panel covers the whole viewport — position: fixed, inset: 0 —
     and the only thing keeping it out of the way is visibility. That is
     enough at rest, but visibility is transitioned here, and a transitioned
     visibility does not fade: going visible -> hidden it stays *visible*,
     and hit-testable, for the whole 0.35s while the inner sheet slides out.
     Every tap in that window lands on the closing panel rather than on the
     page under it.

     pointer-events is not transitioned, so it flips at once and closes that
     window. At rest this changes nothing, because visibility already hides
     the panel from hit-testing. */
  pointer-events: none;
}

.sklad-panel.is-open { visibility: visible; pointer-events: auto; }

/* Both drawers share one mechanism — a transition keyed on .is-open, never a
   keyframe. That is what keeps left and right behaving identically: same
   duration, same easing, same close path. */
.sklad-panel__overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background-color: var(--ui-950);
  opacity: 0;
  transition: opacity var(--duration-medium) var(--ease-out-circ);
}

.sklad-panel.is-open .sklad-panel__overlay { opacity: 0.7; }

.sklad-panel__inner {
  position: absolute;
  top: 0;
  right: 0;
  /* The shell's height, not a viewport unit.
   *
   * This was 100svh — the SMALL viewport, which is the height with the browser
   * chrome fully expanded. A drawer is usually opened from the sticky bar,
   * which means after a scroll, which on iOS is exactly when Safari's toolbar
   * has collapsed and the visible area is larger than svh. The sheet then
   * stopped short of the bottom and the dimmed page showed underneath it, in a
   * strip the width of the screen.
   *
   * .sklad-panel is fixed with inset:0 and .sklad-panel__overlay already takes
   * its height from it, which is why the dim covered the screen while the sheet
   * did not. Taking it from the same place is what keeps the two agreeing, in
   * whatever state the chrome happens to be. */
  height: 100%;
  width: 100%;

  display: flex;
  flex-direction: column;
  background-color: var(--ui-00);

  overflow: hidden auto;
  overscroll-behavior: none;
  scrollbar-width: none;

  transform: translateX(100%);
  transition: transform var(--duration-medium) var(--ease-out-expo);
}

.sklad-panel__inner::-webkit-scrollbar { width: 0; display: none; }

.sklad-panel.is-open .sklad-panel__inner { transform: translateX(0); }

@media (min-width: 1024px) {
  .sklad-panel__inner {
    width: calc(568 * var(--design-ratio-fluid));
    max-width: calc(568 * var(--design-ratio));
  }
}

/* Inline padding belongs to the children, so a sticky header or footer can
   still span the full sheet width. */
.sklad-panel__inner > * { padding-inline: var(--space-medium); }

@media (min-width: 1024px) {
  .sklad-panel__inner > * { padding-inline: var(--space-large); }
}

.sklad-panel__header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  padding-top: calc(var(--space-xlarge) / 2);
  padding-bottom: var(--space-large);
  color: var(--ui-500);
  font-family: var(--font-sans-serif);
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
}

@media (min-width: 1024px) {
  .sklad-panel__header { padding-top: calc(var(--space-xlarge-plus) / 2); }
}

.sklad-panel__title {
  font-family: var(--font-serif-condensed);
  /* Between the 1.875rem it started at and the 0.9375rem that replaced it.
     At the smaller size the serif's parentheses closed around the count
     tightly enough that "Cart (4)" read as a circled numeral. */
  font-size: var(--title-h6-size);
  line-height: 1.2;
  letter-spacing: var(--tracking-display);
  color: var(--ui-950);
  font-weight: var(--font-weight-regular);
  font-variant-ligatures: no-contextual;
}

.sklad-panel__body { flex: 1; }

.sklad-panel__footer {
  position: sticky;
  bottom: 0;
  z-index: 1;
  background-color: var(--ui-00);
  border-top: 1px solid var(--ui-200);
  box-shadow: 0 -2px 6px rgb(0 0 0 / 6%);
  padding-block: var(--space-small-plus)
    calc(var(--space-small-plus) + env(safe-area-inset-bottom, 0px));
}

@media (min-width: 1024px) {
  .sklad-panel__footer { padding-block: var(--space-medium); }
}

/* Hit area extends past the glyph without moving it. */
/* One close, at one size, in every drawer. The mark was 13px inside a 41px
   target — small enough to look like an afterthought next to the title it
   sits beside. */
.sklad-panel__close,
.panel-navigation__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: -10px -12px -10px 0;
  padding: 0;
}

.sklad-panel__close .icon,
.panel-navigation__close .icon { --icon-size: 24px; --icon-stroke: 1.5; }

/* --- Variants ----------------------------------------------------------- */

/* Opens from the left instead of the right. */
.sklad-panel--left .sklad-panel__inner {
  right: auto;
  left: 0;
  transform: translateX(-100%);
}

.sklad-panel--left.is-open .sklad-panel__inner { transform: translateX(0); }

/* A blurred scrim rather than a flat wash. Opacity still animates the same
   way; only the fill and the blur differ. */
.sklad-panel--overlay-blur .sklad-panel__overlay {
  background: var(--ui-950-20);
  backdrop-filter: var(--blur-24);
}

.sklad-panel.sklad-panel--overlay-blur.is-open .sklad-panel__overlay { opacity: 1; }

/* --------------------------------------------------------------------------
   PANEL NAVIGATION — the menu drawer.

   Unlike the cart (which uses the base right-hand sheet), this one is sized
   in GRID COLUMNS: nine of twenty-four. Its overlay is pinned to the right
   and spans only the width the sheet leaves behind, so the blur sits beside
   the panel rather than under it.
   -------------------------------------------------------------------------- */

.panel-navigation {
  --inner-width: calc(
    min(var(--viewport-width), var(--max-width-container)) / var(--grid-columns) * 9
  );
  --overlay-width: calc(var(--viewport-width) - var(--inner-width));
}

/* Spans the full viewport like the cart's, so clicking anywhere outside the
   sheet closes it on both sides. (The reference pins this one to the right at
   --overlay-width; covering the whole viewport keeps the two drawers
   behaving identically, which matters more here.) */
.panel-navigation.sklad-panel--overlay-blur .sklad-panel__overlay {
  inset: 0;
  width: 100%;
}

@media (min-width: 1024px) {
  .panel-navigation .sklad-panel__inner {
    width: var(--inner-width);
    max-width: unset;
  }
}

/* Below 1024 the drawer changes sides and comes in from the right. The base
   panel is right-anchored already, so this only undoes the --left variant on
   phones — same anchoring and transform the cart uses, same transition keyed
   on .is-open, so the slide behaves identically to every other drawer.
   Desktop stays on the left: its trigger ("Shop") sits at the header's left
   edge, and the sheet opens under it. */
@media (max-width: 1023px) {
  .panel-navigation.sklad-panel--left .sklad-panel__inner {
    right: 0;
    left: auto;
    transform: translateX(100%);
  }

  .panel-navigation.sklad-panel--left.is-open .sklad-panel__inner { transform: translateX(0); }
}

/* Sticky search row at the very top of the sheet. */
.panel-navigation .sklad-panel__top-header {
  position: sticky;
  top: 0;
  z-index: 3;
  background-color: var(--ui-00);
  padding-block: 0.625rem;
  border-bottom: 1px solid var(--beige-100);
}

@media (min-width: 1024px) {
  .panel-navigation .sklad-panel__top-header { padding-block: var(--space-medium-plus); }
}

/* Row on mobile (close on the right); a column on desktop with the close
   ABOVE the tab toggle — hence order:1 / order:2. */
/* The drawer's header row. The close sits at the trailing edge, as in every
   other drawer; at the second level a back control appears at the leading
   edge, so one header gains a way back rather than a second bar appearing
   under the first. */
.panel-navigation__top-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-medium);
}

/* Nothing at the first level, so the close stays where it is. */
.panel-navigation__back[hidden] { display: none; }

.panel-navigation__back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-small);
  margin-inline-start: -6px;
  padding: 0;
  color: var(--ui-950);
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  transition: color var(--duration-fast) var(--ease-out);
}

.panel-navigation__back-icon { display: inline-flex; }
.panel-navigation__back-icon .icon { --icon-size: 22px; --icon-stroke: 1.5; }

@media (hover: hover) and (pointer: fine) {
  .panel-navigation__back:hover { color: var(--ui-600); }
}

/* The close is the only thing in the row at the first level, so it has to be
   pushed over on its own rather than relying on a sibling. */
.panel-navigation__back[hidden] + .panel-navigation__close { margin-inline-start: auto; }

@media (min-width: 1024px) {
  .panel-navigation__close { order: 1; }
}

.panel-navigation__menu-toggle {
  transition: opacity var(--duration-medium) var(--ease-out-cubic);
}

@media (min-width: 1024px) {
  .panel-navigation__menu-toggle {
    order: 2;
    margin-top: var(--space-large-plus);
  }
}

.panel-navigation__body { position: relative; }

.panel-navigation__footer {
  display: flex;
  flex-direction: column;
}

.panel-navigation__footer__nav {
  --link-color: var(--ui-950);
  --link-hover-color: var(--ui-600);
  display: flex;
  flex-direction: column;
  gap: var(--space-small-plus);
  padding-bottom: var(--space-small-plus);
  border-bottom: 1px solid var(--beige-200);
}

.panel-navigation__footer__nav-link {
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  font-weight: var(--font-weight-medium);
}

.panel-navigation__footer__lang-switcher {
  flex: 1;
  width: 100%;
  justify-content: flex-start;
  margin-top: var(--space-small-plus);

  /* It read --ui-500 from .link--plain's own fallback. Now that it is an
     ordinary type-2 link, the contract has to say so — the base .link falls
     back to --ui-950, which would make a utility switcher as loud as the
     navigation above it. */
  --link-color: var(--ui-500);
  --link-hover-color: var(--ui-950);
}

/* The title row is desktop-only here — on mobile the search row leads. */
.panel-navigation .sklad-panel__header { display: none; }

@media (min-width: 1024px) {
  .panel-navigation .sklad-panel__header {
    display: flex;
    z-index: 0;
    padding-block: 0.75rem var(--space-medium-plus);
    border-bottom: 1px solid var(--ui-100);
  }
}

.panel-navigation .sklad-panel__footer {
  position: relative;
  border-top: none;
  box-shadow: none;
  background: var(--beige-100);
  padding-block: var(--space-medium);
}

@media (min-width: 1024px) {
  .panel-navigation .sklad-panel__footer { padding-block: var(--space-large); }
}

/* --------------------------------------------------------------------------
   PANEL MAIN NAVIGATION — the drawer's link list.
   -------------------------------------------------------------------------- */

.panel-main-navigation { padding-block: var(--space-medium); }

.panel-main-navigation__list {
  display: flex;
  flex-direction: column;
}

.panel-main-navigation__list-item {
  display: flex;
  height: 56px;
}

.panel-main-navigation__item {
  flex: 1;
  display: flex;
  align-items: center;
  text-align: left;
  gap: var(--space-medium);
  color: var(--ui-950);
  transition: color var(--duration-fast) var(--ease-out);
}

.panel-main-navigation__item[href]:hover { color: var(--ui-600); }

.panel-main-navigation__item__title {
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  /* Was flex: 1, which stretched it across the whole row so the underline
     would have run under 180px of empty space. margin-right:auto does the same
     job — pushing the chevron to the right edge — while the title hugs its
     own text. */
  flex: 0 1 auto;
  margin-right: auto;
  font-weight: var(--font-weight-medium);
}

/* The chevron is a marker inside the row, not its own button — the whole row
   opens the second level, so a separate hit area would be a second control for
   the same thing. */
.panel-main-navigation__item__icon {
  flex: 0 0 auto;
  display: inline-flex;
  color: var(--ui-500);
  transition: transform var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.panel-main-navigation__item__icon .icon { --icon-size: 22px; --icon-stroke: 1.5; }

.panel-main-navigation__item:hover .panel-main-navigation__item__icon {
  color: var(--ui-950);
  transform: translateX(2px);
}

/* --- Item thumbnail ---------------------------------------------------------
   The reference puts a 56px square photograph beside every category rather
   than a glyph: a picture of the thing is a better label for furniture than an
   icon of a chair would be, and the set never needs drawing.
   ---------------------------------------------------------------------------- */

.panel-main-navigation__item__media {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  overflow: hidden;
  background-color: var(--surface-muted);
}

.panel-main-navigation__item__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The row is the height of its thumbnail, and the gap matches the reference. */
/* The row is the whole control on a parent item, so it fills the width and
   pushes its chevron to the right edge. */
.panel-main-navigation__item {
  gap: var(--space-medium);
  min-height: 56px;
  width: 100%;
  cursor: pointer;
}

/* Air between rows. At 56px the thumbnails sat edge to edge and read as one
   strip of images rather than as separate destinations. */
.panel-main-navigation__list-item + .panel-main-navigation__list-item {
  margin-block-start: var(--space-medium);
}

/* --- Second level -----------------------------------------------------------
   Slides over the first rather than expanding inside it. An accordion pushes
   everything below it down the screen; a panel keeps one list on screen at a
   time, which is what a drawer this narrow can actually show.
   ---------------------------------------------------------------------------- */

/* In flow, not absolute. It was `position: absolute; inset: 0`, but hiding the
   first-level list leaves .panel-main-navigation with no in-flow content and
   therefore no height — so the panel resolved to zero and only its back button
   showed. Nothing needs to overlap here: the list is hidden, so the submenu can
   simply take its place. */
.panel-submenu {
  display: flex;
  flex-direction: column;
  animation: panel-submenu-in var(--duration-medium) var(--ease-out);
}

.panel-submenu[hidden] { display: none; }

@keyframes panel-submenu-in {
  from { transform: translateX(16px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .panel-submenu { animation: none; }
}
.panel-submenu__list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

.panel-submenu__link {
  display: flex;
  align-items: center;
  gap: var(--space-medium);
  min-height: 56px;
  color: var(--ui-950);
  transition: color var(--duration-fast) var(--ease-out);
}

.panel-submenu__link:hover { color: var(--ui-600); }

.panel-submenu__link-title {
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
}

.panel-submenu__list-item + .panel-submenu__list-item {
  margin-block-start: var(--space-medium);
}

/* --------------------------------------------------------------------------
   BREADCRUMB — a single flex row that inherits its colour from whatever it
   sits on, which is why it works both over a dark hero and on white. The
   separator is a ::before on every item except the first, so no separator
   spans are needed in the markup and the trail can be cut anywhere.
   -------------------------------------------------------------------------- */

.breadcrumb {
  --breadcrumb-offset-right: var(--space-medium-plus);
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  min-width: 0;
}

/* The collection page nests its trail in a drag-scroll carousel, whose
   .carousel__container supplies the flex row. A page header's trail is short
   and needs no carousel, so it uses a plain <ol> — this gives that <ol> the
   same row. */
.breadcrumb__list {
  display: flex;
  align-items: center;
  /* One line, never two. A trail ends with the page you are on, and on a
     story that item carries the whole post title — 66 characters on one of
     the guides. Wrapping moved it to a second line where, being nowrap, it
     simply ran off the right edge: measured at 392 the list was 472px wide
     inside a 309px column. Nothing was clipped, so it read as a title that
     had been cut short. */
  flex-wrap: nowrap;
  min-width: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The ancestors keep their full width — they are the part you can click. The
   current page gives up whatever space is left and ends in an ellipsis; it is
   the least useful text in the row, because you are already on it.

   The clipping is on the item rather than on the list so a link's focus ring
   is not cut off by an ancestor's overflow. */
.breadcrumb__list > .breadcrumb__item { flex: 0 0 auto; }

.breadcrumb__list > .breadcrumb__item:last-child {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.breadcrumb__item {
  color: currentcolor;
  white-space: nowrap;
  opacity: var(--text-opacity, 1);
}

.breadcrumb__item::before {
  content: "/";
  flex-shrink: 0;
  margin: 0 var(--space-xxsmall);
  font-family: var(--font-sans-serif);
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
}

.breadcrumb__item:first-child::before { content: ""; display: none; }
.breadcrumb__item:last-child { margin-right: var(--breadcrumb-offset-right); }

.breadcrumb__item:hover,
.breadcrumb__item--active { opacity: 1; }

/* Lists background-size as well: this rule sits after the shared underline
   block, and `transition: opacity` alone would drop its animation. */
.breadcrumb__link {
  transition:
    opacity         var(--duration-fast) var(--ease-out),
    background-size var(--duration-fast) var(--ease-out);
}

/* The trail sits in a carousel so it can be dragged when it outruns its
   column. The reference uses a JS carousel for that; a scroll container gives
   the same behaviour without one, and keeps the class names identical. */
.breadcrumb .carousel { max-width: 100%; }

.breadcrumb .carousel__viewport {
  overflow: hidden auto;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}

.breadcrumb .carousel__viewport::-webkit-scrollbar { display: none; }

.breadcrumb .carousel__container {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  overflow: visible;
}

/* --------------------------------------------------------------------------
   HERO NAVIGATION — the breadcrumb + result-count bar that overlays the top
   of a collection hero. Pinned rather than in flow, so the hero image runs
   full-bleed underneath it.
   -------------------------------------------------------------------------- */

.hero-navigation {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-small);
  max-width: var(--max-width-container);
  padding-top: var(--space-medium-plus);
  padding-inline: var(--gutters-side);
}

/* Inside a hero the inline padding comes from the grid wrapper, so the nav
   must not add its own — otherwise the breadcrumb and the result count sit one
   gutter further in than everything else on the page (16px mobile, 64px
   desktop). The standalone .hero-navigation padding above is for use outside
   a hero. */
.hero .hero-navigation {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  grid-column: 1 / -1;
  width: 100%;
  padding-inline: 0;
}

.hero-navigation .breadcrumb { flex: 1 1 auto; min-width: 0; }

/* The category's SEO copy, directly under its title rather than at the foot of
   the page. Centred on a short measure so it reads as an introduction to the
   listing — at full grid width it would be a wall of text between the title
   and the products. */
.category-intro {
  max-width: calc(680 * var(--design-ratio));
  margin-inline: auto;
  margin-block-start: var(--space-medium);
  color: var(--ui-600);
  text-align: center;
}

.category-intro p + p { margin-block-start: var(--space-small-plus); }

.product-counts {
  flex: 0 0 auto;
  /* No auto margin any more. It was here to push the count away from the
     breadcrumb it used to share the hero with; on the filter row it competed
     with the sort control's own auto margin, the two split the free space
     between them, and the count came to rest 470px right of the first card
     instead of above it. The row's own justify-content places it now. */
  white-space: nowrap;
  color: currentcolor;
  opacity: var(--text-opacity, 1);
  display: block;
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
}

/* --------------------------------------------------------------------------
   COLLECTION NAV — the sibling-category switcher inside the hero.

   Two presentations of one list, not two components: a scrolling row of
   buttons from 1024 up, and a native <select> below it. The list is visually
   hidden rather than removed on mobile so the links stay in the document for
   crawlers and screen readers.
   -------------------------------------------------------------------------- */

.collection-nav {
  display: flex;
  align-items: center;
  max-width: 100%;
  overflow: scroll auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.collection-nav::-webkit-scrollbar { display: none; }

.collection-nav__list {
  --collection-nav-gap: 10px;
  display: flex;
  gap: var(--collection-nav-gap);
  padding: 0 var(--space-xxlarge-plus) var(--space-small);
}

.collection-nav .sklad-button { flex-shrink: 0; white-space: nowrap; margin-top: var(--space-large); }

/* Inside the mobile dropdown the select must size to its selected option, not
   fill the button — otherwise the label is clipped. */
/* .sklad-button clips its content (overflow: hidden) so its fills stay inside the
   radius; the select has to escape that. */
.collection-nav__mobile-dropdown { display: inline-block; width: fit-content; overflow: visible; }
.collection-nav__mobile-dropdown .sklad-select { width: auto; }
.collection-nav__mobile-dropdown .sklad-select__wrapper { display: flex; }

.collection-nav .sklad-button--grey.is-selected,
.collection-nav .sklad-button--white.is-selected { border-color: var(--ui-950); }
.collection-nav .sklad-button--black.is-selected { background-color: var(--ui-600); }
.collection-nav .sklad-button--frost-black.is-selected { background-color: var(--ui-950-32); }
.collection-nav .sklad-button--frost-white.is-selected { background-color: var(--ui-00); }

@media (max-width: 1023px) {
  .collection-nav__list {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

@media (min-width: 1024px) {
  .collection-nav__mobile-dropdown { display: none; }
}

/* --------------------------------------------------------------------------
   PRODUCT GRID — the listing itself.

   Items span 6 of the page grid, so the column count comes from
   --grid-columns and changes with the layout breakpoint rather than being
   restated here: 12/6 = 2 up on mobile, 24/6 = 4 up on desktop.
   -------------------------------------------------------------------------- */

.grid-products-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  grid-column: 1 / -1;
  margin-top: var(--space-large-plus);
}

.grid-products-header .sklad-select { display: none; }
.grid-products-header .sklad-button { width: fit-content; }

.grid-products__list {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--grid-products-gap-y) var(--grid-products-gap-x);
  padding-top: var(--space-large);
}

.grid-products__item { grid-column: span 6; }
.grid-products__item--grid { grid-column: 1 / -1; }

.grid-products__empty {
  grid-column: 1 / -1;
  height: 50vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: var(--space-xxxlarge);
  text-align: center;
}

.grid-products__fewer-filters { margin-top: var(--space-large); }
.grid-products__fewer-filters .link { color: var(--ui-600); }

@media (max-width: 1023px) {
  .grid-products__list { padding-inline: 0; }

  /* The listing runs to the screen edges on a phone, Westwing-style: the
     photographs bleed to the viewport with only a slim seam between the two
     columns, so the images get the 16px the page gutter was spending on air.
     Scoped to the .grid-products wrapper — the collection and search PAGES —
     because the same list also renders inside the header search sheet, which
     owns its own inset and must not bleed. Same centring idiom as the
     diptych and the full-bleed bands: 50% is half the container's content
     box, so the margins come out at exactly minus --gutters-side a side. */
  .grid-products .grid-products__list {
    width: var(--viewport-width);
    margin-inline: calc(50% - var(--viewport-width) / 2);
    column-gap: var(--space-small);
  }

  .grid-products__list .card-product__header { padding-inline: 10px; }
}

@media (min-width: 1024px) {
  /* Sorting moves inline on desktop; below this it lives in the filter
     drawer instead, which is why the row flips to a single right-aligned
     cluster rather than space-between. */
  .grid-products-header { justify-content: flex-end; }
  .grid-products-header .sklad-select { display: block; }
  .grid-products-header .sklad-select__wrapper { margin-left: auto; }
  .grid-products-header .sklad-button { margin-left: var(--space-large); }
  .grid-products__list { padding-top: var(--space-large-plus); }
}

@supports (field-sizing: content) {
  .grid-products-header .sklad-select { field-sizing: content; }
  .collection-nav .sklad-select { field-sizing: content; }
}

/* --------------------------------------------------------------------------
   PAGINATION — laid on the page grid so it lines up with the listing above
   it, with the numbers themselves as ordinary .sklad-button--icon controls.
   -------------------------------------------------------------------------- */

.pagination-numbered {
  --pagination-margin-top: var(--space-xxlarge-plus);
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  grid-auto-flow: row;
  grid-column: 1 / -1;
  gap: 0 var(--gutters);
  width: 100%;
  max-width: var(--max-width-container);
  margin-inline: auto;
  margin-top: var(--pagination-margin-top);
  padding-inline: var(--padding-inline, 0);
}

.pagination-numbered__nav {
  grid-column: 1 / -1;
  display: flex;
  flex-flow: column wrap;
  justify-content: center;
  align-items: center;
}

.pagination-numbered__list {
  display: flex;
  align-items: center;
  max-width: 100%;
  margin-inline: auto;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: var(--space-medium);
}

.pagination-numbered__item {
  margin-inline: var(--space-xxsmall);
  min-width: 2.25rem;
}

.pagination-numbered__item--current .sklad-button { border-color: var(--ui-950); }

.pagination-numbered__list-jumper {
  display: flex;
  align-items: center;
  justify-content: center;
}

.pagination-numbered__list-jumper .pagination-numbered__item {
  color: var(--ui-500);
  transition: color var(--duration-fast) var(--ease-out);
}

.pagination-numbered__list-jumper .pagination-numbered__item:hover { color: var(--ui-950); }

@media (max-width: 1023px) {
  .pagination-numbered { padding-inline: 3px; }
}

@media (min-width: 1024px) {
  .pagination-numbered { --pagination-margin-top: 80px; }
}

/* --------------------------------------------------------------------------
   FACETS — the filter drawer's contents. Lives inside the standard .sklad-panel,
   so only the list, the fieldset chrome and the sticky footer are new.

   Each facet group is an ordinary .accordion; the rules below only adjust its
   padding for the drawer. Swatch facets hide their checkbox and let the
   .swatch-item stand in as the control, which is why the checked state is
   expressed as a transform on the sibling label rather than a border.
   -------------------------------------------------------------------------- */

/* The filter trigger carries the active count: with no on-page filter chips,
   this is the only thing on the results page that says filters are on. */
.sklad-button__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5em;
  height: 1.5em;
  padding-inline: 0.4em;
  margin-inline-start: var(--space-small);
  border-radius: 999px;
  background-color: var(--ui-00);
  color: var(--ui-950);
  font-size: var(--text-xsmall-size);
  font-weight: var(--font-weight-bold);
  line-height: 1;
}

/* On a light button the count inverts, so it stays the emphasised mark
   rather than disappearing into the face. */
.sklad-button--grey .sklad-button__count,
.sklad-button--frost-white .sklad-button__count {
  background-color: var(--ui-950);
  color: var(--ui-00);
}

.facets__blocks,
.facets__fieldset { position: relative; }

.facets__legend .text-ui-large { font-weight: var(--font-weight-bold); }

.facets__reset-block {
  position: absolute;
  top: 0;
  right: 26px;
  z-index: 1;
  height: 54px;
  padding-top: var(--space-large-plus);
  color: var(--ui-500);
  transition: color var(--duration-fast) var(--ease-out);
}

.facets__reset-block:hover { color: var(--ui-700); }

.facets__list {
  --facets-list-columns: 1;
  display: grid;
  grid-template-columns: repeat(var(--facets-list-columns), 1fr);
  gap: var(--space-medium);
}

.facets .accordion { padding-bottom: var(--space-large); }

/* The divider must bleed past the drawer's inline padding, so it is drawn as
   a ::before on the header rather than a border on the accordion. */
.facets .accordion__header {
  border-top: none;
  padding-top: var(--space-large-plus);
  padding-bottom: 0;
}

.facets .accordion__header::before {
  content: "";
  position: absolute;
  top: 0;
  left: calc(var(--space-medium) * -1);
  z-index: 1;
  width: calc(100% + var(--space-medium) * 2);
  height: 1px;
  background-color: var(--ui-100);
}

.facets .accordion__inner { padding-top: var(--space-large); }

/* Text facets are a checkbox row; swatch facets replace the box with the
   colour itself and keep the input only for accessibility. */
.facets__item--text .checkbox__label { font-size: var(--text-medium-size); }

.facets__item--swatch { --swatch-item-size: 20px; }

/* The checkbox is the accessible control; the swatch is what you see. */
.facets__item--swatch .checkbox {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.facets__item--swatch .checkbox__label { margin-left: 0; }
.facets__item--swatch .checkbox:checked + .checkbox__label .swatch-item { transform: scale(0.8); }
.facets__item--swatch .checkbox:checked + .checkbox__label .swatch-item::after { opacity: 1; transform: scale(1); }
.facets__item--swatch .checkbox[disabled] + .checkbox__label .swatch-item { opacity: 0.5; }

.facets.is-loading { cursor: wait; }
.facets.is-loading .facets__blocks { pointer-events: none; }

.facets-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.facets-footer__reset { flex: 1; }
.facets-footer__see { flex: 2.5; margin-left: var(--space-small-plus); }

@media (max-width: 1023px) {
  .facets-footer__reset { border: none; }
}

@media (min-width: 1024px) {
  .facets__list { --facets-list-columns: 2; gap: var(--space-small); }
  .facets .accordion { padding-bottom: var(--space-large-plus); }
  .facets .accordion__inner { padding-bottom: var(--space-small); }
  .facets-footer__see { flex: 1; margin-left: var(--space-medium-plus); }
}

/* --------------------------------------------------------------------------
   PRODUCT — the single-product page.

   Two columns: a scrolling image gallery and a buy panel that sticks beside
   it. The gallery is the tall one, so the panel pins and the images travel
   past it; that is the whole idea of the layout and it costs one `sticky`.

   Nothing between .sklad-product__aside and the scroll root may carry
   `overflow: hidden` or a transform — either kills the sticky silently. Same
   trap as .diptych-media__content.
   -------------------------------------------------------------------------- */

/* The trail carried equal top and bottom padding (--space-medium-plus, 20px
   each way): right above the text, where it clears the header, but the same
   20px repeats below it, sitting between the trail and the gallery with
   nothing else contributing to it — measured via getComputedStyle as exactly
   the nav's own padding-bottom, reported from the live product page on an
   iPhone as a visible gap, 05.09.2026. Desktop's two-column grid still wants
   the trail sitting clear of it on both edges, so only the phone half of the
   padding comes off; the symmetric value returns above 1024px, unchanged
   from before.

   --text-opacity used to be set inline on the <nav> (style="--text-opacity:
   0.6", the same dimming .hero--black gives a trail on a plain ground).
   Moved in here so the phone overlay below can re-point it without fighting
   inline-style specificity — the same move PR #261 already made for
   padding-block on this element. */
.sklad-product__breadcrumb { --text-opacity: 0.6; padding-block: var(--space-medium-plus) 0; }

@media (min-width: 1024px) {
  .sklad-product__breadcrumb { padding-block-end: var(--space-medium-plus); }
}

/* --- The trail overlays the first image on a phone -------------------------
   Owner, from the staging product page on a phone, 05.09.2026: "slika skroz
   ispod breadcrumb; breadcrumb dolazi preko slike" — the trail sits ON the
   gallery image instead of pushing it down.

   Ported from how a collection hero already carries its own breadcrumb:
   .hero .hero-navigation is `position:absolute; top:0; left:0` over the
   hero's media, and .hero--white repoints that same breadcrumb to white
   text for exactly this situation — a trail sitting on a photograph rather
   than a plain ground. .sklad-product__top (the <section class="container">
   wrapping the trail and the two-column grid) is the positioning root rather
   than .sklad-product__gallery, so the trail keeps the width it already has
   in flow — the two-column grid has not started yet at this point in the
   markup — while still landing over the top of the gallery once it is lifted
   out of flow.

   Unlike a hero, nothing here picks --white or --black per product photo, so
   there is no signal for which text colour is safe. The header's own
   transparent state is photo-agnostic through a hairline and rejects a scrim
   outright ("to bring white to 4.5:1 over a pure-white pixel needs ~82%
   black, which is a black bar, not a scrim" — see .header.is-transparent
   above) — but a hairline reads as nothing against a product photo's soft
   edge, so that option does not carry over either. The billboard hero
   already answers this: white text plus a scrim strong enough to hold
   contrast regardless of what is under it ("White, over a scrim" — see
   .hero--billboard above). That is the version reused here, resized for a
   phone-width product photo instead of a full-bleed hero — see
   .product-gallery__item::before below for the scrim itself. */
.sklad-product__top { position: relative; }

@media (max-width: 1023px) {
  .sklad-product__breadcrumb {
    position: absolute;
    inset-inline: 0;
    top: 0;
    z-index: 3;
    /* Absolute positioning drops the padding .sklad-product__top was
       supplying to it in flow — restate the same gutter token so the trail
       keeps today's left inset instead of running to the true edge. */
    padding-inline: var(--gutters-side);
    --text-opacity: 1;
    color: var(--ui-00);
    /* The row still spans the full width, so the carousel's own overflow and
       ellipsis maths do not change — but most of that width is now empty
       space over a photo instead of a blank strip above it. Only the actual
       links should take a tap, or the gallery's lightbox trigger loses the
       top of every image to an invisible hit box. */
    pointer-events: none;
  }

  .sklad-product__breadcrumb .breadcrumb__link { pointer-events: auto; }
}

.sklad-product__gallery { position: relative; grid-column: 1 / -1; }
.sklad-product__aside { grid-column: 1 / -1; }

@media (min-width: 1024px) {
  .sklad-product__gallery { grid-column: 1 / 17; }

  .sklad-product__aside {
    grid-column: 18 / -1;
    align-self: start;
    position: sticky;
    top: calc(var(--header-height) + var(--space-large));
  }
}

/* --- Gallery ---------------------------------------------------------------
   A mosaic on the same 4:5 crop the rest of the system uses. Full-width items
   lead, then pairs. On mobile it collapses to one column and runs to the page
   edge, matching how media behaves everywhere else here.
   --------------------------------------------------------------------------- */

.product-gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* 8px, matching the category row and the companion carousel. The gutter is
     the page's rhythm; inside a mosaic of one product's photographs it reads
     as the images being unrelated. */
  gap: var(--space-small);
}

.product-gallery__item {
  position: relative;
  overflow: hidden;
  background-color: var(--surface-muted);
  aspect-ratio: 4 / 5;
  /* The image is the affordance — say so. */
  cursor: zoom-in;
}

/* Two per row: each lead image takes half of the four columns. Everything
   after runs four across. */
.product-gallery__item--wide { grid-column: span 2; }

.product-gallery__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --- Buy panel -------------------------------------------------------------
   Order is deliberate and follows the reference UX: collection, then title,
   then price, then choices, then the commit. Reassurance sits below the
   button, never above it.
   --------------------------------------------------------------------------- */

.product-summary > * + * { margin-block-start: var(--space-medium); }

/* The collection sits above the description as a quiet eyebrow — it says which
   family the piece belongs to, not what it is, so it must not compete with the
   name above it or the copy below. */
.product-summary__collection {
  margin: 0;
  color: var(--text-secondary);
}

.product-summary__collection a { color: inherit; }
.product-summary__collection a:hover { color: var(--ui-950); }

/* The save control is the wishlist heart, sized to the title row rather than to
   a card. Same component, same toggle, same saved state. */
.product-summary__save.button-wishlist {
  width: var(--space-large-plus);
  height: var(--space-large-plus);
  margin-left: auto;
}

.product-summary__save.button-wishlist .icon { --icon-size: 22px; }

.product-summary__title { margin: 0; }

/* The live catalogue names the collection, not the finish-specific product,
   then keeps the WooCommerce short description inside the same H1. */
.product-summary__title-name,
.product-summary__title-description {
  display: block;
}

.product-summary__title-description {
  margin-block-start: var(--space-small);
  color: var(--text-secondary);
  /* Was var(--font-family-body), which is defined nowhere — so the declaration
     was invalid, the property fell back to inheriting, and a sentence of body
     copy inside the H1 has been setting in the display face ever since. */
  font-family: var(--font-sans-serif);
  font-weight: var(--font-weight-regular);
  /* The font-family fix above did not touch letter-spacing, so the sentence
     kept inheriting .title-h4's -0.045em (--tracking-display) — tracking
     belongs to the display face (see tokens.css), and a sans sentence set that
     tight still reads as condensed. Reported off the live product page on an
     iPhone, 05.09.2026. */
  letter-spacing: normal;
}

/* Block-level so the price owns its line, but a flex row so .sklad-price's own
   gap applies: `display: block` threw the gap away — gap is a flex and grid
   property — and the reduced price sat flush against the one it replaced. */
.product-summary__price { display: flex; }

/* The choice rows — swatches, size — separated from the copy above by a rule
   rather than extra space, so the panel stays compact on a short viewport. */
.product-summary__choices {
  padding-block-start: var(--space-large);
  border-block-start: 1px solid var(--border-subtle);
}

.product-summary__label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-small);
  margin-block-end: var(--space-small);
  color: var(--text-secondary);
}

.product-summary__label b {
  font-weight: var(--font-weight-medium);
  color: var(--text-primary);
}

/* Quantity + add-to-cart share a row: the stepper is content-width, the
   button takes the rest. */
.product-summary__buy {
  display: flex;
  align-items: stretch;
  gap: var(--space-small);
}

.product-summary__buy .sklad-button { flex: 1 1 auto; }

.product-summary__meta {
  color: var(--text-tertiary);
}

/* --- Quantity --------------------------------------------------------------
   A bordered shell holding two steppers and a live value. Matches .input's
   box so it sits level with the add-to-cart button.
   --------------------------------------------------------------------------- */

.sklad-quantity {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-control);
}

.sklad-quantity__button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--space-xlarge);
  align-self: stretch;
  color: var(--text-secondary);
  transition: color var(--duration-fast) var(--ease-out);
}

.sklad-quantity__button:hover:not(:disabled) { color: var(--text-primary); }
.sklad-quantity__button:disabled { color: var(--border-strong); cursor: not-allowed; }

.sklad-quantity__value {
  min-width: var(--space-large);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* It is an <input type="number"> now, so the browser's own chrome has to go:
   the spin buttons would sit inside a control that already has steppers, and
   the default border would double the one on .sklad-quantity. Width is fixed
   rather than intrinsic so the control does not resize as digits are added. */
input.sklad-quantity__value {
  appearance: textfield;
  /* Same width the <span> it replaced resolved to (min-width: --space-large),
     so the control keeps its exact dimensions. Fixed rather than intrinsic:
     the box must not resize when the value goes from 1 to 10. */
  width: var(--space-large);
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
}

input.sklad-quantity__value::-webkit-outer-spin-button,
input.sklad-quantity__value::-webkit-inner-spin-button {
  appearance: none;
  margin: 0;
}

input.sklad-quantity__value:focus-visible {
  outline: 2px solid var(--ui-950);
  outline-offset: 1px;
  border-radius: var(--space-xxsmall);
}

/* --- Spec accordions -------------------------------------------------------
   The reference stacks seven of these. They are ordinary button accordions;
   only the divider treatment differs, so the list reads as one block.
   --------------------------------------------------------------------------- */

/* Rows that open the details drawer. Not accordions: seven expanding panels
   on a tinted band pushes everything below them around, and the content is
   long enough to want its own surface. */
.product-specs__list { border-block-start: 1px solid var(--spec-rule, var(--ui-00-40)); }

.product-specs__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-medium);
  width: 100%;
  padding-block: var(--space-medium-plus);
  border-block-end: 1px solid var(--spec-rule, var(--ui-00-40));
  text-align: left;
  cursor: pointer;
}

.product-specs__icon {
  flex: 0 0 auto;
  color: var(--text-tertiary);
  transition: transform var(--duration-fast) var(--ease-out);
}

.product-specs__row:hover .product-specs__icon { transform: translateX(2px); }

/* --- Feature list ----------------------------------------------------------
   A title with a line of explanation under it. The marker is a rule rather
   than a disc — a disc next to a two-line block reads as a list item, this
   reads as a claim with its evidence.
   --------------------------------------------------------------------------- */

.product-features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-large);
  margin-block-start: var(--space-xlarge);
  list-style: none;
  padding: 0;
}

.product-features__item {
  padding-block-start: var(--space-medium);
  border-block-start: 1px solid var(--spec-rule, var(--ui-00-40));
}

.product-features__title {
  font-weight: var(--font-weight-medium);
  margin-block-end: var(--space-xxsmall);
}

.product-features__desc { color: var(--text-secondary); }

/* --------------------------------------------------------------------------
   PRODUCT SUMMARY parts — the buy panel's own pieces.
   -------------------------------------------------------------------------- */

/* Title and the save control share a row; the control never wraps under. */
.product-summary__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-medium);
}

.product-summary__save {
  flex: 0 0 auto;
  color: var(--text-secondary);
  transition: color var(--duration-fast) var(--ease-out);
}

.product-summary__save:hover { color: var(--text-primary); }

/* The description carries its link inline, so the sentence stays one thought
   rather than a paragraph followed by an orphaned call to action. */
.product-summary__description a {
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* --- Variant selector -------------------------------------------------------
   The reference's .siblings-options__link: a bordered card carrying a
   thumbnail, the product name with its defining dimension, and the number of
   alternatives. It reads as a card you can open, not a form control — the
   choice happens in a drawer, because each option is a separate product.
   --------------------------------------------------------------------------- */

.siblings-options__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-small);
  width: 100%;
  /* Asymmetric: tighter on the left where the thumbnail already reads as an
     edge, roomier on the right beside the count. Straight from the reference. */
  padding: var(--space-small) var(--space-medium) var(--space-small) var(--space-small);
  border: 1px solid var(--ui-200);
  border-radius: var(--space-xsmall);
  color: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition:
    box-shadow    var(--duration-fast) var(--ease-out),
    border-color  var(--duration-fast) var(--ease-out);
}

.siblings-options__link:hover {
  box-shadow: var(--shadow-level-1);
  border-color: var(--ui-300);
}

.siblings-options__thumbnail {
  display: block;
  flex: 0 0 auto;
  width: var(--space-xxlarge);
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--space-xsmall);
  background-color: var(--surface-muted);
}

.siblings-options__thumbnail img { width: 100%; height: 100%; object-fit: cover; }

.siblings-options__info { flex: 1 1 auto; min-width: 0; }

.siblings-options__product-title {
  display: block;
  font-weight: var(--font-weight-medium);
  color: var(--ui-950);
}

.siblings-options__dimensions {
  display: block;
  color: var(--ui-500);
}

/* The count is the affordance — it says how much is behind the card. */
.siblings-options__count {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xxsmall);
  flex: 0 0 auto;
  color: var(--ui-500);
  white-space: nowrap;
}

.siblings-options__arrow {
  display: inline-flex;
  transition: transform var(--duration-fast) var(--ease-out);
}

.siblings-options__link:hover .siblings-options__arrow { transform: translateX(2px); }

/* --- Buying help -------------------------------------------------------------
   The reference's "our commitments" shape: a billboard hero carrying only the
   title, a statement under it, then the long editorial folded away. The pieces
   are all components this system already has — hero--billboard, hero--text and
   the button accordion the FAQ uses — so this only places them.
   --------------------------------------------------------------------------- */

.buying-help__body { grid-column: 1 / -1; }

.buying-help__list {
  display: flex;
  flex-direction: column;
  border-block-start: 1px solid var(--beige-200);
}

/* The hero is the first thing under a transparent header, so it pays the
   header's clearance itself rather than through .page-grid, which this page
   does not start with. */
.buying-help__hero { margin-block-start: 0; }

@media (min-width: 1024px) {
  /* Narrower than the page, like the reference's editorial column: guidance is
     read line by line, and the full grid puts sixteen words on one. */
  .buying-help__body { grid-column: 5 / 21; }
}

/* --- Contact ----------------------------------------------------------------
   A centred stack, one block per way of reaching us. Taken from the reference's
   contact page, which is six of these and nothing else: no form, no images, no
   two-column split. Each block is a title, the line that says when to use it,
   and the address — so the page is finished as soon as you find the one that
   matches your question.

   The column is 18 of 24 rather than the full width. Measured off the
   reference: its text sits 979px inside a 1312px wrapper, which is the same
   three quarters.
   --------------------------------------------------------------------------- */

.contact__intro,
.contact__body { grid-column: 1 / -1; }

.contact__intro { margin-block-start: var(--space-medium); }

.contact__lead {
  margin: 0;
  color: var(--text-secondary);
  text-align: center;
}

.contact__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xxlarge);
  margin-block: var(--space-xxlarge) var(--space-xxxlarge);
}

.contact__block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-small);
  text-align: center;
}

.contact__block__title { margin: 0; }

.contact__block__text {
  margin: 0;
  max-width: 46ch;
  color: var(--text-secondary);
}

/* The button is one action on the page, not one per block: the support address
   answers most questions, so it is the only one weighted. */
.contact__block__action { width: max-content; }


/* Two apps, one promise: a pair of equal links in the action slot. */
.contact__block__action--pair {
  display: flex;
  gap: var(--space-large);
  margin: 0;
}

/* Contact blocks borrowed by another page: the heading centres with them. */
.contact-inline__heading { margin: 0; text-align: center; }

/* The registration details are a legal footnote, not a way of reaching anyone.
   Set apart by a rule rather than by another heading weight. */
.contact__block--legal {
  margin-block-start: var(--space-large);
  padding-block-start: var(--space-xlarge);
  border-block-start: 1px solid var(--ui-100);
}

.contact__block--legal .contact__block__text {
  max-width: 60ch;
  color: var(--text-tertiary);
}

@media (min-width: 1024px) {
  /* Not .page-header: narrowing it took the breadcrumb in with it, and the trail
     belongs to the page's edge — every other page starts it on the gutter, this
     one started it 167px in. The title needs no help either; the shared rule
     already sets it to 4 / 22, which is where this page wanted it. What was
     here before narrowed the header and then undid the title's own column to
     compensate. */
  .contact__intro,
  .contact__body { grid-column: 4 / 22; }
}

/* --- Notice ----------------------------------------------------------------
   One shape for everything the site has to say: a white panel with a hairline
   border. Tone is the icon's job, not the panel's.

   It used to be the other way round — a tinted band per tone — which meant a
   page could show two notices at once in two different shapes, a green band
   above a bordered white box, and neither looked like the other. The colour was
   doing the work that the words and the mark already do.
   --------------------------------------------------------------------------- */

.sklad-notice,
.woocommerce-error,
.woocommerce-message,
.woocommerce-info {
  display: flex;
  gap: var(--space-small-plus);
  padding: var(--space-medium);
  border: 1px solid var(--ui-100);
  border-radius: var(--space-xxsmall);
  background-color: var(--notice-background, var(--ui-00));
  color: var(--ui-800);
  font-size: var(--text-medium-size);
  line-height: var(--text-paragraph-lh);
}

.sklad-notice__icon { flex: 0 0 auto; line-height: var(--text-medium-lh); }
.sklad-notice__body > * + * { margin-block-start: var(--space-small); }
.sklad-notice__body a,
.woocommerce-error a,
.woocommerce-message a:not(.button),
.woocommerce-info a:not(.button) { text-decoration: underline; text-underline-offset: 0.2em; }

/* A link inside a sentence is prose, not a flex box. The site's .link is
   display:inline-flex (it carries an icon beside its text elsewhere), which
   makes it an atomic inline box: a period right after it wraps to its own line
   or reads as a space before the full stop — „zatraži novu lozinku ." on the
   sign-in error, and the same on „…postavi je." (QA, 06.09.2026). In a notice
   these links never carry an icon, so they render inline and the punctuation
   hugs them; the .link--underlined gradient still paints on the text runs. */
.notice__text .link,
.sklad-notice__body .link,
.woocommerce-error .link,
.woocommerce-message .link,
.woocommerce-info .link { display: inline; }

/* Tone, carried by the mark. The panel stays white and the body stays --ui-800
   on all of them, so contrast never depends on which tone was picked — and a
   success beside an error reads as two of the same thing, differing where it
   matters rather than in the shape of the box. */
.sklad-notice--error   .sklad-notice__icon { color: var(--status-error); }
.sklad-notice--success .sklad-notice__icon { color: var(--status-success); }
.sklad-notice--info    .sklad-notice__icon { color: var(--ui-500); }

/* --- Form status ------------------------------------------------------------
   One line under a form saying what just happened. Not a .sklad-notice: that is
   a tinted panel for something the page is telling you before you act, while
   this is the answer to something you just did, and it belongs against the
   control rather than above it.

   It reads its colour from --status-*, which .on-dark re-points, so the same
   markup works in the footer and on a white form.
   --------------------------------------------------------------------------- */

.form-status {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xsmall);
  margin-block-start: var(--space-small-plus);
  color: var(--status-muted);
}

.form-status[hidden] { display: none; }

/* Inside the newsletter form the gap was doubled: the form is a flex column
   with a 16px gap and .form-status added 12px of its own on top, putting the
   message 28px under the button it belongs to. The form's own gap is enough.
   Left in place for the forms that lay their fields out without one. */
.newsletter__form .form-status { margin-block-start: 0; }

.form-status__icon {
  flex: 0 0 auto;
  line-height: var(--text-xsmall-lh);
}

.form-status--success { color: var(--status-success); }
.form-status--error   { color: var(--status-error); }

/* Working. The label is the affordance — a spinner for a request that usually
   takes under a second is more motion than information. */
.form-status--pending { color: var(--status-muted); }

/* While a request is in flight the control should not accept another. */
.newsletter__form.is-pending .sklad-button,
.newsletter__form.is-pending .field { opacity: 0.6; pointer-events: none; }

/* --- WooCommerce notices ---------------------------------------------------
   These arrive from the server with only Woo's own classes on them — there is
   no template hook to add ours — so unlike everything else in this file, the
   Woo selector IS the component here rather than a compatibility alias.

   .woocommerce-error is a <ul> of messages, .woocommerce-message carries a
   "View cart" button, and both can be wrapped in a .woocommerce-NoticeGroup.
   --------------------------------------------------------------------------- */

.woocommerce-error   { --notice-background: var(--surface-error); }
.woocommerce-message { --notice-background: var(--surface-success); }
.woocommerce-info    { --notice-background: var(--surface-lavender); }

/* The <ul> has to stop being a list before it can be a panel. */
.woocommerce-error {
  flex-direction: column;
  gap: var(--space-small);
  margin: 0;
  padding-inline-start: var(--space-medium);
  list-style: none;
}
.woocommerce-error li { margin: 0; }

/* Woo puts its button FIRST in the markup and relies on a float to send it
   right. Flex ordering does the same job without the float, and keeps the
   message reading before the action for anyone not looking at it. */
.woocommerce-message,
.woocommerce-info {
  align-items: center;
  justify-content: space-between;
}
.woocommerce-message > .button,
.woocommerce-info > .button { order: 2; flex: 0 0 auto; }

/* Woo generates a bare `.button` for these. It is the only place a control
   reaches the page without our classes on it, so it borrows the secondary
   button's look rather than being left as a UA button. */
.woocommerce-message .button,
.woocommerce-info .button,
.woocommerce-error .button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-xsmall) var(--space-small-plus);
  border: 1px solid var(--ui-200);
  border-radius: var(--space-xsmall);
  background-color: var(--ui-00);
  color: var(--ui-950);
  font-size: var(--text-xsmall-size);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: box-shadow var(--duration-fast) var(--ease-out);
}
.woocommerce-message .button:hover,
.woocommerce-info .button:hover,
.woocommerce-error .button:hover { box-shadow: var(--shadow-level-2); }

/* Stacked notices keep the page rhythm without each notice knowing about it. */
.woocommerce-NoticeGroup > * + *,
.sklad-notice + .sklad-notice,
.woocommerce-error + *,
.woocommerce-message + *,
.woocommerce-info + * { margin-block-start: var(--space-small-plus); }

/* --------------------------------------------------------------------------
   PRODUCT INFO — the tinted band under the buy panel.

   Two columns: the pitch and its proof points on the left, the full spec
   stack on the right. Splitting them means the scannable version and the
   exhaustive version sit side by side instead of one burying the other.
   -------------------------------------------------------------------------- */

.product-info { background-color: var(--product-info-background, var(--lavender-100)); }

.product-info__wrapper {
  --padding-inline: var(--gutters-side);
  padding-block: var(--space-xxlarge);
  row-gap: var(--space-xlarge);
}

.product-info__lead { grid-column: 1 / -1; }
.product-info__specs { grid-column: 1 / -1; }

/* Proof points — an icon over a label, sized off the grid rather than a fixed
   column count, so two or five behave the same. */
@media (min-width: 1024px) {
  .product-info__wrapper { padding-block: var(--space-xxxlarge); }
  .product-info__lead { grid-column: 1 / 15; }
  .product-info__specs { grid-column: 16 / -1; }
}

/* --------------------------------------------------------------------------
   COLLECTION PREVIEW — a lifestyle image beside the rest of the collection.

   The image is full-bleed on its side and the cards sit in the gutters, so
   the row reads as one editorial unit rather than a banner plus a carousel.
   -------------------------------------------------------------------------- */

.collection-preview__media {
  grid-column: 1 / -1;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background-color: var(--surface-muted);
}

.collection-preview__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.collection-preview__content { grid-column: 1 / -1; }

.collection-preview__header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-medium);
  margin-block-end: var(--space-large);
}

.collection-preview__nav { display: flex; gap: var(--space-small); flex: 0 0 auto; }

.collection-preview__items { --slides-visible: 3.5; }

@media (max-width: 1023px) {
  .collection-preview__media {
    width: var(--viewport-width);
    margin-inline: calc(50% - var(--viewport-width) / 2);
  }

  .collection-preview__items { --slides-visible: 1.5; }
}

@media (min-width: 1024px) {
  .collection-preview__media {
    grid-column: 1 / 13;
    /* Holds 4:5 rather than stretching to the content column's height, so it
       matches every other media block in the system. */
    aspect-ratio: 4 / 5;
    align-self: start;
  }
  .collection-preview__content {
    grid-column: 14 / -1;
    align-self: center;
    padding-block: var(--space-xxlarge);
  }
}

/* --------------------------------------------------------------------------
   SIBLINGS OPTIONS — the material / finish selector.

   Each option is a real product, not a form value: picking one navigates. So
   the swatches are links carrying a photograph of that variant, and the label
   above names the current selection rather than the attribute alone.

   The selected state is drawn by insetting the image inside its own border
   (clip-path), not by adding an outline — so nothing shifts and the border can
   sit tight against the crop.
   -------------------------------------------------------------------------- */

.siblings-options {
  display: flex;
  flex-direction: column;
  gap: var(--space-medium);
}

.siblings-options__section {
  display: flex;
  flex-direction: column;
  gap: var(--space-xsmall);
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

.siblings-options__label {
  font-family: var(--font-sans-serif);
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
  color: var(--ui-500);
  padding: 0;
}

.siblings-options__selected-value {
  color: var(--ui-950);
  font-weight: var(--font-weight-medium);
}

.siblings-options__swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-small);
}

.swatch {
  --swatch-size: 56px;
  display: block;
  width: var(--swatch-size);
  /* 4:5 like every other product image in the system, not a square. */
  aspect-ratio: 4 / 5;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--space-xsmall);
  background-color: var(--surface-muted);
  transition: border-color var(--duration-medium) var(--ease-out-cubic);
}

/* The colour chip shows a colour, not a product, so it is exempt from the 4:5
   rule — it stays a wide chip. */
.siblings-options__swatches--color .swatch {
  aspect-ratio: auto;
  height: var(--space-large-plus);
}

.swatch__image,
.swatch__color-placeholder {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--space-xsmall);
  /* The inset IS the selected state — see the note above. */
  clip-path: inset(0 round var(--space-xsmall));
  transition: clip-path var(--duration-slow) var(--ease-out-cubic);
}

.swatch__color-placeholder { background-color: var(--swatch-color, var(--ui-200)); }

/* Selected state reads the radio directly. It used to be a .swatch--active
   class that JS had to keep in step with the real control; now there is only
   one source of truth and they cannot disagree. Same border, same inset — the
   visual is unchanged. */
.swatch:has(.swatch__input:checked),
.swatch:hover:not(:has(.swatch__input:checked)) { border-color: var(--ui-950); }

.swatch:has(.swatch__input:checked) .swatch__image,
.swatch:has(.swatch__input:checked) .swatch__color-placeholder,
.swatch:hover:not(:has(.swatch__input:checked)) .swatch__image,
.swatch:hover:not(:has(.swatch__input:checked)) .swatch__color-placeholder {
  clip-path: inset(2px round var(--space-xxsmall));
  transition: clip-path var(--duration-medium) var(--ease-out-cubic);
}

/* The swatches were anchors with no reachable focus state. As radios they are
   keyboard-operable, so focus has to be visible — the ring goes on the label,
   because the input it belongs to is clipped to one pixel. */
.swatch:has(.swatch__input:focus-visible) {
  outline: 2px solid var(--ui-950);
  outline-offset: 2px;
}

/* An unavailable combination. WooCommerce disables the matching option; the
   adapter mirrors that onto the radio, and this is what it looks like. */
.swatch:has(.swatch__input:disabled) { cursor: not-allowed; opacity: 0.4; }
.swatch:has(.swatch__input:disabled):hover { border-color: transparent; }

/* The finish swatches are links, not radios: every decor is its own product,
   so choosing one navigates rather than setting a form value.

   They do not borrow the radio swatch's selected state. That state is an inset
   — the picture shrinks a couple of pixels inside its own border — which reads
   as depth on a colour chip and as a cropped photograph on a product shot: the
   selected finish looked cut out of its frame with a white margin around it.
   Here the picture fills the chip to the corner and the selection is a ring
   drawn outside it, so nothing is ever taken off the image. */
.swatch--link {
  overflow: hidden;
  outline: 2px solid transparent;
  outline-offset: 2px;
  transition:
    outline-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}

.swatch--link .swatch__image,
.swatch--link .swatch__color-placeholder {
  clip-path: none;
}


.swatch--link.is-active { outline-color: var(--ui-950); }
.swatch--link:hover:not(.is-active) { outline-color: var(--ui-200); }
.swatch--link:focus-visible { outline-color: var(--ui-950); }

/* --- Variation dropdown ---------------------------------------------------
   The size picker. Not .sklad-select, which is the borderless control used
   for sorting — this one sits in the buy panel and has to read as a field
   you are expected to fill in, so it borrows .input's geometry.
   -------------------------------------------------------------------------- */

.variations__row {
  display: flex;
  flex-direction: column;
  gap: var(--space-xsmall);
}

.variations__label {
  color: var(--ui-500);
}

.variations__select {
  width: 100%;
  padding: 15px var(--space-medium);
  border: 1px solid var(--ui-200);
  border-radius: var(--radius-control);
  background-color: var(--ui-00);
  color: var(--ui-950);
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  font-weight: var(--font-weight-medium);

  /* Same caret as .sklad-select, kept on the right where a field expects it,
     with room reserved so a long size never runs underneath it. */
  appearance: none;
  padding-right: var(--space-large);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none'%3e%3cpath fill='currentColor' d='m11 10-3 3-3-3zM5 6l3-3 3 3z'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right var(--space-medium) center;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out);
}

.variations__select:hover { border-color: var(--ui-300); }
.variations__select:focus-visible { outline: 2px solid var(--ui-950); outline-offset: 1px; }

.siblings-options__reset {
  align-self: flex-start;
  color: var(--ui-500);
  text-decoration: underline;
}

/* WooCommerce reveals "Clear" with .css('visibility','visible') — it never
   touches display and never removes an attribute. So this has to start hidden
   on the same channel, otherwise Woo's reveal is a no-op and the link can
   never appear. It carried the `hidden` attribute before, which meant exactly
   that. visibility also keeps it out of the accessibility tree while hidden.

   The trade-off is that visibility reserves its 24px where display: none did
   not. That is the better half of the trade: Woo reveals this the moment a
   variation resolves, so reserving the row means the buy panel does not jump
   at that moment. */
.reset_variations { visibility: hidden; }

/* A fieldset needs its UA border and padding stripped before it can stand in
   for the div it replaced. */

/* --------------------------------------------------------------------------
   COMPLETE THE LOOK — a titled carousel of companion products.

   Reuses the standard [data-carousel] track, so the arrows, snapping and
   disabled-at-the-ends behaviour all come for free. Only the header row and
   the slide width are new.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   PRODUCT ROW — a titled carousel of product cards.

   One component for every "here are more products" band on the site:
   You might be interested, Recently viewed, Complete the look. They differ
   only in width and title size, which is what --compact is for — a fourth
   bespoke header would have been a fourth thing to keep in sync.
   -------------------------------------------------------------------------- */

.product-row__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-medium);
  margin-block-end: var(--space-large);

  /* The row's own arrows are not in here — they lie over the photographs — so
     the space beside the heading is free for the link out of the row. It wraps
     because "Svi proizvodi: TV komode" beside a title-h3 does not fit a phone,
     and a squeezed heading is worse than a second line. */
  flex-wrap: wrap;
}

.product-row__header .stories-teaser__more { margin-block-start: 0; }

/* One carousel, one set of arrows, in one place.

   The arrows used to sit in the header, permanently visible, while the cards
   below carried a second set over their own photographs. They now lie over the
   images and appear on hover, which is what .mini-slider__nav has always done —
   the row and the category strip are the same component to use, so they behave
   the same way.

   Laid out with grid rather than absolute offsets: the nav and the track are
   given the same cell, so the nav starts exactly where the track starts without
   anyone having to know how tall the heading above it happens to be. */
.product-row,
.collection-preview__content {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.product-row__header,
.collection-preview__header { grid-area: 1 / 1; }

.product-row__nav,
.collection-preview__nav,
.product-row .carousel__track,
.collection-preview__items { grid-area: 2 / 1; }

.product-row__nav,
.collection-preview__nav {
  z-index: 1;
  /* The height of the photograph, so the arrows centre on it rather than on the
     card, which carries a name and a price under it. Measured in main.js and
     published on the carousel: a slide's width is a percentage of the track,
     and a percentage used for a height resolves against a height, so this one
     number cannot be arrived at in CSS. Without the script the arrows centre
     on the slide, which is the sane fallback. */
  block-size: var(--carousel-media-height, 100%);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-small);
  padding-inline: var(--space-small);
  pointer-events: none;
}

.product-row__nav .sklad-button,
.collection-preview__nav .sklad-button { pointer-events: auto; }

/* Touch pages the row by dragging it; there is no hover to reveal anything and
   an arrow would only take space from the photograph. */
@media (max-width: 1023px) {
  .product-row__nav,
  .collection-preview__nav { display: none; }
}

@media (min-width: 1024px) {
  .product-row__nav .sklad-button,
  .collection-preview__nav .sklad-button {
    opacity: 0;
    transition: opacity var(--duration-fast) var(--ease-out);
  }

  .product-row:hover .product-row__nav .sklad-button,
  .product-row:focus-within .product-row__nav .sklad-button,
  .collection-preview__content:hover .collection-preview__nav .sklad-button,
  .collection-preview__content:focus-within .collection-preview__nav .sklad-button { opacity: 1; }

  /* At an end there is nothing to page to, so it stays hidden even on hover. */
  .product-row:hover .product-row__nav .sklad-button[disabled],
  .product-row:focus-within .product-row__nav .sklad-button[disabled],
  .collection-preview__content:hover .collection-preview__nav .sklad-button[disabled],
  .collection-preview__content:focus-within .collection-preview__nav .sklad-button[disabled] {
    opacity: 0;
    pointer-events: none;
  }
}

.product-row { --slides-visible: 4.5; }

@media (max-width: 1023px) {
  .product-row { --slides-visible: 1.5; }
}

/* The in-column variant: sits under the buy button, so it is narrower, its
   title is a label rather than a heading, and its controls shrink to match. */
.product-row--compact {
  padding-block-start: var(--space-large);
  border-block-start: 1px solid var(--border-subtle);
}

.product-row--compact .product-row__header { margin-block-end: var(--space-medium); }
.product-row--compact .product-row__nav { gap: var(--space-xsmall); }
/* 8px, the same gap the category row on the homepage uses — a tight gap makes
   a row read as one strip rather than as separate cards. */
.product-row--compact .carousel__track {
  --slides-visible: 2.5;
  --grid-products-gap-x: var(--space-small);
}

/* --- Tracks that are already inside a container ----------------------------
   .carousel__track bleeds to the page edge by default (padding-inline +
   negative margin). Inside a .container that bleed is wrong twice over: it
   double-insets, and the percentage in --slide-width then resolves against the
   padded content box rather than the visible track, so the peek comes out
   wrong. Both of these opt out. */
.product-row .carousel__track,
.collection-preview__items {
  padding-inline: 0;
  margin-inline: 0;
}

/* --------------------------------------------------------------------------
   STICKY BUY BAR — appears once the real add-to-cart has scrolled away.

   Pinned under the header rather than to the bottom of the viewport: the
   header is already fixed there, so docking beneath it keeps one band of
   chrome instead of two competing edges.

   It is `visibility` + `transform`, not `display`, so the bar can animate and
   so its contents stay measurable while hidden.
   -------------------------------------------------------------------------- */

.product-sticky-bar {
  position: fixed;
  top: var(--header-height);
  left: 0;
  right: 0;
  z-index: calc(var(--z-header) - 1);

  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-medium);
  padding: var(--space-small-plus) var(--gutters-side);

  background-color: var(--ui-00);
  border-block-end: 1px solid var(--border-subtle);

  visibility: hidden;
  opacity: 0;
  transform: translateY(-100%);
  transition:
    transform  var(--duration-medium) var(--ease-out-cubic),
    opacity    var(--duration-fast) var(--ease-out),
    visibility var(--duration-medium);
}

.product-sticky-bar.is-visible {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
}

.product-sticky-bar__product {
  display: flex;
  align-items: center;
  gap: var(--space-small-plus);
  min-width: 0;
}

.product-sticky-bar__thumb {
  display: block;
  flex: 0 0 auto;
  width: var(--space-xlarge);
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background-color: var(--surface-muted);
}

.product-sticky-bar__thumb img { width: 100%; height: 100%; object-fit: cover; }

/* The description is the first thing to go when the bar gets tight — the name
   and the price are what the customer is committing to. */
.product-sticky-bar__text { display: block; min-width: 0; }
.product-sticky-bar__name { display: block; font-weight: var(--font-weight-medium); }

.product-sticky-bar__desc {
  display: block;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.product-sticky-bar__actions {
  display: flex;
  align-items: stretch;
  gap: var(--space-small);
  flex: 0 0 auto;
}

/* On a phone the button says only what it does.
 *
 * The bar carries a thumbnail, a name, a description, a quantity stepper and
 * this button across 392px, and "Dodaj u korpu · 38.500 RSD 32.500 RSD" — a
 * sale prints both prices — pushed it over everything to its left. The price is
 * the one part already on screen twice: in the summary above and, once this is
 * pressed, in the drawer that opens. */
@media (max-width: 1023px) {
  .product-sticky-bar__price { display: none; }
}

/* On a phone the bar belongs at the foot of the screen, where the thumb is,
   not under the header where it would eat a third of a short viewport. Same
   bar, same trigger — only the edge it is pinned to changes, so it slides up
   from below rather than down from above. */
@media (max-width: 1023px) {
  .product-sticky-bar {
    top: auto;
    bottom: 0;
    padding-block-end: calc(var(--space-small-plus) + env(safe-area-inset-bottom, 0px));
    border-block-end: 0;
    border-block-start: 1px solid var(--border-subtle);
    transform: translateY(100%);
  }

  .product-sticky-bar__desc,
  .product-sticky-bar .sklad-quantity { display: none; }
}

/* --------------------------------------------------------------------------
   OPTION PICKER — the "choose an option" grid inside a drawer.

   The tiles are the ordinary .card-product used everywhere else; this block
   supplies only the grid and the selected state. Every option is a real
   product, so it should look like one — no bespoke tile.
   -------------------------------------------------------------------------- */

.option-picker {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-large) var(--space-medium);
}

/* Outlines the crop, not the whole tile, so the label underneath is not boxed
   in — the same treatment the material swatches use. */
.option-picker .card-product__medias {
  border: 1px solid transparent;
  transition: border-color var(--duration-fast) var(--ease-out);
}

.option-picker__item:hover .card-product__medias,
.option-picker__item--selected .card-product__medias { border-color: var(--ui-950); }

@media (min-width: 1024px) {
  .option-picker { grid-template-columns: repeat(3, 1fr); }
}

/* --------------------------------------------------------------------------
   CAROUSEL DOTS — position indicator, ported from the reference.

   Two shapes: discrete dots, and a --line progress bar whose fill is one
   slide wide and slides across. The line is driven entirely by two custom
   properties (--length, --current-index), so JS writes numbers, not styles.
   -------------------------------------------------------------------------- */

.carousel-dots {
  display: none;
  position: absolute;
  bottom: 10px;
  left: 0;
  width: 100%;
  justify-content: center;
  align-items: center;
}

.carousel.is-initialized .carousel-dots,
.is-initialized > .carousel-dots { display: flex; }

.carousel-dots__item {
  width: 10px;
  height: 10px;
  margin: 0 5px;
  border-radius: 50%;
  background-color: var(--ui-200);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}

.carousel-dots__item.is-active { background-color: var(--ui-600); }

.carousel-dots--line {
  bottom: 0;
  z-index: 2;
  height: 2px;
  overflow: hidden;
  background-color: var(--ui-200);
}

.carousel-dots--line::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: calc(100% / var(--length, 1));
  height: 100%;
  background-color: var(--ui-600);
  transform: translate(calc(var(--current-index, 0) * 100%));
  transition: transform var(--duration-fast) var(--ease-out);
}

.carousel-dots--line .carousel-dots__item { opacity: 0; }

/* --------------------------------------------------------------------------
   GALLERY ON MOBILE — one image per view, swiped, with the indicator beneath.

   The desktop mosaic and the mobile slider are the same markup: below 1024
   the grid becomes a scroll-snap row. Nothing is duplicated or hidden.
   -------------------------------------------------------------------------- */

@media (max-width: 1023px) {
  .product-gallery {
    display: flex;
    gap: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    /* Room for the indicator to sit under the images. */
    padding-block-end: var(--space-large);
    /* Full-bleed, like every other media block on mobile. */
    width: var(--viewport-width);
    margin-inline: calc(50% - var(--viewport-width) / 2);
  }

  .product-gallery::-webkit-scrollbar { display: none; }

  .product-gallery__item,
  .product-gallery__item--wide {
    flex: 0 0 100%;
    scroll-snap-align: start;
    /* One image per swipe, as on the loop card: a flick used to carry past
       two or three before the snap caught it. */
    scroll-snap-stop: always;
    grid-column: auto;
  }

  /* The indicator belongs to the image, not to the gap under it. It was
     positioned against .sklad-product__gallery, which sits inside the page
     gutters — so it came out 32px narrower than the full-bleed images it
     measures, floating 22px below the last one. Full width, and resting on
     the bottom edge of the image itself. */
  .sklad-product__gallery .carousel-dots--line {
    left: 50%;
    width: var(--viewport-width);
    transform: translateX(-50%);
    bottom: var(--space-large);
  }
}

@media (min-width: 1024px) {
  /* The indicator belongs to the mobile slider only. */
  .sklad-product__gallery .carousel-dots { display: none !important; }
}

/* --------------------------------------------------------------------------
   LIGHTBOX — the gallery, at full size.

   Its own surface rather than a .sklad-panel: a drawer is a side sheet with a
   header, and this wants the whole viewport with nothing else in it. Escape,
   the overlay, arrow keys and the close control all dismiss it.

   .lightbox__figure is the same shape as .product-gallery: a track of full
   slides, one per photo, laid out in a row. At rest (desktop) it clips to one
   slide and the arrows step it; below 1024px it becomes the scroller itself
   — overflow-x auto with mandatory snap — so opening the viewer continues the
   same swipe the inline gallery already answered, only larger. JS builds the
   slides once, from the same trigger images main.js already reads; nothing
   here assumes how many there are.
   -------------------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;

  background-color: var(--ui-00);
  visibility: hidden;
  opacity: 0;
  transition:
    opacity    var(--duration-medium) var(--ease-out),
    visibility var(--duration-medium);
}

.lightbox.is-open { visibility: visible; opacity: 1; }

.lightbox__figure {
  display: flex;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.lightbox__slide {
  flex: 0 0 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: var(--space-xxlarge) var(--gutters-side);
}

/* `contain`, not `cover`: at full size the whole frame matters, and cropping
   is exactly what the viewer opened this to escape. */
.lightbox__image {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

.lightbox__close {
  position: absolute;
  top: var(--space-medium);
  right: var(--gutters-side);
  z-index: 2;
  padding: var(--space-xsmall);
  color: var(--text-primary);
}

.lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
}

.lightbox__nav--prev { left: var(--gutters-side); }
.lightbox__nav--next { right: var(--gutters-side); }

.lightbox__counter {
  position: absolute;
  bottom: var(--space-large);
  left: 0;
  width: 100%;
  text-align: center;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 1023px) {
  /* The track becomes the scroller: edge to edge, like every other media
     block on mobile, with the same snap the inline gallery uses. */
  .lightbox__figure {
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
  }

  .lightbox__figure::-webkit-scrollbar { display: none; }

  .lightbox__slide {
    scroll-snap-align: start;
    scroll-snap-stop: always;
    /* No letterboxing — the arrows that padding cleared room for are hidden
       below, and the image should run to the edges like the gallery it
       continues. */
    padding: 0;
  }

  /* A mouse needs them; a thumb already has the swipe, and at this width
     they were two small targets over a photo that fills the screen. */
  .lightbox__nav { display: none; }
}

/* --------------------------------------------------------------------------
   PAGE GRID — the container every non-hero page sits in.

   Max width, side gutters, the column grid, and the clearance that keeps
   content out from under the header. Cart, wishlist, legal and FAQ all use it;
   it was copied into each of them before this existed, which meant four places
   to change a gutter.

   The reference also clears the announcement bar here, because there the bar
   rides inside the same absolutely-positioned group as the header. In this
   port it sits in normal flow above the header and already takes its own
   space — the same divergence as .hero--spacing-top.
   -------------------------------------------------------------------------- */

.page-grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: 0 var(--gutters);
  width: 100%;
  max-width: var(--max-width-container);
  margin-inline: auto;
  padding-inline: var(--gutters-side);
  --container-padding-top: var(--space-xlarge-plus);
  padding-top: calc(var(--header-height) + var(--container-padding-top));
}

@media (min-width: 1024px) {
  .page-grid { --container-padding-top: var(--space-xxlarge-plus); }
}

/* --------------------------------------------------------------------------
   CART — .cart-items, .line-item, .cart-total. Ported from the reference.

   The whole page is one grid and everything below it is `subgrid`, all the way
   down: .cart-items (24 tracks) → __main (1/16) → __list-items → __item →
   .line-item → .line-item__infos. Twelve of the page's own columns therefore
   reach the inside of a line item, which is what lets the media, the copy, the
   shipping note and the price line up column-for-column down the whole cart —
   and lets the column headers above the list line up with them too. No row
   solves its own layout.

   This is also why the spans look odd out of context (price at 10/-1, shipping
   at 6/span 4): they are written against the tracks .cart-items__main passes
   down, not against the element they sit in.
   -------------------------------------------------------------------------- */

/* While a quantity change is in flight the cart is still readable and still
   says the old numbers — they are about to be replaced by the server's. Dimming
   marks them as stale without moving anything, and blocking pointer events
   stops a second change landing on figures that are already out of date.
   `prefers-reduced-motion` keeps the state, drops the fade. */
.cart-items.is-updating {
  opacity: 0.55;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .cart-items.is-updating { transition: none; }
}

.cart-items__main {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  align-content: flex-start;
}

.cart-items__header,
.cart-items__list-items,
.cart-items__item {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
}

.cart-items__list-items { list-style: none; padding: 0; margin: 0; }

.cart-items__container {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: baseline;
  padding-bottom: var(--space-medium);
  border-bottom: 1px solid var(--ui-100);
}

.cart-items__container__title {
  grid-column: 1 / 8;
  margin: 0;
}

/* Two mutually exclusive headers for the same corner. On mobile a row is a
   stacked card with no columns to label, so all you get is the item count; on
   desktop the rows become a table and the count gives way to the column
   headings that sit above them. Both end at -1 — only one is ever shown. */.cart-items__container__count,
.cart-items__container__price{
  margin: 0;
  font-family: var(--font-sans-serif);
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
  color: var(--ui-500);
  text-align: right;
}

.cart-items__container__count { grid-column: span 4 / -1; }

.cart-items__container__price{ display: none; }

.cart-items__summary {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  margin-top: var(--space-xlarge-plus);
  position: sticky;
  /* Same offset the page uses to clear the header at the top, minus the
     announcement-bar term (see .cart-items above), so the pinned panel lands
     exactly where the page content starts. */
  top: calc(var(--header-height) + var(--container-padding-top));
  align-self: start;
}

.cart-items__summary__title {
  grid-column: 1 / -1;
  margin: 0;
  padding-bottom: var(--space-medium);
  color: var(--ui-950);
}

.cart-items .cart-total { grid-column: 1 / -1; }
@media (min-width: 1024px) {
  .cart-items__main { grid-column: 1 / 16; }
  .cart-items__summary { grid-column: 17 / -1; margin-top: 0; }
  .cart-items__container { padding-bottom: var(--space-large-plus); }
  .cart-items__summary__title { padding-bottom: var(--space-large-plus); }

  .cart-items__container__count    { display: none; }  .cart-items__container__price    { display: block; grid-column: span 3 / -1; }
}

/* --- Empty state ----------------------------------------------------------- */

.cart-empty {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: 0 var(--gutters);
  grid-auto-flow: row;
  width: 100%;
  max-width: var(--max-width-container);
  margin-inline: auto;
  padding-inline: var(--gutters-side);
  --container-padding-top: var(--space-xlarge-plus);
  padding-top: calc(var(--header-height) + var(--container-padding-top));
  min-height: 50vh;
}

.cart-empty__inner {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-xlarge);
  margin-bottom: var(--space-xxxlarge);
}

.cart-empty__title { margin: 0; }

.cart-empty__button { width: max-content; }
@media (min-width: 1024px) {
  .cart-empty { --container-padding-top: var(--space-xxlarge-plus); }
  .cart-empty__inner { margin-bottom: 128px; }
}

/* With a "Poslednje pogledano" row under it the empty state is a message, not
   the whole page: it gives up its half-viewport minimum and the room it kept
   above the footer, and the spacer + row take over the rhythm below. The
   row's own bottom margin is what the cart's cross-sells get from the sticky
   bar's clearance — restated here because the empty page has no such bar. */
.cart-empty--with-row { min-height: 0; }
.cart-empty--with-row .cart-empty__inner { margin-bottom: 0; }
.recently-viewed { margin-block-end: var(--space-xxxlarge); }

/* --- Line item ------------------------------------------------------------- */

.line-item {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  gap: var(--space-medium);
  padding-block: var(--space-medium);
  border-bottom: 1px solid var(--ui-100);
}

.line-item.is-loading { cursor: wait; }
.line-item.is-loading .line-item__actions { pointer-events: none; }

/* `contain`, not `cover`: a cart thumbnail is the receipt for what you picked,
   so the whole object has to stay visible even at this size. */
.line-item__media {
  display: block;
  grid-column: 1 / span 5;
  aspect-ratio: 4 / 5;
  background: var(--ui-50);
  transition: opacity var(--duration-fast) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .line-item__media:hover { opacity: 0.75; }
}

.line-item__media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: var(--space-xsmall);
}

.line-item__infos {
  grid-column: 6 / -1;
  display: grid;
  grid-template-columns: subgrid;
  grid-template-rows: repeat(2, auto) 1fr;
  row-gap: var(--space-medium-plus);
  min-width: 0;
  min-height: 100%;
  padding-block: 2px;
}

/* A product name is not a UI label — see .card-product__title (#122): the
   same call for the listing card, made here for the basket line. This rule
   was still wearing the .text-ui-large recipe (letter-spaced caps) from the
   initial commit, unlike the card, which was moved off it; that is why the
   collection name (e.g. "Moduli") rendered as "MODULI" in the basket after
   #122 had already fixed the card it was copied from. Uppercase and its
   letter-spacing come out together — the 0.03em tracking exists to carry
   caps, not sentence case (see base.css, .text-ui-large). */
.line-item__infos__title {
  grid-column: 1 / -1;
  margin: 0;
  font-family: var(--font-sans-serif);
  font-size: var(--text-ui-large-size);
  line-height: var(--text-ui-large-lh);
  /* Medium, the card's weight (.card-product__title): with the caps gone
     (#260) the bold read heavier here than the same name on the listing —
     Niko, staging, 05.09.2026. One name, one weight, wherever it appears. */
  font-weight: var(--font-weight-medium);
}

.line-item__properties {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--space-xxsmall);
  padding: 0;
  margin: 0;
  list-style: none;
}

.line-item__properties__item { color: var(--ui-500); }
.line-item__properties__item--dimensions { display: flex; gap: var(--space-small-plus); }

.line-item__properties__item--title {
  color: var(--ui-950);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.line-item__properties__item--material { display: flex; align-items: center; gap: var(--space-xxsmall); }

.line-item__delivery { display: flex; align-items: center; gap: var(--space-xxsmall); color: var(--ui-500); }

.line-item__price-container {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: row;
  align-items: baseline;
  gap: var(--space-small);
}

.line-item__price {
  display: flex;
  align-items: center;
  gap: var(--space-xxsmall);
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  color: var(--ui-950);
  font-weight: var(--font-weight-medium);
  text-align: left;
}

.line-item__actions {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  flex-grow: 1;
  gap: var(--space-small-plus);
  margin-top: auto;
  align-self: flex-end;
}

/* The cart's stepper is the compact 36px variant: the row has to fit a whole
   stepper AND a Remove link inside the infos column, which on mobile is only
   seven of twelve tracks wide. Same component, tighter metrics. */
.line-item__quantity-selector { max-height: 36px; }
.line-item__quantity-selector.sklad-quantity { height: 36px; gap: var(--space-xsmall); padding: 0 var(--space-small); }
.line-item__quantity-selector.sklad-quantity .sklad-quantity__button { width: var(--space-large); }

.line-item__remove {
  --link-color: var(--ui-500);
  margin-left: var(--space-xxsmall);
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
}

.line-item__error { grid-column: 1 / -1; color: var(--error); }
.line-item__error:empty { display: none; }

@media (min-width: 1024px) {
  .line-item { gap: var(--space-large); padding-block: var(--space-medium-plus); }
  .line-item__media { grid-column: 1 / span 3; }

  .line-item__infos {
    grid-column: 4 / -1;
    grid-template-rows: repeat(3, auto) 1fr;
    row-gap: var(--space-small);
  }

  /* The row turns back into a table here: title and properties on the left,
     the shipping note in the middle, the price in the last three tracks —
     each under the matching column header above the list. */
  .line-item__infos__title { grid-column: 1 / span 5; }
  .line-item__properties   { grid-column: 1 / span 5; }  .line-item__delivery     { display: none; }

  /* Two lines here, three on mobile: the column is narrower but the row is
     also shorter, so the title gets less room to run on. */
  .line-item__properties__item--title { -webkit-line-clamp: 2; line-clamp: 2; }

  .line-item__price-container {
    grid-column: 10 / -1;
    flex-flow: column wrap;
    align-items: flex-end;
    row-gap: var(--space-xxsmall);
  }

  .line-item__price { margin-top: 0; text-align: right; }
  .line-item__actions { grid-row: 4; }
  .line-item__error { grid-column: 1 / 4; margin-top: var(--space-small); }

  .line-item__quantity-selector.sklad-quantity { padding: 0 var(--space-medium); }
}

/* --- Totals panel ---------------------------------------------------------- */

.cart-total { width: 100%; }
.cart-total__panel { display: flex; flex-direction: column; }

.cart-total__panel__content {
  display: flex;
  flex-direction: column;
  gap: var(--space-large);
  padding: var(--space-medium-plus);
  background: var(--beige-100);
  border-radius: var(--space-xxsmall);
}

.cart-total__rows { display: flex; flex-direction: column; gap: var(--space-large); }
.cart-total__row__group { display: flex; flex-direction: column; gap: var(--space-small); }
.cart-total__row { display: flex; align-items: flex-end; }

.cart-total__row__label,
.cart-total__row__value {
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  color: var(--ui-950);
  flex: 1 1 auto;
}

.cart-total__row__value { text-align: right; }
.cart-total__row__label--with-icon { display: inline-flex; align-items: center; gap: var(--space-xxsmall); }

.cart-total__row--muted .cart-total__row__label,
.cart-total__row--muted .cart-total__row__value {
  font-size: var(--text-xsmall-size);
  line-height: var(--text-xsmall-lh);
  color: var(--beige-600);
}

.cart-total__row--small .cart-total__row__label,
.cart-total__row--small .cart-total__row__value {
  font-size: var(--text-xsmall-size);
  line-height: var(--text-xsmall-lh);
}

.cart-total__row--total .cart-total__row__label,
.cart-total__row--total .cart-total__row__value {
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  font-weight: var(--font-weight-medium);
}

/* The rule sits on this group's top edge, so without room under it the total
   reads as if it were attached to the line rather than separated by it. */
.cart-total__divider {
  position: relative;
  padding-block-start: var(--space-medium);
}

.cart-total__divider::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--beige-200);
}

.cart-total__button,
.cart-total__sticky-checkout__button {
  --button-offset-x: 12px;
  --button-offset-y: 9px;
  width: 100%;
  justify-content: center;
  background-color: var(--ui-950);
  color: var(--ui-00);
}

.cart-total__button { min-height: 3rem; }

.cart-total__button:not([disabled]):hover,
.cart-total__sticky-checkout__button:not([disabled]):hover { background-color: var(--ui-700); }

.cart-total__button[disabled],
.cart-total__sticky-checkout__button[disabled] { background-color: var(--ui-200); color: var(--ui-700); }

/* Buy-now-pay-later. The brand chip keeps its own colour — it is a third
   party's mark, not ours to re-tint. */
.cart-total__bnpl { position: relative; }

.cart-total__bnpl__content {
  display: flex;
  align-items: center;
  gap: var(--space-small);
  margin: var(--space-large) 0 var(--space-large-plus);
  padding: var(--space-medium);
  border: 1px solid var(--ui-100);
  border-radius: var(--space-xxsmall);
  background: var(--ui-00);
}

.cart-total__bnpl__brand {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 8px;
  border-radius: 6px;
  background: #e75d36;
  color: var(--ui-00);
  font-family: var(--font-sans-serif);
  font-size: var(--text-ui-small-size);
  line-height: var(--text-ui-small-lh);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  font-weight: var(--font-weight-bold);
}

.cart-total__bnpl__text {
  margin: 0;
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-large-lh);
  color: var(--ui-950);
}

.cart-total__bnpl__link { --link-color: var(--ui-950); }

/* --- Reassurances ---------------------------------------------------------- */

.cart-total__reassurances { display: flex; flex-direction: column; }

.cart-reassurance {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-medium);
  padding-block: var(--space-medium-plus);
}

.cart-reassurance + .cart-reassurance { border-top: 1px solid var(--ui-100); }
.cart-reassurance__left { display: flex; align-items: center; gap: var(--space-xxsmall); }

.cart-reassurance__title {
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  font-weight: var(--font-weight-medium);
  color: var(--ui-950);
}

.cart-reassurance__meta,
.cart-reassurance__link span {
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  color: var(--ui-500);
}

.cart-reassurance__link { display: inline-flex; align-items: center; gap: var(--space-xxsmall); }

.cart-reassurance__payments {
  display: flex;
  align-items: center;
  gap: var(--space-small-plus);
  max-width: 215px;
  flex: 1;
}

/* The payment row carries four marks rather than one value, so it stacks its
   label above them instead of trying to sit on the same line. */
.cart-reassurance:has(.cart-reassurance__payments) {
  flex-direction: column;
  align-items: center;
  gap: var(--space-medium);
}

.cart-reassurance__payment-badge { height: 20px; color: var(--ui-500); }
.cart-reassurance__payment-badge svg { height: 100%; width: 100%; }

/* Stand-in for the card-scheme SVGs until the real marks are dropped in.
   Same 20px box, so swapping them changes nothing around it. */
.payment-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  padding-inline: 4px;
  border: 1px solid var(--ui-100);
  border-radius: var(--space-xxsmall);
  background: var(--ui-00);
  font-family: var(--font-sans-serif);
  font-size: var(--text-ui-small-size);
  line-height: var(--text-ui-small-lh);
  letter-spacing: 0.03em;
}

/* --- Mobile sticky checkout ------------------------------------------------ */

/* On a phone the totals panel sits below the whole list, so the checkout button
   is several screens away. This keeps it reachable, and retracts once you
   actually arrive at the real one. */
.cart-total__sticky-checkout {
  position: fixed;
  inset: auto 0 0;
  z-index: 12;
  padding: var(--space-small) var(--gutters-side) calc(var(--space-small) + env(safe-area-inset-bottom, 0px));
  background: var(--ui-00);
  border-top: 1px solid var(--ui-100);
  transform: translateY(0) translateZ(0);
  transition: transform var(--duration-medium) var(--ease-out-cubic);
}

.cart-total__sticky-checkout.is-hidden { transform: translateY(100%) translateZ(0); }

@media (min-width: 1024px) {
  .cart-total__sticky-checkout { display: none; }
}

/* --------------------------------------------------------------------------
   WOOCOMMERCE VARIATION FORM — the seams where Woo writes into our layout.

   These are Woo's class names, not ours. We do not style them as components;
   we only give what Woo injects somewhere sensible to land, and collapse the
   containers while they are empty so an unresolved variation costs no space.
   -------------------------------------------------------------------------- */

.single_variation_wrap > * + * { margin-block-start: var(--space-medium); }

.woocommerce-variation:empty,
.woocommerce-variation-availability:empty { display: none; }

.woocommerce-variation-availability .stock { margin: 0; color: var(--ui-500); }
.woocommerce-variation-availability .out-of-stock { color: var(--error); }

/* Woo marks the whole block while no complete variation is chosen. The button
   also carries a real [disabled] attribute — this is the visual half. */
.woocommerce-variation-add-to-cart-disabled .single_add_to_cart_button {
  cursor: not-allowed;
}

/* --------------------------------------------------------------------------
   SEARCH — a sheet that drops out of the header.

   The layout follows ferm LIVING: the input IS the headline rather than a box
   with a magnifier in it, and below it the sheet shows one of two states —
   what people search for when they have not typed anything, and results once
   they have. The class vocabulary is the reference's own search component, so
   this sits inside the system rather than beside it.

   Nothing here re-implements a product grid or a pill: .grid-products__list
   and .sklad-button carry those, exactly as they do on a collection page.
   -------------------------------------------------------------------------- */

.header-search {
  position: fixed;
  /* Anchored to where the header actually ends, measured when the sheet
     opens. The calc is only the pre-JS fallback: it adds the announcement
     bar's height unconditionally, so once that bar has scrolled away the sheet
     started 32px too low and the blurred overlay showed through the gap. */
  top: var(--search-top, calc(var(--header-height) + var(--announcement-bar-height, 0px)));
  left: 0;
  width: 100%;
  max-height: calc(100dvh - var(--search-top, var(--header-height)));
  /* Behind the header, not above it — which is what makes the sheet look like
     it drops out from underneath rather than landing on top. The header turns
     solid at the same moment (.header.is-search-open) so there is something
     for it to come out from. */
  z-index: calc(var(--z-header) - 1);
  opacity: 0;
  pointer-events: none;
  overflow: hidden auto;
  overscroll-behavior: contain;
}

.header-search.is-open { opacity: 1; pointer-events: auto; }

/* The trigger already toggles the sheet; this is what makes it say so. While
   search is open the magnifier becomes an X, so the control that opened the
   sheet is visibly the one that closes it — which matters most on mobile,
   where the sheet fills the screen and the header is the only thing left. */
.header__button--search .header__button__icon--close { display: none; }
.header.is-search-open .header__button--search .header__button__icon--search { display: none; }
.header.is-search-open .header__button--search .header__button__icon--close { display: inline-block; }

/* An overlay header has white marks on a photograph. The moment the sheet is
   out, the thing behind the header is a white sheet — so it takes its solid
   state for as long as search is open, whatever it was before. */
.header.is-search-open {
  --background-color: var(--ui-00);
  --border-color: var(--ui-100);
  background-color: var(--ui-00);
}

/* Nothing to re-reset here. Every .header rule that depends on .is-transparent
   is scoped :not(.is-search-open), so while the sheet is out the header simply
   is not transparent and its ordinary dark treatment applies — the search
   field included, which used to keep white text and a white underline on the
   now-white header and disappear into it. */

/* Sits behind the sheet and fills the rest of the viewport, so a click
   anywhere off the sheet closes the search. */
.header-search__overlay {
  position: fixed;
  /* Starts where the sheet starts, not at the top of the viewport. At inset:0
     it dimmed and blurred the announcement bar and the header along with the
     page, so the strip above the header read as switched off while search was
     open. Everything above --search-top now stays untouched. */
  top: var(--search-top, calc(var(--header-height) + var(--announcement-bar-height, 0px)));
  right: 0;
  bottom: 0;
  left: 0;
  z-index: -1;
  /* Lighter than it was, because it is now doing the job with a blur behind
     it rather than with opacity alone — the page stays legible as context
     instead of going flat grey. */
  background: var(--ui-950-20);
  backdrop-filter: var(--blur-24);
  -webkit-backdrop-filter: var(--blur-24);
  opacity: 0;
  transition: opacity var(--duration-medium) var(--ease-out-cubic);
}

.header-search.is-open .header-search__overlay { opacity: 1; }

.section-form-search {
  position: relative;
  background-color: var(--ui-00);
  padding-block-end: var(--space-xlarge);
  transform: translateY(-30px);
  opacity: 0;
  transition:
    transform var(--duration-medium) var(--ease-out-cubic),
    opacity var(--duration-fast) var(--ease-out);
}

.header-search.is-open .section-form-search { transform: translateY(0); opacity: 1; }

/* The close button is positioned, not laid out, so it must not take the
   content width every other child of the sheet gets. */
.section-form-search > * {
  width: 100%;
  max-width: var(--max-width-container);
  margin-inline: auto;
  padding-inline: var(--gutters-side);
}

/* --- The field ------------------------------------------------------------- */

.form-search { position: relative; border-block-end: 1px solid var(--ui-100); }

.form-search__inner {
  display: grid;
  grid-template-areas: "content";
  grid-template-columns: 1fr;
}

/* The input carries the display scale instead of a heading above it — one
   element doing the job of two, which is why it needs its own reset. */
.form-search .input {
  grid-area: content;
  padding-block: var(--space-large-plus);
  border-color: transparent;
  border-radius: 0;
  background: none;
  box-shadow: none;
  font-family: var(--font-serif-condensed);
  font-size: var(--title-h3-size);
  line-height: var(--title-h3-lh);
  letter-spacing: var(--tracking-display);
  font-weight: var(--font-display-weight);
  font-variant-ligatures: no-contextual;
}

.form-search .input::placeholder { color: var(--ui-300); opacity: 1; }

/* type="search" gets us the right keyboard and semantics, but WebKit also
   draws its own clear button inside the field — which sat next to ours, so the
   sheet showed two ✕. Ours stays, because it is the one that also resets the
   results. */
.form-search .input::-webkit-search-cancel-button,
.form-search .input::-webkit-search-decoration { appearance: none; display: none; }
.form-search .input:focus,
.form-search .input:focus-visible { outline: none; box-shadow: none; }

/* Submit and clear occupy the same cell as the input, pinned right. Only one
   is ever shown: clear once there is something to clear, submit otherwise. */
.form-search__submit-wrapper {
  grid-area: content;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  z-index: 1;
  pointer-events: none;
}

.form-search__submit {
  display: flex;
  align-items: center;
  gap: var(--space-xsmall);
  padding: var(--space-xsmall);
  border: 0;
  background: none;
  color: var(--ui-500);
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  cursor: pointer;
  pointer-events: auto;
  transition: color var(--duration-fast) var(--ease-out);
}

.form-search__submit:hover { color: var(--ui-950); }
.form-search.is-empty .form-search__submit--clear { display: none; }
.form-search:not(.is-empty) .form-search__submit:not(.form-search__submit--clear) { display: none; }

/* --- The two states -------------------------------------------------------- */

.search-empty__header,
.search-results__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-large);
  flex-wrap: wrap;
  margin-block-start: var(--space-large);
}

.header-search .search-results { display: none; }
.header-search.has-query .search-empty { display: none; }
.header-search.has-query .search-results { display: block; }

.search-scope { display: flex; flex-direction: column; gap: var(--space-small-plus); }

.search-scope__label {
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  color: var(--ui-500);
}

.search-scope__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-small);
  list-style: none;
  padding: 0;
  margin: 0;
}

/* Term suggestions. They sit directly under the field, next to what was
   typed, rather than below the results — the point is to correct or narrow the
   query before you read anything. */
.recommendations-terms {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-small) var(--space-medium);
  margin-block-start: var(--space-large);
}

.recommendations-terms__title {
  margin: 0;
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  color: var(--ui-500);
}

.recommendations-terms__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-small);
  list-style: none;
  padding: 0;
  margin: 0;
}

.recommendations-terms__item button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--ui-950);
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--ui-200);
  cursor: pointer;
  transition: text-decoration-color var(--duration-fast) var(--ease-out);
}

.recommendations-terms__item button:hover { text-decoration-color: var(--ui-950); }

/* The trailing product row is a .product-row like every other, only the top
   margin differs. */
.header-search .search-results .product-row { margin-block-start: var(--space-xxlarge); padding-inline: 0; }

.search-results__terms,
.search-empty .product-counts,
.header-search .search-results .product-counts { color: var(--ui-500); }

/* The grid is the collection page's grid, at a denser setting. In a sheet the
   products are a shortcut, not the destination — the reader is aiming at the
   field above them — so the row shows more of them at a smaller size rather
   than four the size of a listing. Items span 3 of the 24 desktop columns,
   which is eight across. */
.search-empty .grid-products__list,
.header-search .search-results .grid-products__list { margin-block-start: var(--space-medium); }

/* The count describes the grid, not the scope control it follows. It was flush
   against the pills — 0px — and then 16px off the grid, so it read as the last
   line of the control rather than the first line of the results. Pushed away
   from the pills and pulled towards what it counts. */
.header-search .search-results .product-counts { margin-block-start: var(--space-large); }

.header-search .search-results .product-counts + .grid-products__list {
  margin-block-start: var(--space-small-plus);
}

/* .grid-products__list carries padding-top: 24px for the collection page,
   where it separates the grid from the sort-and-filter row. In the sheet that
   stacked on top of the margin above and the real gap to the first image was
   32px, not the 8 the margin claimed. The sheet's spacing is the margin's
   job alone. */
.header-search .grid-products__list { padding-top: 0; }

@media (min-width: 1024px) {
  .form-search .input { padding-block: var(--space-xlarge); }

  /* Tightened: the gap under the label was reading as a break between the
     field and the results rather than as a heading for them. */
  .search-empty__header,
  .search-results__header { margin-block-start: var(--space-medium-plus); }

  .search-empty .grid-products__item,
  .header-search .search-results .grid-products__item { grid-column: span 3; }

  /* At an eighth of the row the swatch row and the description are noise —
     the name and the price are what a shortcut needs to carry. */
  .search-empty .card-product__desc,
  .header-search .search-results .card-product__desc,
  .search-empty .swatch-colors,
  .header-search .search-results .swatch-colors { display: none; }
}

/* --------------------------------------------------------------------------
   WISHLIST — a saved-products page.

   Structurally this is the collection listing with a different heading: the
   cards, the grid and the empty state are all components that already exist,
   so what is here is the page container and the one state the wishlist button
   never needed until now — saved.
   -------------------------------------------------------------------------- */

.wishlist__header {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-medium);
  flex-wrap: wrap;
  padding-bottom: var(--space-medium);
  border-bottom: 1px solid var(--ui-100);
}

.wishlist__title { margin: 0; }
.wishlist__count { color: var(--ui-500); }

.wishlist__list { grid-column: 1 / -1; margin-block-start: var(--space-large); }

@media (min-width: 1024px) {
  .wishlist__header { padding-bottom: var(--space-large-plus); }
  .wishlist__list { margin-block-start: var(--space-large-plus); }
}

/* --- Sold out ---------------------------------------------------------------
   WooCommerce puts `outofstock` (or `instock`) on the loop item itself, beside
   the post classes, so that is the hook rather than a modifier of our own —
   the state arrives from the server already on the element that wraps the card.

   The badge announced it before, but the card still offered a working "Add to
   bag": you could put a sold-out piece in your basket. The button goes, since
   the action does not exist, and the price drops back to secondary because it
   is no longer something you can act on. Everything else stays — the image,
   the carousel and the heart all still make sense on a piece you cannot buy
   yet, and saving it is the one useful thing left to do.
   --------------------------------------------------------------------------- */

.outofstock .card-product__add { display: none; }

.outofstock .sklad-price { color: var(--ui-500); }

/* --- Saved state ----------------------------------------------------------- */

/* The heart was write-only before: every card offered "add", nothing showed
   that a product was already saved. */
/* Saved. The heart fills from the same class that colours it, so nothing in
   markup or JavaScript has to manage the icon itself. */
.button-wishlist.is-active { color: var(--ui-950); }
.button-wishlist.is-active:hover { color: var(--ui-600); }
.button-wishlist.is-active .icon { fill: currentColor; }

/* --- Empty state ----------------------------------------------------------- */

.wishlist-empty {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-xlarge);
  min-height: 40vh;
  margin-bottom: var(--space-xxxlarge);
}

.wishlist-empty__title { margin: 0; }
.wishlist-empty__text { color: var(--ui-500); max-width: 46ch; margin-inline: auto; }
.wishlist-empty__button { width: max-content; }

/* --------------------------------------------------------------------------
   HEADER — SEARCH-FIELD VARIANT (test)

   .header--search-field drops the right-hand nav links and puts a search field
   in their place, so the two outer grid zones carry similar visual weight
   instead of the right being a short row of icons.

   The field is a <button>, not an <input>. Typing happens in the search sheet,
   which is already built and already focuses its own field on open; a real
   input here would mean two fields for one query and a decision about which
   one owns the text. A button that looks like a field and says what it does is
   the honest version.

   This is the header now — it was a variant on one page while it was being
   tried, and the scoping under .header--search-field is what made promoting it
   a markup change rather than a rewrite.
   -------------------------------------------------------------------------- */

.header--search-field .header__nav--secondary { display: none; }

/* An underline, not a box: a rule under the icon and the word, which is what
   a search field looks like when it is the only thing on that side of the
   header. A boxed input would read as a second, competing control next to the
   cart and account buttons. */
.header-search-field {
  display: none;
  align-items: center;
  gap: var(--space-small);
  width: 100%;
  /* Narrower, so the wordmark looks centred.
   *
   * The header is a three-part grid and the middle part holds the logo, but
   * what the eye centres on is the gap either side of it — and the right-hand
   * group is search plus three icons against four nav links on the left. At
   * 280 the field pushed that group far enough left that the wordmark read as
   * off-centre even though its column was not.
   *
   * This value is a judgement, not a measurement: it wants an eye on it at a
   * real width. */
  max-width: calc(200 * var(--design-ratio));
  padding: var(--space-xsmall) 0;
  border: 0;
  border-bottom: 1px solid var(--ui-950);
  border-radius: 0;
  background: none;
  color: var(--ui-500);
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  text-align: left;
  cursor: text;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

@media (min-width: 1024px) {
  .header--search-field .header-search-field { display: inline-flex; }
}

.header-search-field:hover { color: var(--ui-950); }

.header-search-field:focus-visible {
  outline: 2px solid var(--ui-950);
  outline-offset: 2px;
}

/* The glyph carries the field's identity, so it sits at the text's colour
   strength rather than the placeholder's. */
.header-search-field__icon {
  flex: 0 0 auto;
  font-size: 1.125rem;
  color: var(--ui-950);
}
.header-search-field__text { flex: 1 1 auto; }

/* The field replaces the icon, so the icon would be a second trigger for the
   same thing sitting right next to it. */
@media (min-width: 1024px) {
  .header--search-field .header__icons [data-search-open]:not(.header-search-field) { display: none; }

  /* While the sheet is out, the hidden icon button returns to the row — and
     the is-search-open swap above has already turned it into the X, so the
     desktop closes exactly where the mobile does. */
  .header--search-field.is-search-open .header__icons [data-search-open]:not(.header-search-field) { display: inline-flex; }

  /* And the field itself steps aside: with the sheet's own input on screen a
     second search box is noise, so only the X remains of the pair. */
  .header--search-field.is-search-open .header-search-field { display: none; }
}

/* On a transparent header the field has to stay visible over a photograph. */
.header--search-field.is-transparent:not(.is-search-open) .header-search-field {
  border-bottom-color: var(--ui-00);
  color: var(--ui-00);
}

/* ...unless the hero is pale, in which case the rest of the header has already
   flipped to black and a white field would be the one thing left invisible. */
.header--search-field.is-transparent:not(.is-search-open).is-black-and-white .header-search-field {
  border-bottom-color: var(--ui-950);
  color: var(--ui-950);
}

.header--search-field.is-transparent:not(.is-search-open) .header-search-field__icon { color: var(--ui-00); }
.header--search-field.is-transparent:not(.is-search-open) .header-search-field:hover { color: var(--ui-00); }

/* --------------------------------------------------------------------------
   LEGAL / STATIC PAGE

   Layout after the-brandidentity.com: a numbered index, then each section as a
   very large sticky number in the left column with the copy running down the
   right. The number is the only navigation you need in a long document — it
   tells you where you are without a floating menu.

   The body is .richtext, the CMS island from base.css, because this is exactly
   what that island is for: prose someone types into an editor, with headings
   and lists that have to look like headings and lists.
   -------------------------------------------------------------------------- */

/* --- Index ----------------------------------------------------------------- */

.legal__index {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--space-xsmall);
  padding: 0;
  margin: 0 0 var(--space-xxxlarge);
  list-style: none;
  counter-reset: legal;
}

.legal__index-item { counter-increment: legal; }

.legal__index-link {
  display: flex;
  align-items: baseline;
  gap: var(--space-large);
  color: var(--ui-950);
}

/* The number is generated, so the index cannot fall out of step with itself
   when a section is added or removed. */
.legal__index-link::before {
  content: counter(legal, decimal-leading-zero);
  flex: 0 0 auto;
  min-width: 2.5ch;
  color: var(--ui-500);
  font-variant-numeric: tabular-nums;
}

.legal__index-link:hover { color: var(--ui-600); }

/* --- Sections -------------------------------------------------------------- */

.legal__section {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  padding-block-end: var(--space-xxxlarge);

  /* The index links to these, and the header is sticky: without this every one
     of them parked the section heading exactly under it — measured at 56px of
     it covered, on all six. The offset is the one the number sticks at, so the
     section arrives with its number already in place rather than sliding into
     it. */
  scroll-margin-top: calc(var(--header-height) + var(--space-xlarge));
}

.legal__number {
  grid-column: 1 / -1;
  margin: 0 0 var(--space-large);
  color: var(--ui-950);
  font-variant-numeric: tabular-nums;
  line-height: 0.8;
}

.legal__body { grid-column: 1 / -1; }

.legal__title {
  margin: 0 0 var(--space-medium);
  padding-block-start: var(--space-medium);
  border-block-start: 1px solid var(--ui-100);
}

/* The title sits over the index rather than over the whole page: centred, it
   owned a full-width band nothing else shared, and it named a document whose
   contents were listed left-aligned underneath it. Same column as the index, so
   the two read as one block.

   The numbers stay where they were. The left column is theirs below this point
   — the index ends before the first section begins — and the pinned number is
   what tells you where you are in a long document. */
@media (min-width: 1024px) {
  .legal .page-header__title {
    grid-column: 1 / 13;
    text-align: left;
  }

  .legal__index { grid-column: 1 / 13; }

  /* The number pins while its own section scrolls past, then releases when the
     next section pushes it out — the section is the sticky container, so this
     needs no scroll listener. */
  .legal__number {
    grid-column: 1 / 9;
    position: sticky;
    top: calc(var(--header-height) + var(--space-xlarge));
    align-self: start;
    margin-bottom: 0;
  }

  .legal__body { grid-column: 13 / -1; }
}

/* --------------------------------------------------------------------------
   ACCOUNT FORMS — log in and register.

   One narrow centred column, after the-brandidentity.com. The two pages are
   siblings rather than tabs in one document: they are separate routes in
   WordPress, and the tab row is a pair of links that marks which one you are
   on. Fields are .field / .input, unchanged.
   -------------------------------------------------------------------------- */

.account {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  padding-inline: var(--gutters-side);
  padding-top: calc(var(--header-height) + var(--space-xlarge-plus));
  padding-bottom: var(--space-xxxlarge);
}

.account__inner {
  width: 100%;
  max-width: calc(438 * var(--design-ratio));
}

.account__tabs {
  display: flex;
  justify-content: center;
  gap: var(--space-large);
  margin-block-end: var(--space-xlarge);
}

.account__tab {
  padding-block-end: var(--space-xsmall);
  border-block-end: 2px solid transparent;
  color: var(--ui-500);
  transition: color var(--duration-fast) var(--ease-out);
}

.account__tab:hover { color: var(--ui-950); }
.account__tab[aria-current="page"] { color: var(--ui-950); border-block-end-color: var(--ui-950); }

/* Woo prints its login and registration notices here now, inside the shell.

   The margin is conditional because woocommerce_output_all_notices() prints its
   wrapper on every load whether or not it has anything in it — :empty would
   never match, and an unconditional margin would push the tabs down on the
   pages that have nothing to say. */
.account__notices {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
}

/* WooCommerce puts the action button BEFORE the message inside a notice and
   floats it right. The body is a flex item of the banner, so it contains that
   float and takes its full height — while the message stays on its own first
   line, 15px above the icon the banner has carefully centred against it.

   Laid out as a row instead: the message keeps the space it needs, the button
   is ordered past it and pushed to the end, and all three centre on one line.
   A notice with no button is unaffected — one item, still at the start. */
.wc-block-components-notice-banner__content {
  display: flex;
  align-items: center;
  gap: var(--space-medium);

  /* Wrapping, because on a phone the two do not fit one line: held there, the
     message truncated and the button shrank to 49px with its label cut to
     "Potvrdi". Given a second line each keeps its own width. */
  flex-wrap: wrap;
}

/* Qualified by the banner because WooCommerce ships
   `.woocommerce-info .button { float: right }` at the same specificity and
   loads after us — and something in its block CSS adds a 20px left margin on
   top. One class more and the outcome stops depending on load order. */
.wc-block-components-notice-banner .wc-block-components-notice-banner__content > .button {
  order: 1;
  float: none;
  flex: 0 0 auto;
  margin-inline: auto 0;
}

/* One step back from a single address to the pair. */
.account-back { margin-block: 0 var(--space-large); }

.account__notices:has(.woocommerce-error, .woocommerce-message, .woocommerce-info, .wc-block-components-notice-banner) {
  margin-block-end: var(--space-large);
}

.account__form { display: flex; flex-direction: column; gap: var(--space-medium); }

.account__submit { width: 100%; justify-content: center; margin-block-start: var(--space-small); }

/* The account forms are a two-column grid, and a <p> with no column of its own
   lands in one of them — which is how the submit ended up 280px wide under a
   576px column of fields. It spans, like every full-width row above it. */
.account__actions { grid-column: 1 / -1; margin: 0; }


.account__aside {
  display: flex;
  justify-content: space-between;
  gap: var(--space-medium);
  flex-wrap: wrap;
}

/* "ili" between the password form and the social sign-in: the .divider
   primitive's 1px line, split around one word. The word is the label of the
   alternative below it, so it takes the muted note colour, not text-950. */
.account__divider {
  display: flex;
  align-items: center;
  gap: var(--space-small);
  color: var(--ui-500);
}

.account__divider::before,
.account__divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--ui-100);
}

.account__link { --link-color: var(--ui-500); }
.account__note { color: var(--ui-500); }

@media (min-width: 1024px) {
  .account { padding-top: calc(var(--header-height) + var(--space-xxlarge-plus)); }
}

/* --------------------------------------------------------------------------
   FAQ / HELP PAGE

   Layout after fermliving.com/pages/shipping: the page title sticks in the
   left column while the questions scroll past it on the right. Same shape as
   the legal template, and the same reason — in a long document the heading is
   the thing that tells you where you are.

   The questions are .accordion, the button variant, which already animates its
   own height and is already initialised. Only the icon differs: plus/minus
   rather than a caret, which is what the reference draws for a Q&A list.
   -------------------------------------------------------------------------- */

.faq__intro { grid-column: 1 / -1; }

.faq__title { margin: 0 0 var(--space-large); }

.faq__lead { color: var(--ui-500); max-width: 58ch; }

.faq__body { grid-column: 1 / -1; margin-block-start: var(--space-xlarge); }

.faq__group + .faq__group { margin-block-start: var(--space-xxlarge); }

.faq__group-title {
  margin: 0 0 var(--space-medium);
  color: var(--ui-500);
}

/* The last question's bottom rule closes the list — .accordion__header only
   draws a top border, so without this the group ends on nothing. */
.faq__list { border-block-end: 1px solid var(--beige-200); }

@media (min-width: 1024px) {
  /* Over the column it heads, not over the whole page. Centred, it sat above a
     left-aligned intro and a list that starts two-thirds across, and belonged
     to neither — the legal pages already put their title on their own column. */
  .faq .page-header__title {
    grid-column: 1 / 10;
    text-align: left;
  }

  .faq__intro {
    grid-column: 1 / 10;
    position: sticky;
    top: calc(var(--header-height) + var(--space-xlarge));
    align-self: start;
  }

  .faq__body { grid-column: 12 / -1; margin-block-start: 0; }
}

/* --- Plus / minus icon ------------------------------------------------------
   Ported from the reference. Two glyphs, one shown at a time, swapped by
   .is-active — rather than one glyph rotated, because + turning into − is not
   a rotation.
   ---------------------------------------------------------------------------- */

.accordion .accordion__icon--plus { display: flex; align-items: center; justify-content: center; }
.accordion .accordion__icon--minus { display: none; }
.accordion.is-active .accordion__icon--plus { display: none; }

.accordion.is-active .accordion__icon--minus {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ui-800);
}

/* --------------------------------------------------------------------------
   CHECKOUT

   WooCommerce's classic checkout markup, styled with our components. Every
   class WooCommerce needs is present and unrenamed — .woocommerce-checkout,
   #customer_details, .form-row, .shop_table, #payment, #place_order and the
   rest — because its checkout script binds to them, updates the order review
   fragment through them, and injects validation messages into them.

   Same rule as the product form: Woo's class names are an API we consume, not
   a namespace we own. Where an element also has a look, it carries our class
   too — `class="form-row field"`, `class="input-text input"` — so the styling
   lives in one place and Woo's hooks stay untouched.

   Layout is ours: details on the left, the order review sticky on the right,
   on the shared .page-grid.
   -------------------------------------------------------------------------- */

.checkout__header {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-medium);
  flex-wrap: wrap;
  padding-bottom: var(--space-medium);
  border-bottom: 1px solid var(--ui-100);
  margin-bottom: var(--space-large);
}

/* form., not bare. WooCommerce puts woocommerce-checkout on <body> as well as
   on the form, so unqualified this made the whole document a grid — measured on
   the running page: body computed display:grid with a 40px row-gap it never
   asked for. The class is Woo's; the element we mean is ours to say. */
form.woocommerce-checkout {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  row-gap: var(--space-xlarge);
}

#customer_details { grid-column: 1 / -1; }

#order_review_heading { display: none; }

#order_review {
  grid-column: 1 / -1;
  align-self: start;
}

/* The tint is the box's, not the column's.
   The coupon and the delivery window used to sit on the same ground as the
   totals, which made them read as two more lines of the sum. They are below it
   now, on the page. */
/* One source for the space at the top and bottom, so the number here is the
   number you see.

   It was a sum before, and that is why trimming this rule alone did almost
   nothing: measured, the box gave 16px and the first and last cells gave 8px of
   their own, for 24px above "Proizvod" and 24px under "Ukupno". The cells keep
   their padding between rows — that is what separates the lines — and give it
   up only where they meet the edge of the box. */
.checkout-summary__box {
  padding: var(--space-medium) var(--space-medium-plus);
  background: var(--beige-100);
  border-radius: var(--space-xxsmall);
}

.checkout-summary__box .woocommerce-checkout-review-order-table thead th {
  padding-block-start: 0;
}

.checkout-summary__box .woocommerce-checkout-review-order-table tfoot tr:last-child th,
.checkout-summary__box .woocommerce-checkout-review-order-table tfoot tr:last-child td {
  padding-block-end: 0;
}

.checkout-summary__panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-medium);
}

/* --- Checkout sections ----------------------------------------------------
   The form asks five questions in the order a person answers them: who you
   are, where it goes, whether somewhere else, how it travels, how you pay.
   Each is a <section> with its own heading, so a phone screen shows a step
   rather than a wall. The headings are the same size everywhere; only the
   grouping is new. */
.checkout-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-medium);
}

.checkout-section__title {
  margin: 0;
  color: var(--text-primary);
}

/* The billing wrapper is now a stack of sections rather than one field grid. */
.woocommerce-billing-fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-xlarge);
}

/* The notes belong to Dostava but are not another address line, so they sit
   under the grid rather than inside it. */
.checkout-section .woocommerce-additional-fields { margin-block-start: var(--space-small-plus); }

.woocommerce-shipping-fields.checkout-section { gap: var(--space-medium); }

/* --- Način dostave -------------------------------------------------------
   The same card as Adresa za naplatu, one row per service: what it is and
   from which basket value it is free on the left, the price on the right.
   The list is the card, so it carries no gap of its own — the rows are
   separated by the card's hairlines. */
.checkout-shipping__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.checkout-shipping__item { margin: 0; }

/* The row's own flex comes from .choice-card__option; the text block takes the
   width and the price sits at the far end, so a long note wraps under the name
   and never pushes the figure off its column.

   Two classes deep on purpose. The list keeps WooCommerce's
   .woocommerce-shipping-methods, whose label rule further down (written for
   the cart page's rate list, where the label is the whole row) sets
   flex-wrap: wrap and space-between — measured on production 04.09.: the
   text block took the full width, the radio stood alone on the first line and
   the price dropped to a third. The rule below outranks it by specificity and
   says the opposite for these rows. */
.checkout-shipping__list .checkout-shipping__item { display: block; }

.checkout-shipping__list .checkout-shipping__option {
  display: flex;
  flex-wrap: nowrap;
  align-items: start;
  justify-content: flex-start;
  gap: var(--space-small);
}

.checkout-shipping__list .checkout-shipping__option input { flex: 0 0 auto; margin-block-start: 2px; }

/* And the price keeps the row's colour rather than the cart page's grey. */
.checkout-shipping__list .checkout-shipping__price .woocommerce-Price-amount { color: inherit; }

/* Before the postcode the rows are a price list, not a choice: same card, no
   radio, no hand cursor. */

.checkout-shipping__text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--space-xxsmall);
  min-width: 0;
}

.checkout-shipping__name { color: var(--text-primary); }

.checkout-shipping__note { color: var(--text-secondary); }

.checkout-shipping__price {
  flex: 0 0 auto;
  margin-inline-start: auto;
  padding-inline-start: var(--space-small);
  color: var(--text-primary);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  text-align: end;
}

.checkout-shipping__empty,
.checkout-shipping__package { margin: 0; color: var(--text-secondary); }

/* The body is a fragment: a sentence, then the card (or a package heading,
   then its card). They stack with the form's own gap so the sentence does
   not sit on the card's border — measured on production 05.09.: zero
   pixels between the two. */
.checkout-shipping__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-small-plus);
}

/* --- A choice with its consequence inside it -------------------------------
   Two answers that are both real, so a radio pair rather than a checkbox: a
   checkbox names one outcome and leaves the other implied, which is how
   "Dostava na drugu adresu" managed to be a question about billing.

   The fields open inside the option that opened them, so what the choice did
   is visible without looking anywhere else on the page. */
.choice-card {
  border: 1px solid var(--ui-200);
  border-radius: var(--radius-control);
  overflow: hidden;
}

.choice-card__option {
  display: flex;
  align-items: center;
  gap: var(--space-small);
  padding: var(--space-medium);
  border-block-start: 1px solid var(--ui-200);
  cursor: pointer;
}

.choice-card__option:first-child { border-block-start: 0; }

/* The whole row reads as chosen, not just the dot — on a phone the dot is the
   smallest thing on the screen and the row is what the thumb aimed at. */
.choice-card__option:has(input:checked) { background: var(--ui-50); }

/* A lone checkbox is a switch, not a choice between two named outcomes, so the
   row does not fill when it is on — the open panel underneath already says so,
   and a filled row with nothing to contrast against just looks selected. */
.choice-card--switch .choice-card__option:has(input:checked) { background: transparent; }

.choice-card__option:has(input:focus-visible) { outline: 2px solid var(--ui-950); outline-offset: -2px; }

.choice-card__label { margin: 0; }

/* The fields were stacking on their own margins, which on a phone put five
   controls in the height of four. Same grid and same gap as every other field
   wrapper on this page, so the panel reads as part of the form rather than as
   a denser copy of it. */
.choice-card__panel {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-medium);
  padding: var(--space-medium);
  border-block-start: 1px solid var(--ui-200);
  background: var(--ui-50);
}

.choice-card__panel > .form-row { margin: 0; }

/* Opened, these stop being optional — and the label has to say so.

   The server has always required all three once the box is on: a missing name,
   a PIB that fails its check digit or a matrički broj that is not eight digits
   each stop the order. The labels went on reading "(opciono)" through all of
   it, which is a form telling the customer one thing and the validator another.

   Done in CSS off :has() rather than in script, so the label cannot disagree
   with the checkbox that is sitting right above it. The input's aria-required
   is flipped alongside it, because a pseudo-element is not announced. */
.choice-card__panel .form-row.validate-required .optional { display: none; }

.choice-card__panel .form-row.validate-required .field__label::after {
  content: " *";
}

.choice-card__panel[hidden] { display: none; }

/* Payment already draws its own rule above itself. With a section heading now
   sitting there too, the rule would cut between the heading and what it
   heads. */
.checkout-section > .woocommerce-checkout-payment {
  margin-block-start: 0;
  padding-block-start: 0;
  border-block-start: 0;
  border-block-start: 0;
}

/* --- Collapsible summary --------------------------------------------------
   #order_review is a <details> now. On desktop nothing about it changes: the
   bar is hidden and ::details-content holds the panel open, so it stays the
   sticky column it was, open, with no dependence on the open attribute or on
   JavaScript. On a phone it collapses to one line: what you are buying and
   what it costs, with the table one press away. */
.checkout-summary__bar {
  display: none;
  align-items: center;
  gap: var(--space-small);
  cursor: pointer;
  list-style: none;
}

.checkout-summary__bar::-webkit-details-marker { display: none; }

/* Title and item count stack: the count is the same information the basket
   page's own heading carries, so a phone that opens the summary already
   knows how many items the table is about to show. */
.checkout-summary__label {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--space-xxsmall);
}

.checkout-summary__title { color: var(--text-secondary); }

/* Quieter than the title on purpose — the same size/colour pair the address
   review uses for its own meta line (.woocommerce-shipping-destination). */
.checkout-summary__count { color: var(--ui-500); }

.checkout-summary__total { font-weight: var(--font-weight-medium); }

.checkout-summary[open] .checkout-summary__bar .accordion__icon { transform: rotate(45deg); }

@media (min-width: 1024px) {
  /* Held open without the attribute, so a phone-width close does not follow
     the viewport back up to desktop. */
  .checkout-summary::details-content {
    content-visibility: visible;
    block-size: auto;
  }
}

@media (max-width: 1023px) {
  .checkout-summary__bar { display: flex; }

  /* The panel starts under the bar rather than against it. */
  .checkout-summary[open] .checkout-summary__panel { margin-block-start: var(--space-medium); }

  .checkout-summary__box { padding: 0; background: transparent; border-radius: 0; }

  /* #order_review again, the same reason the bar's own rule further down
     needs it: without the id this loses. .checkout-coupon (~8830) and
     .checkout-note (~8785) are plain classes that sit AFTER this rule in the
     file and carry the same specificity (one class each) — on a tie the
     LATER declaration wins no matter which one is behind a media query, so
     their own padding-inline: var(--space-medium-plus) kept winning here.
     Measured on the unmodified rule: both wrappers landed with a 20px start
     padding, nothing zeroed. .checkout-coupon__toggle carries none of its
     own (button.link resets it to 0), so the wrapper was the whole story.
     The id makes this rule outrank them on its own strength instead of on
     being last. */
  #order_review.checkout-summary .checkout-coupon,
  #order_review.checkout-summary .checkout-note { padding-inline: 0; }

  /* The line above "Imaš kupon?" — same token the table draws above "Ukupno"
     (.shop_table tfoot .order-total's border-block-start). */
  #order_review.checkout-summary .checkout-coupon {
    padding-block-start: var(--space-small);
    border-block-start: 1px solid var(--beige-200);
  }
}

/* On a phone the summary is the first thing on the page and it stays there.
   -------------------------------------------------------------------------
   It used to come last — after the payment method and after the button that
   spends the money — because the payment block moved into #customer_details so
   the two could share the left column on desktop. Ordering it above the form
   fixed that. This goes further: it sits under the header, edge to edge, and
   stays under the header as you scroll, so the total is answerable at any point
   in a long form rather than only at the top of it.

   The lift is done with `display: contents` on the form, and that is the whole
   trick. #order_review lives inside <form class="checkout">, two levels down,
   so no amount of `order` could put it above .checkout__header, which is the
   grid's own child. Removing the form's box for layout — only here, only under
   1024 — makes its children siblings of the header and the notices, and then
   one `order` puts them in the order a person reads them. The form still posts:
   display has nothing to do with submission, and the DOM is untouched, so
   desktop keeps the layout and the markup it already had.

   Sticky needs saying too. A grid item can only stick within its own grid area,
   which here would be one row and no travel at all; a flex item sticks within
   the flex container, which is the whole page. That is why this switches to a
   column rather than staying a grid. */
@media (max-width: 1023px) {
  .checkout.page-grid {
    display: flex;
    flex-direction: column;
    /* Zero, so the margins the pieces already carry keep their own spacing
       rather than having a container gap added on top of them. */
    gap: 0;
    /* Flush under the header. The 48px this used to add above the title is
       given back below the summary, so the title keeps the air it had. */
    padding-block-start: var(--header-height);
  }

  form.checkout { display: contents; }

  /* #order_review as well as the class: the base rule is an id selector, and a
     class alone loses to it on padding and radius no matter where it sits. */
  /* The bar is the tinted thing on a phone, edge to edge and stuck under the
     header, so the ground comes back to the column here — and the box inside it
     stops painting a second one on top of the first. */
  #order_review.checkout-summary {
    background: var(--beige-100);
    padding-block: var(--space-medium);
    order: -4;
    position: sticky;
    top: var(--header-height);
    /* Under the header at 200, over the form it scrolls across. */
    z-index: 10;
    /* The base rule sets align-self: start, which is right in a grid and wrong
       in a column — as a flex item it shrank the bar to its content, 250px of
       a 375px screen. */
    align-self: stretch;
    /* Edge to edge: out to the page gutter, and the gutter put back inside so
       the label still lines up with the title underneath it. */
    margin-inline: calc(var(--gutters-side) * -1);
    padding-inline: var(--gutters-side);
    border-radius: 0;
    margin-block-end: var(--container-padding-top);
  }

  /* Above even the summary. Woo prints its validation notices at the top of the
     form, which on a phone put them under the whole order summary — you pressed
     the button, the page did not move, and the reason was a screen below. */
  .woocommerce-NoticeGroup,
  form.checkout > .woocommerce-error { order: -5; }

  .checkout__header { order: -3; }
  .checkout__notices { order: -2; }
}

/* The page it is, not the page it inherits.
 *
 * Everywhere else the grid is holding a catalogue, and full width is right for
 * that. Checkout is a form: one column of fields with a summary beside it, and
 * at full width the fields ran long enough that the eye had to travel back
 * across the page for each one. Two columns off each side narrows it to
 * something you fill in rather than scan. */
@media (min-width: 1024px) {
  /* Fourteen columns: seven for the form, one of air, six for the summary.
     The form is 444px now rather than 645 — the reference form this one is
     modelled on is 450, and a field you can read end to end without moving your
     eyes is the point of the condensed shape. Fourteen columns divide evenly
     into twenty-four, so the block is centred again: 335px of margin either
     side, and 91px of channel between the two halves. */
  .checkout__header,
  .checkout__notices,
  .woocommerce-NoticeGroup,
  form.checkout > .woocommerce-error,
  form.checkout > .woocommerce-message,
  form.checkout > .woocommerce-info { grid-column: 6 / 20; }

  /* Equal halves, one gutter apart.
     The summary used to be a track narrower than the form and separated from it
     by a whole empty column, which read as two unrelated panels rather than one
     order seen from two sides. Seven tracks each, touching across the grid's own
     gutter, is the same page with the pieces the same size. */
  #customer_details { grid-column: 6 / 13; }

  #order_review {
    grid-column: 13 / 20;
    position: sticky;
    top: calc(var(--header-height) + var(--container-padding-top));
  }
}

/* The order button sat flush against the footer's dark ground, so the last
   thing on the page and the first thing after it shared an edge.

   Scoped to the page. `checkout` is one of WooCommerce's names, and Woo puts it
   on the mini cart's proceed link as well as on this page — so the unscoped
   rule gave that button 56px of padding under its label, an 85px slab where
   every other button in the drawer is 38px. The name is borrowed, not owned. */
.checkout.page-grid { padding-block-end: var(--space-xxlarge); }

/* The delivery window, under the money.
   A line rather than a panel: it belongs to the summary it sits in, and a
   second filled box inside a filled box reads as a warning, which this is not. */
.checkout-note {
  display: flex;
  align-items: start;
  gap: var(--space-xsmall);
  margin: 0;
  padding-inline: var(--space-medium-plus);
  color: var(--text-secondary);
}

.checkout-note svg {
  flex: none;
  width: var(--space-medium);
  height: var(--space-medium);
}

/* The same line on the product page, under the add-to-cart form: the truck
   and one sentence — "Dostava: Besplatna" or "Dostava: od 2.999 RSD". */
.product-delivery {
  display: flex;
  align-items: start;
  gap: var(--space-xsmall);
  margin: var(--space-small) 0 0;
  color: var(--text-secondary);
}

.product-delivery svg {
  flex: none;
  width: var(--space-medium);
  height: var(--space-medium);
}

/* The name and its size stack; the count sits under them, quieter. */
.checkout-review__meta {
  display: block;
  color: var(--text-secondary);
}

/* --- Checkout coupon -------------------------------------------------------
   Inside the summary box, under the totals it changes. It is not a form — see
   review-order.php — so it has no submit of its own and the fields stay hidden
   until asked for, the way Woo's own bar behaved.
   --------------------------------------------------------------------------- */

/* Outside the box, so it needs neither the rule that separated it from the
   totals nor the space that rule sat in. */
.checkout-coupon { padding-inline: var(--space-medium-plus); }

.checkout-coupon__toggle {
  padding: 0;
  border: 0;
  background: none;
  color: var(--ui-950);
  cursor: pointer;
}

/* The summary column is narrow enough that a field and a button side by side
   leave neither room to read. .sklad-button is width:100% by design, so it also
   resolves flex-basis to the full column — stacking is both the honest layout
   and the one that needs no override. */
.checkout-coupon__fields {
  display: grid;
  gap: var(--space-small);
  margin-block-start: var(--space-small);
}

.checkout-coupon__fields[hidden] { display: none; }
.checkout-coupon__input { width: 100%; }

/* The answer belongs under the field that caused it, in both places it is
   asked for. .coupon is a two-column grid, so the message has to be told to
   cross it or it lands in the gap beside the button. */
.checkout-coupon__message:empty,
.coupon__message:empty { display: none; }

.checkout-coupon__message,
.coupon__message {
  margin-block-start: var(--space-small);
  color: var(--ui-600);
}

.coupon__message { grid-column: 1 / -1; margin-block-start: 0; }

.checkout-coupon__message[data-state="error"],
.coupon__message[data-state="error"] { color: var(--status-error); }

/* --------------------------------------------------------------------------
   SUPPORT BAND — cart and checkout, directly above the footer.

   A hairline is the whole separation it needs: it belongs to the page it ends,
   not to the footer it precedes, and a filled panel here would read as a second
   footer stacked on the first.
   -------------------------------------------------------------------------- */

.support-band {
  border-top: 1px solid var(--ui-100);
  padding-block: var(--space-xlarge);
  text-align: center;
}

.support-band__inner {
  max-width: calc(720 * var(--design-ratio));
  margin-inline: auto;
  padding-inline: var(--gutters-side);
}

.support-band__icon .icon { --icon-size: 24px; }

.support-band__title { margin-block-start: var(--space-small); }

.support-band__lead {
  margin-block-start: var(--space-small);
  color: var(--ui-600);
}

/* Hours and number read as one line on a wide screen and as two on a narrow
   one. The row gap is what separates them once they wrap, so it is the smaller
   of the two — the column gap has to hold them apart mid-line. */
.support-band__meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xsmall) var(--space-large);
  margin-block-start: var(--space-medium);
}

/* --------------------------------------------------------------------------
   SEARCH RESULTS PAGE — the destination behind the header sheet.

   The sheet answers a query in place; this is where ?s= lands, and where you
   end up on a shared link or from the browser's own search. It reuses the
   collection's .grid-products wholesale — a search result IS a listing, so it
   gets no tile of its own — and only adds a header that states the query.
   -------------------------------------------------------------------------- */

.search-page { --container-padding-top: var(--space-xlarge-plus); }

.search-page__header {
  grid-column: 1 / -1;
  padding-block: var(--container-padding-top) var(--space-large);
}

@media (min-width: 1024px) {
  .search-page__header { grid-column: 1 / 15; }
}

.search-page__title { margin: 0; }

.search-page__meta {
  margin-block: var(--space-small) var(--space-medium);
  color: var(--ui-600);
}

.search-page__term { color: var(--ui-950); }

/* Deliberately NOT .form-search: that is the sheet's field, which carries the
   display scale in place of a heading. This page already has an h1, so the
   field here is an ordinary .input. */
.search-page__form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-small);
  align-items: start;
  max-width: 34rem;
}

.search-page__form .sklad-button { width: fit-content; }

/* --------------------------------------------------------------------------
   404 — deliberately quiet. A dead end is not the place for a hero.
   -------------------------------------------------------------------------- */

/* Centred on the same axis as the title above it. The body used to be
   left-aligned in columns 5-17 under a centred .page-header title, and a
   46vh min-height with centred content pushed it most of a screen away from
   that title — the two halves of one short message read as two things. */
/* Room to breathe.
   -------------------------------------------------------------------------
   A 404 is a short apology and two buttons on an otherwise empty page. Held
   tight under the heading it read as a form someone had forgotten to fill in.
   The space above pushes the message off the header instead of letting it hang
   from it, and the space below stops the footer arriving right after the last
   button on a page that has nothing else on it. */
.error-404__inner {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-medium);
  padding-block: var(--space-xxlarge) calc(var(--space-xxxlarge) * 2);
}

@media (min-width: 1024px) {
  .error-404__inner { padding-block: var(--space-xxxlarge) calc(var(--space-xxxlarge) * 2.5); }
}

@media (min-width: 1024px) {
  .error-404__inner { grid-column: 6 / 20; }
}

.error-404__code {
  margin: 0;
  color: var(--ui-500);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.error-404__title { margin: 0; }

.error-404__text {
  margin: 0;
  max-width: 46ch;
  color: var(--ui-600);
}

.error-404__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-small-plus);
  margin-block-start: var(--space-medium);
}

.error-404__actions .sklad-button { width: fit-content; }

/* --------------------------------------------------------------------------
   STORY CARD — the editorial counterpart to .card-product.

   Category label above the image rather than on it, because a story is filed
   under something whereas a product is badged. The label sits on a hairline so
   the column reads as a list of filed pieces, which is what gives the ferm
   LIVING grid its rhythm.

   Same 4:5 as the product card, deliberately: one ratio across the site means
   a "shop this story" row can put products and stories side by side without
   the grid stepping.
   -------------------------------------------------------------------------- */

.card-story {
  display: flex;
  flex-direction: column;
  color: inherit;
  text-decoration: none;
}

/* Under the image, directly above the title — the category reads as a label on
   the story rather than a header on the column. It carried a hairline above it
   when it sat at the top; below the image there is nothing to separate it from,
   so the rule is gone. */
.card-story__category {
  margin: var(--space-medium) 0 0;
  color: var(--ui-500);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.card-story__media {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background-color: var(--ui-50);
}

.card-story__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* .title-h6, not .title-h5: at four across the card is narrow enough that the
   larger level wrapped every title to three lines and swamped the image. */
.card-story__title {
  margin: var(--space-xsmall) 0 0;
  max-width: 22ch;
}

.card-story__more {
  margin-block-start: var(--space-small-plus);
  width: fit-content;
}

/* The whole tile is the link, so the "Read more" is a label rather than a
   second target — it takes the hover from the card, like the account tile. */
.card-story:hover .card-story__more .link__text {
  background-size: 100% 100%;
  background-position: left;
}

@media (hover: hover) and (pointer: fine) {
  .card-story__media img {
    transition: transform var(--duration-slow, 600ms) var(--ease-out);
  }
  .card-story:hover .card-story__media img { transform: scale(1.03); }
}

/* --- Stories on the homepage ------------------------------------------------
   Three across rather than the index's two: this is a teaser, so it wants to
   read as a row inside a longer page rather than as a grid that owns one.
   ---------------------------------------------------------------------------- */

/* The section is a tinted band, so it owns its ground and its own inset
   rather than sitting inside .container — a container would leave the tint
   stopping short of the page edge. */
.stories-teaser {
  background-color: var(--surface-lavender);
  padding-block: var(--space-xxlarge);
  padding-inline: var(--gutters-side);
}

/* Left-aligned, and the title is a small bold uppercase mark rather than a
   display heading — the reference gives this slot a label and a sentence, not
   a headline, because the cards below are the thing being looked at. */
.stories-teaser__header {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  margin-block-end: var(--space-xlarge);
}
.stories-teaser__intro {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
}

.stories-teaser__title {
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--ui-950);
}

.stories-teaser__subtitle {
  max-width: calc(535 * var(--design-ratio));
  margin: 0;
  color: var(--ui-600);
}

/* Below 1024 this is a peeking carousel, not a stack — the same treatment
   .three-up__carousel gets, and for the same reason: four stacked cards is a
   long scroll past the same shape four times, where a peek says there is more
   and costs one gesture to see it. The track bleeds past the section's inline
   padding so the next card is cut by the screen edge rather than by a margin. */
.stories-teaser__grid {
  display: flex;
  gap: var(--space-small-plus);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  /* Without this, `snap-align: start` snaps to the border edge, so the track
     immediately scrolls its own inline padding away and the first card sits
     flush to the screen instead of on the gutter the title above it uses.
     Measured: scrollLeft settled at 16 on load. */
  scroll-padding-inline: var(--gutters-side);
  scroll-behavior: smooth;
  scrollbar-width: none;
  margin-inline: calc(var(--gutters-side) * -1);
  padding-inline: var(--gutters-side);
}

/* On a phone a horizontal row uses the whole screen.

   Every one of these tracks pads both ends with the page gutter, so the row
   stopped short of the right edge and the space beyond it read as the end of
   the row rather than as more to come. Dropping only the end padding leaves the
   first item on the gutter the heading above it uses, runs the last item to the
   screen edge, and — because a start padding scrolls away with the content —
   puts items flush against both edges the moment you start scrolling.

   Phones only. On a desktop the row is not the width of the window and the
   gutter is what keeps it inside the page. */
@media (max-width: 1023px) {
  .mini-slider__track,
  .carousel__track,
  .stories-teaser__grid,
  .packshots__track {
    padding-inline-end: 0;
  }

  /* Snapping otherwise parks the last item a gutter short of the edge, undoing
     the line above for every row that snaps. */
  .mini-slider__track,
  .stories-teaser__grid { scroll-padding-inline-end: 0; }

  /* These two opt out of the default bleed because they sit inside a
     .container that already insets them — which on a phone is the padding that
     stops the row reaching the screen edge. Pull them back out by that inset
     and put the start back, as everywhere else. */
  .product-row .carousel__track,
  .collection-preview__items {
    margin-inline: calc(var(--gutters-side) * -1);
    padding-inline: var(--gutters-side) 0;
    /* These snap, so without this the first stop is the border edge: the track
       scrolls its own start padding away and a card parks flush to the screen
       while the heading above it stays on the gutter. Same line .mini-slider
       and .stories-teaser__grid already carry. */
    scroll-padding-inline: var(--gutters-side) 0;
  }

  /* An article puts a second inset in the way. The row above escapes the
     .container it is in, but inside .article.page-grid that container is itself
     inset by the page grid — so one gutter of negative margin cancels one of
     two and the row stops a gutter short of the screen. Measured at 392: the
     track ran 16 to 376 and the first card sat at 32, while the heading above
     it sat on 16.

     A second gutter rather than a viewport-relative bleed, because that is what
     it is: one inset for each ancestor that adds one. */
  .article-products .carousel__track {
    margin-inline: calc(var(--gutters-side) * -2);
  }

  /* .three-up__carousel has no padding of its own — it takes the gutter from
     the .container around it, which is why the rule above could not reach it.
     Pulling it out by that gutter and putting the start back gives the same
     result the other rows get. */
  .three-up__carousel {
    margin-inline: calc(var(--gutters-side) * -1);
    padding-inline: var(--gutters-side) 0;
    scroll-padding-inline: var(--gutters-side) 0;
  }

  /* Two steps down the type scale now. 32px is what the reference sets and
     what every other title-h3 on the site uses, but this one labels a row
     rather than opening a section — and above cards small enough to show two
     and a half of them, 28px was still the loudest thing on the screen. */
  .mini-slider__title {
    font-size: var(--title-h6-size);
    line-height: var(--title-h6-lh);
  }

  /* Two and a half cards, so the row reads as a row: at 200 board units it
     showed 1.8 and the third was a sliver you had to guess at. 142 lands 2.50
     at 375 and 2.63 at 768 — the whole range this board covers, since the
     desktop board takes over at 1024. */
  .mini-slider { --mini-slider-item-width: calc(142 * var(--design-ratio-fluid)); }
}

.stories-teaser__grid::-webkit-scrollbar { display: none; }

.stories-teaser__grid > .card-story {
  flex: 0 0 78%;
  scroll-snap-align: start;
}

@media (min-width: 1024px) {
  .stories-teaser__grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-xlarge) var(--gutters);
    overflow: visible;
    margin-inline: 0;
    padding-inline: 0;
  }

  .stories-teaser__grid > .card-story { flex: initial; }
}

.stories-teaser__more {
  display: flex;
  justify-content: flex-start;
  margin-block-start: var(--space-xlarge);
}

/* The homepage link sits beside the heading, not under the row.
 *
 * .stories-teaser__more is also the article product row's "Svi proizvodi" link,
 * which still belongs under its row — so this resets the top margin only where
 * the link has been moved into the header, rather than changing the class.
 *
 * On a phone it stays stacked under the subtitle: at 375px a heading and a link
 * on one line leaves neither enough room.
 */
.stories-teaser__header .stories-teaser__more {
  margin-block-start: 0;
}

@media (min-width: 1024px) {
  .stories-teaser__header {
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--gutters);
  }

  /* The link aligns with the label, which is the first line of the block —
     baseline rather than box top, because the two have different line
     heights and flex-start put the link a few pixels high. */
  .stories-teaser__header .stories-teaser__more {
    flex: 0 0 auto;
    align-self: baseline;
  }
}

/* Blocks the article drops into its own flow. A .product-row and a
   .stories-teaser arrive as siblings of the prose carrying none of the
   article's vertical rhythm, so both butted straight against the paragraph
   above them — measured 0px in each case. */
.article-products { margin-block-start: var(--space-xxlarge); }

/* The related band is tinted and full-bleed, so it needs air above it as well
   as inside it: with none, the colour started on the last line of the product
   row's prices. Its own padding goes to the page's section step, 72, rather
   than the 56 the untinted sections use — a band has to hold its contents
   away from its own edges. */
.article-related {
  /* The same full-width band the homepage gives this block. It sits inside
     .article.page-grid, which carries the page's 64px inline padding, so the
     tint stopped short of both edges and read as a panel rather than a section.
     The negative margin is the pattern .carousel__track already uses to bleed;
     .stories-teaser supplies the matching padding-inline itself.

     No padding-block of its own any more: .stories-teaser's own block padding is
     what the homepage shows, and overriding it here made the same component
     taller in one place than in the other for no reason anyone could point at. */
  margin-block-start: var(--space-xxxlarge);
  margin-inline: calc(var(--gutters-side) * -1);
}

/* --- The stories grid ------------------------------------------------------- */

.blog__grid {
  /* Two across from the smallest screen up. One card per row meant a phone
     showed a single guide per screenful and the index read as a list of covers
     rather than as a set to choose from — the product grid has been two across
     on a phone all along, and a story card is the same shape. */
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-xxlarge) var(--gutters);
  padding-block-end: var(--space-xxxlarge);
}

/* Three across on desktop. Six stories in twos was three rows of a shape the
   reader has already understood by row two; in threes the whole index is two
   rows and the page ends where it should. */
@media (min-width: 1024px) {
  .blog__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* --------------------------------------------------------------------------
   ARTICLE — a single story.

   Full-bleed hero, then everything else on one narrow measure. The date sits
   above the title rather than beside it: on a story the date is context you
   read past, not a field you scan.
   -------------------------------------------------------------------------- */

/* The hero is the page's header, so there is no container padding above it —
   only the sticky-header clearance .page-grid already pays. */
.article { --container-padding-top: var(--space-large); }

/* The story opens as two columns: what the piece is on the left, the photograph
   on the right. It used to run the image the full width of the window at 3:2,
   which is 960px tall on a 1440 screen — the whole first screen was one picture
   and the title was below the fold.

   4:5 so a single upload serves the story, the card that links to it and the
   phone. That is the ratio the rest of the site is built on, and the card
   preview was already using it: one image, cropped nowhere. */
.article__intro {
  grid-column: 1 / -1;
  /* The positioning root for ::before below. */
  position: relative;
  /* A grid item may not shrink below its content unless told it can, and the
     photograph has no width of its own to be measured against — so it reported
     its full intrinsic size, took the column with it and the pair came out
     2334px wide inside a 1440px window. */
  min-width: 0;
  display: grid;
  gap: var(--space-large);
  align-items: start;
}

.article__intro > * { min-width: 0; }

/* .article__meta and .article__title are shared with the editorial content
   pages, where they sit in a plain block and their own margins do the spacing.
   Here the column is a flex stack with a gap, so those margins would be added
   on top of it — 40px between meta and title where the gap already says 24.
   Zeroed for this one context rather than changed at the source. */
.article__intro-text .article__meta,
.article__intro-text .article__title { margin: 0; }

.article__intro-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-large);
}

@media (min-width: 1024px) {
  /* The photograph is 4:5 and the text beside it is roughly half that tall, so
     centring it left a block adrift in the middle of a column with nothing
     above or below. Pinned to the top it starts where the reader's eye already
     is, and rides the picture's height the way the homepage diptych rides its
     panel — same trick, one axis over.

     Nothing between this and the scroll root may carry overflow: hidden or a
     transform; either cancels the sticky without a word. */
  .article__intro-text {
    position: sticky;
    top: calc(var(--header-height) + var(--space-xlarge));
  }
}

.article__hero {
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background-color: var(--ui-50);
}

@media (min-width: 1024px) {
  .article__intro {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--gutters);
  }
}

@media (max-width: 1023px) {
  .article__hero { aspect-ratio: 4 / 5; }
}

.article__hero img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The warm light that spills off the photograph lives with the diptych, which
   is the other half of what it serves — see "Light off a photograph". */

/* Any full-width block dropped into the article — a product row, the related
   stories — is a grid child, so without a column it lands in ONE of the 24
   tracks. Same trap the blog nav fell into. */
.article > section,
.article > .container {
  grid-column: 1 / -1;
}

.article__inner {
  grid-column: 1 / -1;
  padding-block: var(--space-xxlarge) 0;
}

.article__inner:last-child { padding-block-end: var(--space-xxxlarge); }

/* The story page builds its measure out of several .article__inner blocks: the
   template owns the first (date and title) and the last (share and back link),
   the imported article owns the ones between. Where two land next to each
   other with no image block separating them they are one column of prose, not
   two sections, so the second one does not pay the top step again. */
.article__inner + .article__inner { padding-block-start: 0; }

@media (min-width: 1024px) {
  .article__inner { grid-column: 6 / 20; }
}

.article__meta {
  margin: 0 0 var(--space-medium);
  color: var(--ui-500);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.article__title {
  margin: 0 0 var(--space-xlarge);
}

.article__lead {
  margin: 0 0 var(--space-large);
  color: var(--ui-800);
}

/* --- Article media ----------------------------------------------------------
   The reference alternates text on a narrow measure with image blocks that
   break out wider — a pair of portraits, or one landscape. Breaking out is the
   point: it is what stops a long read from looking like a document.

   These are siblings of .article__inner in the page grid rather than children
   of it, because a child cannot be wider than the column it sits in.
   ---------------------------------------------------------------------------- */

.article-media {
  grid-column: 1 / -1;
  display: grid;
  gap: var(--gutters);
  margin-block: var(--space-xxlarge);
}

.article-media figure { margin: 0; }

.article-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  background-color: var(--ui-50);
}

/* Two portraits side by side — the block the reference uses most. */
.article-media--pair { grid-template-columns: minmax(0, 1fr); }
.article-media--pair figure { aspect-ratio: 4 / 5; }

@media (min-width: 768px) {
  .article-media--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* One landscape, the full measure of the grid. */
.article-media--wide figure { aspect-ratio: 3 / 2; }

@media (max-width: 1023px) {
  .article-media--wide figure { aspect-ratio: 4 / 3; }
}

@media (min-width: 1024px) {
  .article-media--pair { grid-column: 4 / 22; }
  .article-media--wide { grid-column: 3 / 23; }
}

.article-media__caption {
  margin-block-start: var(--space-small);
  color: var(--ui-500);
}

/* A heading introducing a block rather than a paragraph — centred like the
   reference's section headings. */
.article__section-title {
  grid-column: 1 / -1;
  margin-block: var(--space-xlarge) var(--space-large);
  text-align: center;
}

@media (min-width: 1024px) {
  .article__section-title { grid-column: 6 / 20; }
}

/* --------------------------------------------------------------------------
   SHARE — a row of destinations, not a row of logos.

   No brand marks: the icon set is Lucide, which carries none, and drawing
   approximations of other companies' logos to sit under ours is not worth it.
   The names do the work, which also matches how every other link on the site
   behaves.
   -------------------------------------------------------------------------- */

.share {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-small-plus) var(--space-medium);
  margin-block-start: var(--space-xxlarge);
  padding-block-start: var(--space-large);
  border-block-start: 1px solid var(--border-subtle);
}

.share__label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xsmall);
  margin: 0;
  color: var(--ui-500);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  flex: 0 0 auto;
}

.share__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-small-plus) var(--space-medium);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Feedback for "Copy link", which is the one share target that does not leave
   the page and so has nothing else to confirm it worked. */
.share__copied {
  color: var(--success);
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.share__copied.is-visible { opacity: 1; }

/* The copy button is a <button> wearing .link, so it needs the row's baseline
   rather than a button's own box. */
.share__list li { display: inline-flex; align-items: baseline; gap: var(--space-small); }

/* --- Story page furniture --------------------------------------------------- */

/* The article has no .page-header — its hero is the header — so the breadcrumb
   row carries the same spacing on its own. */
.article__crumbs {
  grid-column: 1 / -1;
  margin-block-start: calc(var(--container-padding-top, 0px) * -1);
}

.article__back { margin-block-start: var(--space-xlarge); }

/* The story categories are the collection page's pills, centred like the
   account tabs — same component, third context. */
.blog-nav__list { justify-content: center; flex-wrap: wrap; padding-inline: 0; }

/* The collection page hides its pill list below 1024 and swaps in a select,
   because it can carry forty categories. A blog has six, so they wrap instead
   — this opts back out of that hiding. */
@media (max-width: 1023px) {
  .blog-nav__list {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
    white-space: normal;
  }
}

/* It is a child of .page-grid, so without a column it lands in ONE of the 24
   tracks — 177px — and every pill wraps onto its own line. */
.blog-nav {
  grid-column: 1 / -1;
  /* .collection-nav is a flex row and the list is one item in it, so centring
     the list only centred its own pills inside a shrink-wrapped box. The row
     has to centre the box. */
  justify-content: center;
  overflow: visible;
  margin-block-end: var(--space-xlarge);
}

/* .sklad-button is width:100% and the CONTAINER opts it down — the collection
   page never needed this because its row does not wrap, so nothing there ever
   let a button reach full width. */
.blog-nav__list .sklad-button {
  width: fit-content;
  margin-top: 0;
}

/* --------------------------------------------------------------------------
   PAGE HEADER — the collection page's header, made reusable.

   The collection page builds its header out of .hero, which carries machinery
   this does not need: a media layer, alignment custom properties, fill modes.
   What the other pages actually need is the SHAPE — a breadcrumb and a meta
   figure on one row, then a centred title at .title-h2 — so that is what this
   is. Same type scale, same gutters, same rhythm; none of the hero's weight.

   Before this the site had eight different page headers: --h1, --h2, --h3,
   --h4, and four pages where the h1 was visually hidden and the page simply
   started.
   -------------------------------------------------------------------------- */

.page-header {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  /* .page-grid pays `header-height + container-padding-top` above its first
     child. The header-height part is needed — it clears the sticky header —
     but the container padding is not: the collection page's header sits
     directly under the header, and this has to start at the same place. */
  margin-block-start: calc(var(--container-padding-top) * -1);
  padding-block: 0 var(--space-large);
}

/* Breadcrumb left, meta right — the same row the collection page uses. Baseline
   alignment so the two read as one line even at different sizes. */
.page-header__nav {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-medium);
  /* Matches the reference's .hero-navigation exactly: 20px above the trail, none
     below, so the row is 20 + 18 = 38px. It was 32/8 for a while, which matched
     our OWN collection page at the time — but that page was itself 20px too
     tall, because .product-counts carried a top margin the reference does not
     have. Matching a deviation propagates it. */
  padding-block: var(--space-medium-plus) 0;
  /* The gap to the title is 18 at mobile and 34 at desktop in the reference —
     it grows with the title, which goes 46px to 68px across the breakpoint. */
  margin-block-end: var(--space-medium-plus);
}

.page-header__meta {
  flex: 0 0 auto;
  color: var(--ui-500);
}

/* Centred on the same measure the collection page uses — 16 of 24 columns —
   so a long title wraps at the same width rather than running the full grid. */
.page-header__title {
  grid-column: 1 / -1;
  margin: 0;
  text-align: center;
}

.page-header__intro { grid-column: 1 / -1; }

@media (min-width: 1024px) {
  .page-header__title,
  .page-header__intro { grid-column: 4 / 22; }

  .page-header__nav { margin-block-end: var(--space-large-plus); }
}

/* A line under the title — where a page has one, it is the same distance from
   the title on every page rather than each one choosing. */
/* Every page in a set that shares a header must fill this slot, or the tabs
   below it move as you switch between them — that was a 26px jump across the
   account pages. min-height reserves the line so an empty slot still holds it,
   and two lines on a phone, where the same sentence wraps on some pages and
   not others. The reserved space is whitespace; the moving tabs were not. */
.page-header__intro {
  min-height: var(--text-paragraph-lh);
  margin-block: var(--space-small) 0;
  text-align: center;
  color: var(--ui-600);
}

.page-header__intro a { color: var(--ui-950); }

@media (max-width: 1023px) {
  /* Bottom only. The base rule deliberately has no top padding — .page-grid
     already pays the header clearance — and this used to re-add 24px of it,
     which pushed the whole header down the page on a phone. */
  .page-header { padding-block: 0 var(--space-medium-plus); }
  .page-header__intro { min-height: calc(var(--text-paragraph-lh) * 2); }
}

/* --------------------------------------------------------------------------
   MY ACCOUNT — the logged-in area.

   WooCommerce renders every endpoint (dashboard, orders, a single order,
   addresses, account details) into ONE template: a navigation beside a content
   well. So the shell is defined once here and each page only supplies what
   goes in the well.

   .account, above, is the logged-OUT pair — login and register — and stays a
   narrow centred column. This is deliberately the wider two-column layout,
   because the account area is a place you work in rather than pass through.
   -------------------------------------------------------------------------- */

.my-account { --container-padding-top: var(--space-xlarge-plus); }

.my-account__header {
  grid-column: 1 / -1;
  padding-block: var(--container-padding-top) var(--space-large);
}

.my-account__title { margin: 0; }

.my-account__intro {
  margin-block: var(--space-small) 0;
  color: var(--ui-600);
}

.woocommerce-MyAccount-navigation { grid-column: 1 / -1; }

/* The same pills the collection page uses for its sub-categories, and the same
   component: each item is a real .sklad-button--grey with .is-selected marking
   the current one. It was a row of bare text links, which made the account's
   navigation the only set of tabs on the site that did not look like tabs.

   In WordPress this needs `myaccount/navigation.php` overridden so the classes
   reach the markup — one of the templates a theme overrides as a matter of
   course. */
.woocommerce-MyAccount-navigation ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-small-plus);
  margin: 0;
  padding: 0;
  list-style: none;
}

.woocommerce-MyAccount-navigation .sklad-button {
  width: fit-content;
  flex-shrink: 0;
  white-space: nowrap;
}

/* Same selected treatment as .collection-nav — the border, not a fill. */
.woocommerce-MyAccount-navigation .sklad-button--grey.is-selected {
  border-color: var(--ui-950);
}

.woocommerce-MyAccount-content {
  grid-column: 1 / -1;
  /* Capped and centred. With the navigation moved out of the left rail the
     well ran the full 24 columns — 1312px at 1440 — which is fine for a row of
     tiles and far too wide for anything you read or fill in. */
  width: 100%;
  /* A fixed measure, not a design-ratio one. --base-design-viewport switches
     to 392 below 1024, so a design-ratio cap stops binding at exactly the
     tablet widths where it is most needed. How wide a line can be before it
     is hard to read does not scale with the design board. */
  max-width: 67rem;
  margin-inline: auto;
  /* The tabs are a control strip, not a heading, so the content needs a gap
     that reads as a break rather than as the next line of the same block —
     24px did not. And the account is the one place on the site where the
     page can end on a short paragraph, which put the footer directly under
     two lines of text. */
  padding-block: var(--space-xxlarge) var(--space-xxxlarge);
}

@media (min-width: 1024px) {
  /* Same tail the cart's empty state leaves: on a wide screen 72px under the
     last line is a footer that starts before the page has finished. */
  .woocommerce-MyAccount-content { padding-block-end: 128px; }
}

/* A form is a single column of fields, so it takes the same measure the
   logged-out forms use — .account__inner is 438 too. A 1312px-wide text input
   is not a form, it is a horizon. */
/* A grid, like every other Woo field wrapper. .form-row carries margin:0 and
   grid-column:1/-1 because it is built to sit in a grid that supplies the
   gap — in a display:block form it got neither, and the fields stacked flush
   against each other with 0px between them. The two-column track also lets
   form-row-first / form-row-last pair up, which is what those classes are for. */
/* The account form holds its rows directly, so it is the grid. The address
   form holds a __field-wrapper instead, and that wrapper is the grid — it was
   simply missing from the list of Woo field wrappers above, which is why its
   rows stacked one per line at a single column's width. */
.woocommerce-EditAccountForm {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-medium);
}

/* .account-back is in here so the way out of a single address sits on the
   form's own left edge. As a child of .woocommerce-MyAccount-content it ran
   the full 1072px while the form below it was 576 and centred, so the link
   hung off in the margin with nothing under it. Same measure, same centring,
   one rule — not a copy of the number. */
.woocommerce-EditAccountForm,
.woocommerce-address-fields,
.account-back {
  width: 100%;
  max-width: 36rem;
  margin-inline: auto;
}

/* Only the fieldset. Adding `> p` here too was a 0,2,0 rule that outranked
   .form-row-first / .form-row-last and pinned every row to the full width —
   .form-row already defaults to 1/-1 on its own. */
.woocommerce-EditAccountForm > fieldset { grid-column: 1 / -1; }

/* The submit row, and only it.
 *
 * WooCommerce wraps both save buttons in a bare <p> with no class of its own.
 * In a two-column grid that paragraph took one column, so the button sat at
 * half the form's width and off its axis. Selecting the class-less paragraph
 * reaches that row and nothing else — a plain `> p` would outrank
 * .form-row-first and .form-row-last and pin every field to the full width,
 * which is the rule that had to be taken out of the line above.
 *
 * The button then fills the row, so it is the width of the form it submits
 * rather than the width of the words on it. */
.woocommerce-EditAccountForm > p:not([class]),
.woocommerce-address-fields > p:not([class]) {
  grid-column: 1 / -1;
  margin-block-start: var(--space-medium);
}

.woocommerce-EditAccountForm > p:not([class]) button[type="submit"],
.woocommerce-address-fields > p:not([class]) button[type="submit"] {
  width: 100%;
}

/* The fieldset is its own grid, so the rows inside it get the same rhythm. */
.woocommerce-EditAccountForm fieldset .woocommerce-form-row + .woocommerce-form-row,
.woocommerce-address-fields fieldset .woocommerce-form-row + .woocommerce-form-row {
  margin-block-start: var(--space-medium);
}

/* The navigation stays a horizontal bar at every width, centred under the
   page header. It was a left rail on desktop, which pushed the content into
   18 of 24 columns and left the addresses too narrow to sit side by side —
   and put the account's own navigation in a different place from every other
   page's. Centred keeps it on the header's axis. */
@media (min-width: 1024px) {
  .my-account__header { grid-column: 1 / -1; }
}

.woocommerce-MyAccount-content > :first-child { margin-block-start: 0; }
.woocommerce-MyAccount-content h2 { margin-block: var(--space-xlarge) var(--space-medium); }
.woocommerce-MyAccount-content h2:first-child { margin-block-start: 0; }

/* --- Dashboard tiles --------------------------------------------------------
   The dashboard is a directory, not a document: its whole job is to send you
   somewhere else. Tiles say that better than a paragraph and a list of links,
   and they give each destination room for a line explaining what is behind it.

   The tile is a link but not a TEXT link — the whole block is the target — so
   it is excluded from the underline rule the same way the footer reassurance
   items are. Its title carries the hover instead, which is what tells you the
   block is clickable.
   --------------------------------------------------------------------------- */

.account-tiles {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-medium);
  margin-block-start: var(--space-large);
}

@media (min-width: 640px) {
  .account-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .account-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.account-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  padding: var(--space-large);
  min-height: 10rem;
  border: 1px solid var(--border-default);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--duration-fast) var(--ease-out);
}

.account-tile:hover { border-color: var(--ui-950); }

/* Same treatment every other standalone link gets, on the element that reads
   as the link. */
.account-tile__title {
  background-image: linear-gradient(
    transparent calc(100% - 1px),
    var(--link-underline-color, var(--ui-200)) 1px
  );
  background-repeat: no-repeat;
  background-size: 0% 100%;
  transition: background-size var(--duration-fast) var(--ease-out);
}
.account-tile:hover .account-tile__title { background-size: 100% 100%; background-position: left; }

.account-tile__title {
  margin: 0;
  width: fit-content;
}

.account-tile__text {
  margin: 0;
  color: var(--ui-600);
}

/* The dashboard's own actions sit above the tiles. */
.account-dashboard__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-medium);
  margin-block-start: var(--space-large);
}

.account-dashboard__actions .sklad-button { width: fit-content; }

.account-dashboard__logout { margin-block-start: var(--space-xlarge); }

/* Sits above the panel it returns you from. The breadcrumb already leads back,
   but a breadcrumb is orientation, not an action — a single order is somewhere
   you go INTO from a list, so the way out belongs on the page itself. */
.woocommerce-MyAccount-back {
  margin: 0 0 var(--space-large);
}

/* --- Orders table ----------------------------------------------------------
   Woo gives it .shop_table_responsive and a data-title on every cell, which is
   the hook its own stylesheet uses to turn the table into stacked rows on a
   phone. Same trick here, so the markup needs no variant.
   --------------------------------------------------------------------------- */

.woocommerce-orders-table { width: 100%; border-collapse: collapse; }

.woocommerce-orders-table th,
.woocommerce-orders-table td {
  padding-block: var(--space-small-plus);
  text-align: left;
  font-size: var(--text-medium-size);
  border-block-end: 1px solid var(--border-subtle);
}

.woocommerce-orders-table thead th {
  font-size: var(--text-xsmall-size);
  font-weight: var(--font-weight-regular);
  color: var(--ui-500);
}

.woocommerce-orders-table__cell-order-total,
.woocommerce-orders-table__header-order-total,
.woocommerce-orders-table__cell-order-actions,
.woocommerce-orders-table__header-order-actions { text-align: right; }

.woocommerce-orders-table__cell-order-number a { color: var(--ui-950); }

@media (max-width: 1023px) {
  .woocommerce-orders-table thead { display: none; }

  .woocommerce-orders-table tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-xsmall) var(--space-small);
    padding-block: var(--space-medium);
    border-block-end: 1px solid var(--border-subtle);
  }

  /* th as well as td: the order number is a <th scope="row">, and resetting
     only td left it carrying the desktop rule's block padding and bottom
     border — a rule under the order number, half the width of the row, that
     read as a divider inside the order rather than between two. */
  .woocommerce-orders-table th,
  .woocommerce-orders-table td { padding-block: 0; border: 0; text-align: left; }

  /* The header text has to come back from somewhere once thead is gone. */
  .woocommerce-orders-table td::before {
    content: attr(data-title) " ";
    color: var(--ui-500);
  }

  /* The number and the one action share the first row; everything else takes
     the full width under them.

     It used to be two columns all the way down, with the actions cell spanning
     the first two rows. The second column is auto, and the total sits in it —
     so "164.199 RSD za 2 proizvoda" sized that column to 216 of 375px and left
     the date 151px to wrap "6. septembar 2026." across two lines, with the
     action floating between them. Measured at 375; the total ran off the right
     edge as well.

     td.… rather than the class alone, because .woocommerce-orders-table td::before
     above carries an element selector and would otherwise win on specificity —
     which is why the label "Radnje" was printing next to the button. */
  .woocommerce-orders-table__cell-order-actions {
    grid-column: 2;
    grid-row: 1;
    align-self: center;
    text-align: right;
  }

  .woocommerce-orders-table td.woocommerce-orders-table__cell-order-actions::before { content: none; }

  .woocommerce-orders-table__cell-order-date,
  .woocommerce-orders-table__cell-order-status,
  .woocommerce-orders-table__cell-order-total { grid-column: 1 / -1; }
}

/* --- Addresses -------------------------------------------------------------- */

.woocommerce-Address-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-medium);
  margin-block-end: var(--space-small);
}

.woocommerce-Address-title h2,
.woocommerce-Address-title h3 { margin: 0; }

.woocommerce-Address address {
  font-style: normal;
  font-size: var(--text-medium-size);
  line-height: var(--text-paragraph-lh);
  color: var(--ui-800);
}

/* --- Account forms ---------------------------------------------------------- */

.woocommerce-EditAccountForm fieldset {
  margin-block-start: var(--space-xlarge);
  padding: 0;
  border: 0;
  border-block-start: 1px solid var(--border-subtle);
  padding-block-start: var(--space-large);
}

.woocommerce-EditAccountForm legend {
  padding: 0;
  margin-block-end: var(--space-medium);
  font-weight: var(--font-weight-medium);
}

.woocommerce-form__label-for-checkbox {
  display: flex;
  align-items: center;
  gap: var(--space-small);
}

/* --------------------------------------------------------------------------
   COUPON + SHIPPING CALCULATOR — Woo's own cart furniture.

   Both are printed by WooCommerce with only its classes, so the Woo selector
   is the component. `.button` here is Woo's generic button, the same one the
   notices carry, and it borrows the outlined look for the same reason.
   -------------------------------------------------------------------------- */

.coupon {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-small);
  margin-block-start: var(--space-medium);
}

/* The checkout's Primeni is this button, not the black one.
   -------------------------------------------------------------------------
   Two coupon forms on one site should not be two different controls. The cart
   has always used this outlined button; the checkout was reaching for
   .sklad-button--black, which put a solid black slab in a panel whose only
   other black thing is the order button. .checkout-coupon__submit was a hook
   class with no rules at all until now — the appearance came entirely from the
   component classes beside it, which is why swapping them was enough.

   The vertical padding is --space-small-plus rather than --space-small so the
   button matches the input's 44px when it stands alone. In the cart the two sit
   in one grid row and the button was stretched to that height anyway; on the
   checkout they are stacked, and an inline-flex button would otherwise come out
   six pixels shorter than the field above it. */
.coupon .button,
.checkout-coupon__submit,
.woocommerce-shipping-calculator .button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 15px var(--space-medium);
  border: 1px solid var(--ui-200);
  border-radius: var(--space-xsmall);
  background-color: var(--ui-00);
  color: var(--ui-950);
  font-size: var(--text-medium-size);
  white-space: nowrap;
  cursor: pointer;
  transition: box-shadow var(--duration-fast) var(--ease-out);
}
.coupon .button:hover,
.checkout-coupon__submit:hover,
.woocommerce-shipping-calculator .button:hover { box-shadow: var(--shadow-level-2); }

/* On the checkout the apply button follows the field rather than matching it.
   The comment above explains the 15px: in the cart the two sit in one grid row
   and the button is stretched to the field's height anyway, so the padding is
   what keeps them level when they are not. On the checkout they are stacked and
   nothing is being matched — a second control the full height of the input
   above it read as a second field, not as its action. */
.checkout-coupon__submit { padding-block: var(--space-small); }

.cart-total__update {
  width: 100%;
  margin-block-start: var(--space-small);
}

/* Nothing to update until a quantity moves — Woo's own default state. */
.cart-total__update[disabled] {
  color: var(--ui-500);
  border-color: var(--ui-100);
  cursor: not-allowed;
}
.cart-total__update[disabled]:hover { box-shadow: none; }

.woocommerce-shipping-calculator { margin-block-start: var(--space-medium); }

.shipping-calculator-form { margin-block-start: var(--space-medium); }

/* Woo emits every field as a <p class="form-row">, which already has its
   spacing defined for the checkout — this only has to stop the <p> margin
   from doubling it up. */
.shipping-calculator-form .form-row { margin-block: 0 var(--space-small); }
.shipping-calculator-form .form-row:last-child { margin-block-end: 0; }

/* --------------------------------------------------------------------------
   ORDER RECEIVED — the thank-you page.

   Woo's own template, so its classes are the component here. The overview is a
   <ul> of label/value pairs, the details are a .shop_table (the same one the
   checkout review uses, so totals read identically on both sides of the
   purchase), and the addresses come wrapped in .col2-set.
   -------------------------------------------------------------------------- */

.order-received { --container-padding-top: var(--space-xlarge-plus); }

.woocommerce-order {
  grid-column: 1 / -1;
  padding-block: var(--container-padding-top) var(--space-xxxlarge);
}

@media (min-width: 1024px) {
  .woocommerce-order { grid-column: 5 / 21; }
}

/* The confirmation line carries the page, so it is set as a heading would be
   rather than as a notice — a tinted panel would make the good news look like
   a warning. */
.woocommerce-notice--success {
  margin: 0 0 var(--space-xlarge);
  /* Matches .title-h3 rather than carrying it: Woo prints this <p> itself, so
     there is no template hook to put our class on it. Kept in step with the
     h3 rules in base.css — condensed cut, display weight, -0.01em. */
  font-family: var(--font-serif-condensed);
  font-weight: var(--font-display-weight);
  font-size: var(--title-h3-size);
  line-height: var(--title-h3-lh);
  letter-spacing: var(--tracking-display);
  font-variant-ligatures: no-contextual;
}

/* Label over value, wrapping to as many columns as fit — five items read badly
   as one long row on a phone and badly as a stack on a desktop. */
.woocommerce-order-overview {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-medium) var(--space-large);
  margin: 0 0 var(--space-xlarge);
  padding: var(--space-medium-plus);
  background: var(--beige-100);
  border-radius: var(--space-xxsmall);
  list-style: none;
}

.woocommerce-order-overview li {
  display: flex;
  flex-direction: column;
  gap: var(--space-xxsmall);
  font-size: var(--text-xsmall-size);
  color: var(--ui-600);
}

.woocommerce-order-overview li strong {
  font-size: var(--text-medium-size);
  font-weight: var(--font-weight-medium);
  color: var(--ui-950);
}

.order-received__notice { margin-block-end: var(--space-xlarge); }

.woocommerce-order-details,
.woocommerce-customer-details { margin-block-start: var(--space-xlarge); }

.woocommerce-order-details__title,
.woocommerce-column__title { margin: 0 0 var(--space-medium); }

.woocommerce-order-details .product-name a {
  color: var(--ui-950);
  text-decoration: none;
}
.woocommerce-order-details .product-name a:hover { text-decoration: underline; }

/* --- Addresses -------------------------------------------------------------
   .col2-set is Woo's own two-column wrapper — used here and on the checkout,
   so it is defined once rather than per page.
   --------------------------------------------------------------------------- */

.col2-set {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-large);
}

/* Woo uses .woocommerce-columns--addresses on the order pages and
   .woocommerce-Addresses on the account's address book. Both are the same
   two-up; only the first was listed, which is why billing and shipping
   stacked on the address book. */
@media (min-width: 768px) {
  .woocommerce-columns--addresses,
  .woocommerce-Addresses { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.woocommerce-customer-details address {
  font-style: normal;
  font-size: var(--text-medium-size);
  line-height: var(--text-paragraph-lh);
  color: var(--ui-800);
}

.woocommerce-customer-details address p { margin-block: var(--space-small) 0; }

.order-received__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-medium) var(--space-large);
  margin-block-start: var(--space-xxlarge);
}

.order-received__actions .sklad-button { width: fit-content; }

/* --- Field groups ----------------------------------------------------------- */

.woocommerce-billing-fields > h3,
.woocommerce-shipping-fields > h3,
.woocommerce-additional-fields > h3 {
  margin: 0 0 var(--space-medium);
}

.woocommerce-shipping-fields + .woocommerce-additional-fields,
.woocommerce-billing-fields + .woocommerce-additional-fields {
  margin-block-start: var(--space-xlarge);
  padding-block-start: var(--space-large);
  border-block-start: 1px solid var(--ui-100);
}

/* Dostava is the main path now, so it stops being drawn as an aside.

   This block used to be "Dostava na drugu adresu": a boxed panel with a
   hairline suppressed above it and no space under its heading, because the
   panel was usually shut and the box was what said "an option you may open".
   It is the delivery address itself now — always open, always answered — and a
   box around it made the one required part of the form look optional. */
.woocommerce-shipping-fields > h3 { margin-block-end: 0; }

/* Woo emits .form-row as a <p>; the two-up variants are its own naming for a
   half-width field, which is why they are not --half and --full. */
/* The wrapper that actually holds the rows. Woo nests the shipping fields one
   level deeper than the billing ones — .shipping_address > __field-wrapper >
   .form-row — so putting the grid on .shipping_address made that wrapper a
   single grid item in the first column, and every shipping field came out
   315px wide inside a 645px block. Measured: billing's street line was 645,
   shipping's was 315. */
.woocommerce-billing-fields__field-wrapper,
.woocommerce-shipping-fields__field-wrapper,
.woocommerce-additional-fields__field-wrapper,
.woocommerce-address-fields__field-wrapper {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-medium);
}

.form-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-xsmall);
  margin: 0;
  grid-column: 1 / -1;
}

@media (min-width: 768px) {
  .form-row-first { grid-column: 1 / 2; }
  .form-row-last  { grid-column: 2 / -1; }
}

/* Tighter than the page around it. The reference form runs on a 12px gap
   between fields where this site's forms use 16 — with the label inside the
   control there is no longer a line of text between one field and the next to
   carry the separation, so the gap does it and can afford to be smaller. */
.woocommerce-billing-fields__field-wrapper,
.woocommerce-shipping-fields__field-wrapper,
.woocommerce-additional-fields__field-wrapper,
.woocommerce-address-fields__field-wrapper { gap: var(--space-small-plus); }

/* The label itself is defined once, up with .field__label. This only carries
   what is specific to a Woo row. */
.form-row > label { font-family: var(--font-sans-serif); }

.form-row .optional { color: var(--ui-500); }
.form-row abbr.required { color: var(--error); text-decoration: none; }
.woocommerce-input-wrapper { display: block; }

/* Woo adds these on validation; the field has to answer without a class of
   ours being added alongside.

   Validated first, invalid second, and the order is the whole rule. The two
   are meant to be mutually exclusive — Woo's own validate_field clears both
   before setting one — but a row that ends up carrying both took the grey,
   because the two selectors weigh the same and the last one written won. That
   is not a state to rely on never happening: it is exactly the state a field
   marked from a server-side error lands in, valid as far as Woo could tell and
   invalid as far as the server was concerned. Red wins here on order, so the
   pair cannot go quiet again if it recurs. */
.woocommerce-validated .input-text { border-color: var(--ui-200); }

.woocommerce-invalid .input-text,
.woocommerce-invalid select,
.woocommerce-invalid input[type="checkbox"] { border-color: var(--error); }

/* The consent row says it by going red, and says it once.
   -------------------------------------------------------------------------
   A text field can be marked on its border because the border is the field.
   This checkbox is a 13px native box with no border of its own, so a border
   colour lands on nothing — the row needed something that reads. WooCommerce
   puts its `checkbox` class on the LABEL, so colouring that turns the whole
   phrase red, link and asterisk with it, which is the mark.

   This replaced a sentence printed under the control. Both together said the
   same thing twice in the same colour, a hand's width apart. */
.woocommerce-invalid .checkbox { color: var(--error); }

/* --- Order review ----------------------------------------------------------- */

.shop_table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
}

.shop_table th,
.shop_table td {
  padding-block: var(--space-small);
  text-align: left;
  font-weight: var(--font-weight-regular);
  vertical-align: top;
}

.shop_table td.product-total,
.shop_table th.product-total,
.shop_table tfoot td { text-align: right; white-space: nowrap; }

/* The amount sits on the stepper's line, which is where the mini cart puts it.
   Every cell in this table is vertical-align: top, and that is right for the
   name — it should start at the top of its row. But it left the price hanging
   level with the product name while the stepper sat 30px lower, so a row that
   is one thing read as two. */
.shop_table .cart_item td.product-total { vertical-align: bottom; }

/* The widest thing in the price column is not a price. It is the tax note
   under the total — "(uključuje 14.450 RSD PDV)" — and tfoot cells are nowrap
   so amounts do not break mid-number. That one line held the column open at
   151px on a 375px screen, which left the product name 88px and five lines to
   wrap into. The amounts keep their nowrap; the note is a sentence and may
   wrap like one. Measured at 375: the name goes from 88px to 138. */
.woocommerce-checkout-review-order-table tfoot .includes_tax {
  display: block;
  white-space: normal;
}

.shop_table thead th {
  padding-block-end: var(--space-small);
  border-block-end: 1px solid var(--beige-200);
  color: var(--ui-500);
}

/* Rows of the cart and rows of an order are the same row: .cart_item on the
   basket, .order_item on the order's own table (account view-order, and the
   classic thank-you). The line belongs to both — naming only one of them
   left the order table without a single rule between its products. */
.shop_table .cart_item td,
.shop_table .order_item td { border-block-end: 1px solid var(--beige-200); }
.shop_table .product-quantity { color: var(--ui-500); font-weight: var(--font-weight-regular); }

.shop_table tfoot th { color: var(--ui-950); }

/* --- Shipping methods ------------------------------------------------------
   Woo renders the rate choice as a <ul> inside the totals table. It lands in a
   tfoot cell, which this table right-aligns for amounts — a radio list read
   right-aligned is unreadable, so this cell opts back out.
   --------------------------------------------------------------------------- */

/* The rate list moved into the form — see .checkout-shipping — so this cell
   carries an amount like the rows above and below it, and takes their alignment
   with them. It used to opt out of both: a radio list read right-aligned is
   unreadable, so the cell went left and wrapping went back on, which is why
   "Dostava" was the one row in the totals whose value did not line up with the
   others once the radios were no longer in it. */

.woocommerce-shipping-methods {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Flex, not a two-track grid.
   -------------------------------------------------------------------------
   The grid assumed the rate's input was a grid item holding track one. It is,
   while there are several rates — but WooCommerce emits type="radio" only when
   there are two or more, and a single rate gets type="hidden", which the UA
   sheet sets to display:none. A display:none element is not a grid item, so the
   label fell into the `auto` track, shrink-wrapped to its own text, and the
   `justify-content: space-between` below had no free space left to push the
   price anywhere. "Pošiljka  Standardna dostava" then read as one run of text
   at the left of a cell whose neighbours — Međuzbir, Ukupno — sit flush right.

   A display:none input contributes nothing to a flex container either, so the
   label fills the row in both cases and the price lands at the same edge
   whether there is one rate or three. Adding `grid-column: 2` instead would
   have left the empty track's gap behind as a stray indent. */
.woocommerce-shipping-methods li {
  display: flex;
  align-items: start;
  gap: var(--space-small);
}

.woocommerce-shipping-methods label { flex: 1 1 auto; }

.woocommerce-shipping-methods label {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-xxsmall) var(--space-small);
  min-width: 0;
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  cursor: pointer;
}

/* The rate sits hard right so the three prices form a column to compare. */
.woocommerce-shipping-methods label .woocommerce-Price-amount {
  flex: 0 0 auto;
  color: var(--ui-600);
}

/* accent-color is stated once for every native control; only the nudge is
   this group's own — the radio sits against a two-line label. */
.woocommerce-shipping-methods input { margin-block-start: 2px; }

.woocommerce-shipping-destination {
  margin-block: var(--space-small) 0;
  font-size: var(--text-xsmall-size);
  color: var(--ui-500);
}
.shop_table tfoot .order-total th,
.shop_table tfoot .order-total td {
  padding-block-start: var(--space-medium);
  border-block-start: 1px solid var(--beige-200);
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  /* The amount arrives inside WooCommerce's own <strong>, so the figure and the
     word beside it were two different weights on one line. The label matches
     the number rather than the number being softened to match it: this row is
     the answer the page exists to give.

     The <strong> has to be told as well. Its weight is `bolder`, which is
     relative — against a 500 parent it computed 700, and the moment the label
     went to 700 it climbed to 900 and the two were still apart. Measured both
     times. */
  font-weight: var(--font-weight-bold);
}

.shop_table tfoot .order-total td strong { font-weight: inherit; }

/* And under it, what the total already contains — the same quiet line the basket
   ends on. No rule above it: it belongs to the total, not to a new section, and
   .order-total has already drawn the one that separates the two. */
.shop_table tfoot .order-total-tax th,
.shop_table tfoot .order-total-tax td {
  padding-block-start: var(--space-xxsmall);
  border-block-start: 0;
  font-size: var(--text-xsmall-size);
  line-height: var(--text-xsmall-lh);
  font-weight: var(--font-weight-regular);
  color: var(--beige-600);
}

.woocommerce-shipping-destination { color: var(--ui-500); }

/* --- Payment ---------------------------------------------------------------- */

.woocommerce-checkout-payment { margin-block-start: var(--space-large);
  margin-block-start: var(--space-xlarge);
  padding-block-start: var(--space-large);
  border-block-start: 1px solid var(--ui-100);
}

.wc_payment_methods {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  padding: 0;
  margin: 0 0 var(--space-large);
  list-style: none;
}

/* Inside its own section the list is the section's last child: the button no
   longer follows it, so the room it kept for the button goes. */
.checkout-section > .woocommerce-checkout-payment .wc_payment_methods { margin-block-end: 0; }

/* The account under "Bankarski depozit": three rows a customer copies into a
   payment slip, so the values read as text rather than as the muted note
   around them. The row wrapper is only there for the markup to be a list of
   pairs; display: contents lets dt and dd sit on the grid themselves. */
.payment_box .predracun-account {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--space-xxsmall) var(--space-medium);
  margin: var(--space-small) 0;
}

.payment_box .predracun-account__row { display: contents; }

.payment_box .predracun-account dt { margin: 0; color: var(--text-secondary); }

.payment_box .predracun-account dd {
  margin: 0;
  color: var(--text-primary);
  font-weight: var(--font-weight-medium);
  overflow-wrap: anywhere;
}

.payment_box .predracun-account__note { margin: 0; }

.wc_payment_method {
  padding: var(--space-medium);
  border: 1px solid var(--ui-100);
  border-radius: var(--space-xxsmall);
  background: var(--ui-00);
  transition: border-color var(--duration-fast) var(--ease-out);
}

.wc_payment_method:has(.input-radio:checked) { border-color: var(--ui-950); }

.wc_payment_method > label {
  display: flex;
  align-items: center;
  gap: var(--space-small);
  cursor: pointer;
}

/* Woo shows and hides these itself; ours only has to look right when it does. */
.payment_box {
  margin-block-start: var(--space-small);
  padding-inline-start: calc(var(--space-medium) + var(--space-small));
  color: var(--ui-500);
}

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

/* --- The two ticks above the button ------------------------------------------
   Keep my details, send me the news: a sentence each and a quieter line under
   it, indented to the sentence so the note reads as part of its tick, not as
   a new paragraph. The block ends with the form's rule above the terms. */
.checkout-optins {
  display: flex;
  flex-direction: column;
  gap: var(--space-small-plus);
  margin: 0;
}

/* The rule between the ticks and the terms is the one the button block
   already draws above itself (.checkout-place-order); a second one here
   made a double line with 65px of air (measured on staging 05.09.). */

.checkout-optin {
  display: flex;
  flex-direction: column;
  gap: var(--space-xxsmall);
  margin: 0;
}

/* 14px is the box (.checkbox), the small gap is the label's own. */
.checkout-optin__note {
  padding-inline-start: calc(14px + var(--space-small));
  color: var(--text-secondary);
}

/* The account row is a statement with a way out, not a tick: text on the
   left, "Ne sada" on the right, no box to indent under. Declined, the title
   goes quiet and the note says what happens instead; the button offers the
   account back. */
.checkout-optin--account {
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-medium);
}

.checkout-optin--account .checkout-optin__text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--space-xxsmall);
  min-width: 0;
}

.checkout-optin--account .checkout-optin__note { padding-inline-start: 0; }

.checkout-optin--account .checkout-optin__title { color: var(--text-primary); }

.checkout-optin--account.is-off .checkout-optin__title { color: var(--text-secondary); }

.checkout-optin__toggle {
  flex: none;
  margin-block-start: 1px;
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
}

/* The coupon's remove link, drawn by a filter in inc/woocommerce.php as the
   underlined text link the rest of the site uses: underline on, retracting
   on hover. It follows the amount on the same line. */
.cart-discount .link { margin-inline-start: var(--space-xsmall); font-size: inherit; }

/* The privacy sentence and the terms checkbox are two things WooCommerce
   prints into one wrapper; they stack with the form's gap rather than
   touching — measured on production 05.09.: zero pixels between them. */
.woocommerce-terms-and-conditions-wrapper {
  display: flex;
  flex-direction: column;
  gap: var(--space-small-plus);
  margin-block-end: var(--space-medium);
}

.woocommerce-terms-and-conditions-wrapper > .form-row,
.woocommerce-privacy-policy-text,
.woocommerce-privacy-policy-text > p { margin: 0; }

/* With the privacy sentence emptied (migration 1.6.39) WooCommerce still
   prints its wrapper; empty, it would still take a slot of the gap above. */
.woocommerce-privacy-policy-text:empty { display: none; }

/* The terms line's star sits inside the sentence, in the same grey as the
   "*" every placeholder carries — not two flex gaps away in red. WooCommerce
   prints "&nbsp;<span class=required>*</span>" as its own flex item; that one
   is hidden and the sentence draws its own. */
.woocommerce-terms-and-conditions-checkbox-text::after { content: " *"; color: var(--text-secondary); }

.woocommerce-terms-and-conditions-wrapper .required { display: none; }

.place-order { display: flex; flex-direction: column; gap: var(--space-medium); }

/* Last child of #customer_details: the grid's gap separates it from the
   section above, the rule marks where the questions end and the act begins. */
.checkout-place-order {
  padding-block-start: var(--space-large);
  border-block-start: 1px solid var(--ui-100);
}

#place_order { width: 100%; justify-content: center; min-height: 3rem; }

/* --------------------------------------------------------------------------
   CHECKOUT HEADER / FOOTER

   The checkout and order-confirmation pages replace the full site chrome —
   nav, search, account and wishlist icons, the announcement bar, the wide
   three-section footer — with a header that is only the logo and the way
   back to the cart, and a footer that is only the four pages and the phone
   number someone might still need mid-order. Niko's decision, 05.09.2026:
   nothing on either page should read as an invitation to leave it.

   The header keeps `height: var(--header-height)`, the same token the full
   header sets. That is the whole trick for not breaking anything else: every
   rule that clears the sticky header on this page — .page-grid's own
   padding-top, the mobile order-summary bar's `top`, the desktop sticky
   #order_review's `top` — reads that one token, so a header of the SAME
   height needs no second number kept in step with it. If a future header is
   ever a different height, override --header-height on the checkout body
   (e.g. `body.woocommerce-checkout { --header-height: …; }`) rather than
   touching each of those rules individually.

   The second half of that contract is the pull-up. Those clearing rules
   assume the content STARTS under the header — which is what every full
   template arranges with `.header--overlay + *`. This header is in normal
   flow too, so without the same pull-up the page-grid paid for the header
   twice: measured on production on 05.09.2026, 120px between the header
   and „Plaćanje" on desktop (56 of header + 64 of design padding) where
   the full-header checkout had 64, and a 56px band of white above the
   sticky summary bar on a phone that was meant to sit flush under it. The
   rule lives in the theme's wordpress.css, next to the overlay one, because
   the block-layout reset there outranks anything written in this file.
   -------------------------------------------------------------------------- */

.header-checkout {
  /* Same hook the full header reads (see .header above), so .header__logo
     and every .link inside resolve to the right ink without a second rule. */
  --link-color: var(--ui-950);
  --link-hover-color: var(--ui-600);
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  height: var(--header-height);
  background: var(--ui-00);
  border-bottom: 1px solid var(--ui-100);
}

.header-checkout__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-medium);
  height: 100%;
  max-width: var(--max-width-container);
  margin-inline: auto;
  padding-inline: var(--gutters-side);
}

/* Keeps "Nazad na korpu" on one line at the narrowest widths this system
   supports, so the two ends of the row never wrap onto each other. */
.header-checkout__back .link__text { white-space: nowrap; }

/* Niko's call, 05.09.2026: on desktop the chrome should read exactly as
   wide as the content it wraps — logo over "Plaćanje", back link over the
   order summary — rather than the full container width the flex row above
   gives it below 1024. Reusing .page-grid's own tracks and gutters (the
   tokens, not a computed width) is what keeps the two lined up at every
   width instead of only the one this was checked at: --grid-columns,
   --gutters and --gutters-side all step at the same 1024/1280 breakpoints
   .page-grid reads, so this grid and that one are never out of step. */
@media (min-width: 1024px) {
  .header-checkout__inner {
    display: grid;
    grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
    gap: 0 var(--gutters);
    justify-content: normal;
  }

  /* Same spans as #customer_details and #order_review below (see the
     checkout grid rules) — track 6 is the form's left edge, track 20 is the
     summary's right edge, and 13 is the line between them. */
  .header-checkout__inner .header__logo { grid-column: 6 / 13; justify-self: start; }
  .header-checkout__back { grid-column: 13 / 20; justify-self: end; }
}

.footer-checkout {
  background-color: var(--ui-800);
  color: var(--ui-00);
}

.footer-checkout__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-medium);
  max-width: var(--max-width-container);
  margin-inline: auto;
  padding: var(--space-large) var(--gutters-side);
  text-align: center;
}

.footer-checkout__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-small) var(--space-medium);
  /* One size under the body's: the four page names and the © line are the
     quietest thing on the page, and at 13px the row has room to spare on
     the narrowest desktop (Niko, 05.09.2026: „smaller links so it fits"). */
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
}

/* Same colour the full footer gives its copyright line on the same --ui-800
   ground (see .footer-bottom .copyright) — dimmer than the links on purpose,
   since it is not something to click. */
.footer-checkout__copyright { color: var(--ui-500); }

@media (min-width: 1024px) {
  /* Same grid as .header-checkout__inner immediately above, for the same
     reason: the footer's own edges should sit on tracks 6 and 20, not on
     the full container width. */
  .footer-checkout__inner {
    display: grid;
    grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
    /* Top-aligned, not centred: the four links wrap to two lines in their
       seven tracks, and the © line reads with the first of them, not with
       the gap between (measured on staging 05.09.2026: centred put it
       under the second line). */
    align-items: start;
    gap: 0 var(--gutters);
    text-align: start;
  }

  /* Two groups on the header's two spans: the © line on the left half, where
     the logo is, and the four page links on the right half, ending where
     „Nazad na korpu" ends. Niko, 05.09.2026 — the phone and email that used
     to sit between them are gone, the support band above already carries
     both. The links keep their own wrap and gap; only the anchor changes,
     so a second line, should the four ever need one, still ends on the
     summary's right edge. */
  /* Both on the content's full span, 6 / 20, in one named row: the © sits
     at its start, the links at its end. Splitting the span in half gave the
     four links seven tracks (444px at 1440) and „Kontakt" fell to a second
     line — Niko, 05.09.2026, on the live footer. Sharing the span costs
     nothing: the © is ~100px wide at the start and the links right-align
     from the end, so they meet only if the four links ever exceed the span
     minus the © — at 1024, the narrowest desktop, that is 562px against
     ~453px of links. grid-row: 1 on both because auto-placement only walks
     forward and the © comes after the links in the markup. */
  .footer-checkout__copyright { grid-row: 1; grid-column: 6 / 20; justify-self: start; }
  .footer-checkout__links { grid-row: 1; grid-column: 6 / 20; justify-self: end; justify-content: flex-end; }
  .footer-checkout__link { white-space: nowrap; }
}

/* Between 1024 and 1279 the span is 562–700px and the four links take
   ~450 of it at the medium gap; measured at 1024 on staging (05.09.2026)
   the © ended 7px before the first link. A smaller gap between the links
   buys the two groups a readable distance without changing the type. */
@media (min-width: 1024px) and (max-width: 1279px) {
  .footer-checkout__links { column-gap: var(--space-small); }
}

/* --------------------------------------------------------------------------
   ICONS

   One sprite of Lucide symbols (ISC), inlined per page, referenced with
   <svg class="icon"><use href="#i-name"></use></svg>.

   Every presentational decision lives here rather than on the symbols, so the
   whole set retightens from one place: Lucide ships at stroke-width 2, which
   reads heavy beside this system's type. 1.5 at 20px is the weight that sits
   with DM Sans at 14–15px without shouting.

   Size follows the text it sits with by default (1em) and is overridden with
   --icon-size where an icon is a target in its own right rather than a mark
   inside a line of text.
   -------------------------------------------------------------------------- */

.icon {
  --icon-size: 1em;
  display: inline-block;
  flex: 0 0 auto;
  width: var(--icon-size);
  height: var(--icon-size);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke, 1.5);
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: middle;
  pointer-events: none;
}

/* Brand marks are drawn as filled glyphs, so they opt out of the stroke the
   Lucide-derived UI set is built on. Declared after .icon deliberately: same
   specificity, so source order is what decides it. */
.icon--brand { fill: currentColor; stroke: none; }

/* A hairline set for large icons: stroke weight is absolute, so an icon scaled
   up without thinning its stroke reads much heavier than the same icon small. */
.icon--lg { --icon-size: 24px; --icon-stroke: 1.25; }
.icon--xl { --icon-size: 32px; --icon-stroke: 1; }
.icon--sm { --icon-size: 16px; }

/* Filled state, for the one icon that has one. */
.icon--filled { fill: currentColor; }

/* Icons sized to their control rather than to text. */
.header__button .icon,
.button-wishlist .icon { --icon-size: 20px; }
.sklad-button__icon .icon { --icon-size: 18px; }
.sklad-quantity__button .icon { --icon-size: 16px; }

/* --------------------------------------------------------------------------
   MINI CART — the drawer behind the header's bag button.

   Information modelled on skladliving.com, which shows more than a list of
   what you picked: a subtotal with the tax it includes, both onward routes
   (basket and checkout), and — when the cart is empty — a way back into the
   catalogue rather than a dead end.

   WooCommerce's mini-cart class names are used as the contract, the same rule
   as the checkout and the product form: .widget_shopping_cart_content,
   .woocommerce-mini-cart, .woocommerce-mini-cart-item, __total, __buttons,
   __empty-message. Woo re-renders this whole fragment over AJAX on every cart
   change, so nothing inside may depend on JavaScript we added.
   -------------------------------------------------------------------------- */

.mini-cart__body { display: flex; flex-direction: column; height: 100%; }

/* The list scrolls; the totals and buttons stay put at the foot of the sheet,
   so the commit is reachable however many items are in the cart. */
.mini-cart__scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin-inline: calc(var(--space-medium) * -1);
  padding-inline: var(--space-medium);
}

.woocommerce-mini-cart {
  display: flex;
  flex-direction: column;
  padding: 0;
  margin: 0;
  list-style: none;
}

.woocommerce-mini-cart-item {
  display: grid;
  grid-template-columns: 72px 1fr;
  gap: var(--space-medium);
  padding-block: var(--space-medium);
  border-block-end: 1px solid var(--ui-100);
}

/* The thumbnail links where the title already links. It is aria-hidden and
   out of the tab order so it is not a second stop announcing the same
   destination — the title carries the name, the image carries the reach. */
.mini-cart__media {
  display: block;
  aspect-ratio: 4 / 5;
  background: var(--ui-50);
  transition: opacity var(--duration-fast) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .mini-cart__media:hover { opacity: 0.75; }
}

.mini-cart__media img { width: 100%; height: 100%; object-fit: contain; padding: var(--space-xxsmall); }

.mini-cart__info { display: flex; flex-direction: column; gap: var(--space-xxsmall); min-width: 0; }

/* The row's data follows the reference's arrangement: the name and the way to
   remove it share the top line, the description gets its own full-width line
   under it, and the quantity control and the price sit on one line at the
   bottom with the price pushed to the far edge. What changed from ours is the
   grouping — a name with a bin at the end of it, rather than a bin trailing
   the price at the end of a control row. */
.mini-cart__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-small);
}

.mini-cart__variant { color: var(--ui-600); }

/* The stepper, in its compact variant — the same control the cart page's line
   items use, so a quantity is changed the same way in both places. */
.mini-cart__quantity.sklad-quantity {
  flex: 0 0 auto;
  height: 32px;
  gap: var(--space-xxsmall);
  padding: 0 var(--space-xsmall);
}

.mini-cart__quantity.sklad-quantity .sklad-quantity__button { width: var(--space-medium-plus); }
.mini-cart__quantity.sklad-quantity .sklad-quantity__value { width: var(--space-medium-plus); }

/* Same product-name-is-not-a-label call as .line-item__infos__title above —
   the drawer carried the identical forked .text-ui-large recipe. */
.mini-cart__title {
  margin: 0;
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
  /* Same call as .line-item__infos__title: the card's medium, not bold. */
  font-weight: var(--font-weight-medium);
}

.mini-cart__variant { color: var(--ui-500); }

.mini-cart__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-small);
  margin-block-start: var(--space-xsmall);
}

.mini-cart__remove {
  --link-color: var(--ui-500);
  display: inline-flex;
  align-items: center;
  padding: var(--space-xxsmall);
  border: 0;
  background: none;
  color: var(--ui-500);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out);
}

.mini-cart__remove:hover { color: var(--ui-950); }

/* --- Totals ----------------------------------------------------------------- */

.mini-cart__footer {
  flex: 0 0 auto;
  padding-block-start: var(--space-medium);
  /* The drawer runs to the bottom of the viewport, so without this the last
     button sits flush against the screen edge. The safe-area inset is added
     rather than substituted: on a phone with a home indicator the bar sits
     over that last strip. */
  /* --space-large, to match the panel header's own bottom padding: the drawer
     closed tighter at the foot than it opened at the head. */
  padding-block-end: calc(var(--space-large) + env(safe-area-inset-bottom, 0px));
  border-block-start: 1px solid var(--ui-100);
}

.woocommerce-mini-cart__total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-medium);
  margin: 0;
  font-family: var(--font-sans-serif);
  font-size: var(--text-large-size);
  line-height: var(--text-large-lh);
  font-weight: var(--font-weight-medium);
}

/* The tax line is skladliving's own addition to Woo's mini cart, and it earns
   its place: it is the only thing here that tells you the subtotal is already
   tax-inclusive rather than about to grow at checkout. */
.sklad-mini-cart-tax {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-medium);
  margin: var(--space-xxsmall) 0 0;
  color: var(--ui-500);
  font-size: var(--text-xsmall-size);
  line-height: var(--text-xsmall-lh);
}

.mini-cart__note {
  margin: var(--space-small) 0 0;
  color: var(--ui-500);
  font-size: var(--text-xsmall-size);
  line-height: var(--text-xsmall-lh);
}

/* The delivery price under the subtotal: the same two-column line as the tax
   row, in the text colour rather than the grey, because it is a figure the
   customer adds to the one above it — "Besplatna" or "od 2.999 RSD". */
.mini-cart__delivery {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-medium);
  margin: var(--space-xsmall) 0 0;
  color: var(--text-primary);
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
}

.mini-cart__delivery__value { font-weight: var(--font-weight-medium); }

/* --- Free-delivery bar -------------------------------------------------------
   One sentence and a thin bar that fills towards the door threshold: what is
   still missing, or — once it is full — what the basket has earned. In the
   drawer it leads the footer, on the basket page it follows the rows; the
   same markup in both, built by sklad_delivery_progress_html(). No icon, no
   colour change: the full bar and the sentence are the celebration. */
.delivery-progress {
  display: flex;
  flex-direction: column;
  gap: var(--space-xsmall);
}

.delivery-progress__title {
  margin: 0;
  color: var(--text-primary);
  font-weight: var(--font-weight-medium);
}

.delivery-progress__bar {
  height: 4px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--ui-100);
}

.delivery-progress__fill {
  display: block;
  height: 100%;
  background: var(--ui-950);
  transition: width var(--duration-medium) var(--ease-out);
}

.delivery-progress__text {
  margin: 0;
  color: var(--text-secondary);
}

/* Leads the drawer's footer: a rule under it separates the promise from the
   sums it refers to. */
.mini-cart__footer .delivery-progress {
  margin-block-end: var(--space-medium);
  padding-block-end: var(--space-medium);
  border-block-start: 0;
  border-block-end: 1px solid var(--ui-100);
}

.cart-total .delivery-progress { margin-block-start: var(--space-small); }

.woocommerce-mini-cart__buttons {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  margin: var(--space-medium) 0 0;
}

.woocommerce-mini-cart__buttons .sklad-button { width: 100%; justify-content: center; }

/* --- Empty state ------------------------------------------------------------ */

.mini-cart__empty {
  display: flex;
  flex-direction: column;
  /* Alignment comes from the shared empty-state rule further down. */
  gap: var(--space-medium);
  padding-block: var(--space-xlarge);
}

.woocommerce-mini-cart__empty-message {
  margin: 0;
  /* A notice, not a page title. It keeps .title-h5's face and drops a step in
     size: the drawer is a third the width of the page that class was sized for,
     and at 28px this line was the same weight as the site's section headings. */
  font-size: var(--title-h6-size);
  line-height: var(--title-h6-lh);
}

/* The action is a button, not a bar. Stretch is what a flex column does to a
   child by default, and with align-items centred it no longer applies — stated
   anyway, because this is the property that was wrong. */
.sklad-mini-cart-empty-action { width: max-content; }

/* Which half of the drawer is showing is read off the drawer itself.
   -------------------------------------------------------------------------
   This has now been wrong twice, in the same direction both times, and the
   reason is that it kept depending on a signal that some render path did not
   carry. First it was a class the script added after a refresh, which the
   non-script path never got. Then it was data-cart-empty, which the fragment
   carried and the first paint did not — fixed — and which any HTML older than
   that fix still does not, so a cached page served with current CSS showed the
   empty half hidden and nothing in its place: white under "Korpa (0)".

   Both versions failed the same way because both hid the empty state by
   default and revealed it on a signal. So stop asking for a signal. The filled
   half holds an element only when there is something in the basket — see
   mini-cart.php, where .mini-cart__scroll is inside an if ( $has_items ) — so
   the markup already answers the question, in every render path, with no
   attribute and no class to keep in step.

   data-cart-empty stays on the fragment: it is still the server's own statement
   and worth having for anything that wants to read it. Nothing here needs it. */
.mini-cart__empty { display: flex; }
.mini-cart__filled { display: block; }

.sklad-panel__body:has(.mini-cart__filled > *) .mini-cart__empty { display: none; }
.sklad-panel__body:not(:has(.mini-cart__filled > *)) .mini-cart__filled { display: none; }
/* The list scrolls; everything else stays put.

   .mini-cart__scroll has always declared `overflow-y: auto`, and it never once
   engaged: a flex item's min-height defaults to `auto`, which refuses to shrink
   below its content, so with a full basket the list simply grew and pushed the
   footer past the bottom of the screen — with ten lines in it the buttons sat
   964px below the fold, reachable only by scrolling the whole panel. Every step
   of the chain from the panel body down has to be allowed to shrink for the one
   element that should scroll to be the one that does.

   Scoped to .mini-cart: the other drawers scroll their inner wrapper, and that
   is right for them. */
.mini-cart .sklad-panel__inner { overflow: hidden; }

.mini-cart .sklad-panel__body {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* WooCommerce's own wrapper, which it re-renders and we cannot remove. */
.mini-cart .widget_shopping_cart_content {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.mini-cart__scroll { min-height: 0; }

/* The empty state has no scroller of its own, and the panel no longer scrolls
   on its behalf. It fits today at 320x568 with room to spare, but it carries a
   row of category tiles that can grow — so it is allowed to scroll rather than
   be silently cut off if it ever does. */
.mini-cart__empty {
  /* Both axes stated: a lone `overflow-y: auto` computes overflow-x to `auto`
     too, which turns this into a horizontal scroller the moment anything inside
     is wider than it — the category row, as it turned out. */
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  min-height: 0;
}

.mini-cart__filled { display: flex; flex-direction: column; height: 100%; }

/* --- Sign in, as a dialog --------------------------------------------------
   A <dialog>, so the page behind it is inert, focus is trapped, Escape closes
   it and the backdrop is the browser's. The panel borrows the newsletter
   modal's proportions because there is no reason for a site to have two
   different sizes of centred sheet.
   --------------------------------------------------------------------------- */

.checkout-login {
  display: flex;
  align-items: center;
  gap: var(--space-xsmall);
  margin: 0;
}

.checkout-login svg { flex: none; width: var(--space-medium); height: var(--space-medium); }

.login-modal {
  /* Centred by hand rather than by the UA's margin: auto, which this rule set
     was quietly relying on — measured, the panel sat in the top-left corner.

     No --design-ratio in the width either: the token is a desktop board scale
     and is not defined below 1024, so a calc() against it would have been an
     invalid max-width on exactly the screens where a full-width dialog is worst. */
  margin: auto;
  width: min(100%, 480px);
  padding: 0;
  border: 0;
  background: transparent;
}

.login-modal::backdrop { background-color: rgb(0 0 0 / 45%); }

.login-modal__panel {
  position: relative;
  padding: var(--space-large);
  background-color: var(--ui-00);
}

.login-modal__close {
  position: absolute;
  top: var(--space-small-plus);
  right: var(--space-small-plus);
  display: flex;
  padding: var(--space-xxsmall);
  border: 0;
  background: none;
  color: var(--ui-950);
  cursor: pointer;
}

/* Woo prints its own heading and prompt inside the form; the dialog does not
   need a second one above them. */
.login-modal .woocommerce-form-login { margin: 0; }

@media (max-width: 1023px) {
  .login-modal { width: 100%; }
}

/* --- Checkout: notices, review table, place order --------------------------- */

/* The band of validation errors at the top of the form is not drawn.
   It ran the width of the page above a form inset by five columns, and it
   repeated as a list what the fields say where the answer has to be typed. The
   fields carry it: an empty required one goes red on submit, the same red every
   other field on the site uses.

   Worth knowing before this line is copied anywhere: what it hides is not only
   the field errors. A gateway refusing the order, or no shipping method being
   available, prints into the same group and now prints into nothing. */
.woocommerce-NoticeGroup-checkout { display: none; }

/* …except once it has been moved under the order button, which is where the
   script puts it. Hidden where WooCommerce prepends it, shown where it lands. */
.checkout-errors .woocommerce-NoticeGroup-checkout { display: block; }

.checkout-errors:empty { display: none; }

.checkout-errors {
  margin-block-start: var(--space-medium);
  color: var(--error);
}


.checkout__notices {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  margin-block-end: var(--space-large);
}

/* The same column as everything else on the page, repeated here because it has
   to come after the rule above. The checkout's other desktop block sits earlier
   in the file and already said 4 / 22; two declarations of equal weight and the
   later one wins, so the login prompt and the error banner ran the full width
   of the window while the heading under them was inset by four columns. */
@media (min-width: 1024px) {
  .checkout__notices { grid-column: 6 / 20; }
}

/* Woo puts its validation notices inside form.checkout, which is a subgrid —
   and a grid item nobody places lands in a single track. Measured at 392: the
   whole list of "X je obavezno polje" rendered 19px wide, one word per line,
   down a column the width of a scrollbar. Everything else on this page states
   its column; these had no one to state it for them. */
.woocommerce-NoticeGroup,
form.checkout > .woocommerce-error,
form.checkout > .woocommerce-message,
form.checkout > .woocommerce-info { grid-column: 1 / -1; }

/* Woo's own notice class. Both of these toggle a hidden form in WordPress; here
   they are the two things a returning customer looks for first, so they sit
   above everything else on the page. */
.checkout-notice {
  display: flex;
  align-items: center;
  gap: var(--space-small);
  padding: var(--space-medium);
  border: 1px solid var(--ui-100);
  border-radius: var(--space-xxsmall);
  background: var(--ui-00);
  font-family: var(--font-sans-serif);
  font-size: var(--text-medium-size);
  line-height: var(--text-medium-lh);
}

/* .woocommerce-info is space-between so Woo's own notices can push their
   button (order: 2) to the far edge. This notice has no button — it is an
   icon and a sentence — so space-between drove the two apart across the whole
   width, measured 1,050px of nothing between them. It packs to the start. */
.woocommerce-info.checkout-notice { justify-content: flex-start; }

.checkout-notice .icon { color: var(--ui-500); }

/* The review table carries a thumbnail, a live quantity and a remove control,
   not just a name and a number — the last chance to fix the order is here, and
   sending someone back to the basket to change a quantity loses the sale. */
.shop_table .product-remove { width: 24px; padding-inline-end: 0; }
.shop_table .product-thumbnail { width: 56px; }

.shop_table .product-thumbnail img {
  display: block;
  width: 56px;
  /* aspect-ratio only sizes the box while height stays auto. An <img> carrying
     width/height attributes gets them as presentational hints, which set height
     and make the browser ignore the ratio entirely. Nothing here writes those
     attributes, but WooCommerce writes them on every thumbnail it renders, and
     this frame came out 54x900 there. */
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: contain;
  background: var(--ui-00);
  padding: var(--space-xxsmall);
}

.checkout-review__remove {
  display: inline-flex;
  padding: var(--space-xxsmall);
  border: 0;
  background: none;
  color: var(--ui-500);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out);
}

.checkout-review__remove:hover { color: var(--ui-950); }

/* Three lines that were sitting on top of one another beside the picture: the
   piece, its size, how many. They are a stack with air between them now, and
   the stack starts away from the thumbnail rather than against it. */
/* The name column is a stack that starts at the top; the price is one line and
   sits against the middle of it, so it reads level with the piece rather than
   with the first of three lines about it. */
.woocommerce-checkout-review-order-table tbody td { vertical-align: top; }

/* .cart_item in the selector, because the basket's own rule has it and puts the
   price on the baseline — and WooCommerce stamps .cart_item on these rows too,
   so the basket's rule was reaching a table it was not written for. */
.woocommerce-checkout-review-order-table .cart_item td.product-total { vertical-align: middle; }

.shop_table td.product-name { padding-inline-start: var(--space-small); }

.checkout-review__name {
  display: block;
  margin-block-end: var(--space-xxsmall);
}

.checkout-review__meta { line-height: 1.5; }

.checkout-review__quantity.sklad-quantity {
  height: 32px;
  gap: var(--space-xxsmall);
  padding: 0 var(--space-xsmall);
  background: var(--ui-00);
}

.checkout-review__quantity.sklad-quantity .sklad-quantity__button { width: var(--space-medium-plus); }
.checkout-review__quantity.sklad-quantity .sklad-quantity__value { width: var(--space-medium-plus); }

.shop_table tfoot .includes_tax {
  display: block;
  margin-block-start: var(--space-xxsmall);
  color: var(--ui-500);
  font-size: var(--text-xsmall-size);
  line-height: var(--text-xsmall-lh);
  font-weight: var(--font-weight-regular);
}

/* Payment moved into the left column under the details, so it needs the same
   separation from the block above it that the field groups have. */

.woocommerce-checkout-payment > h3 { margin: 0 0 var(--space-medium); }
.woocommerce-privacy-policy-text { color: var(--ui-500); }
.create-account, .woocommerce-account-fields { margin-block-start: var(--space-medium); }

/* --------------------------------------------------------------------------
   PRODUCT CARD — SMALL

   Below roughly 200px the card's hover furniture stops working: a full-width
   "Add to bag" bar covers a third of the image, and 32px arrows leave almost
   no image between them. The card is the same component — same anatomy, same
   media, same link — it just drops the affordances that need room.

   The heart stays, because saving is the one action that still makes sense at
   this size and it is not a hover affordance. The progress line stays, because
   it is 2px and reports something the arrows no longer can.
   -------------------------------------------------------------------------- */

.card-product--small .card-product__nav,
.card-product--small .card-product__add { display: none; }

.card-product--small .card-product__wishlist {
  top: var(--space-xsmall);
  right: var(--space-xsmall);
  width: var(--space-large);
  height: var(--space-large);
}

.card-product--small .card-product__wishlist .icon { --icon-size: 16px; }

.card-product--small .card-product__infos { margin-top: var(--space-small); gap: 2px; }
.card-product--small .card-product__title { font-size: var(--text-medium-size); line-height: var(--text-medium-lh); }
.card-product--small .card-product__desc  { font-size: var(--text-xsmall-size); line-height: var(--text-xsmall-lh); }
.card-product--small .sklad-price         { font-size: var(--text-medium-size); }

/* In the option picker the card is a choice, not a product to buy: adding a
   different variant to the bag from inside "choose an option" is the wrong
   action, so the small variant's hidden add button is exactly right here. */
.option-picker .card-product__add { display: none; }

/* --------------------------------------------------------------------------
   CONSENT — the cookie notice.

   A legal surface, so it is built to be refusable, not to be got past:
   "Only essential" carries the same weight, size and prominence as
   "Accept all", because a reject that is harder to find than an accept is not
   consent. Nothing non-essential runs until a choice is stored, and the
   preferences view lets that choice be partial.

   It is not a .sklad-panel: a panel traps focus and dims the page, which would
   make an unanswered notice a wall. This sits at the bottom, leaves the page
   usable, and simply does not go away until answered.
   -------------------------------------------------------------------------- */

.consent {
  position: fixed;
  /* Above the header and both sticky bars, so it is never buried; below the
     search sheet and the drawers, so opening one covers it. */
  z-index: calc(var(--z-overlay) - 1);
  inset-inline: 0;
  inset-block-end: 0;
  display: none;
  background-color: var(--ui-00);
  border-block-start: 1px solid var(--ui-100);
  box-shadow: 0 -8px 32px rgb(0 0 0 / 8%);
}

.consent.is-open { display: block; }

.consent__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-medium);
  padding: var(--space-large) var(--gutters-side);
  /* Not --max-width-container: at 1940 the sentence stretches to a single
     unreadable line and the two answers end up a screen apart from the text
     they answer. This is a notice, not a layout band. */
  max-width: calc(820 * var(--design-ratio));
  margin-inline: auto;
}

.consent__title { color: var(--ui-950); }
.consent__text { color: var(--ui-600); max-width: 68ch; }

.consent__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-small-plus);
}

/* Preferences is not one of the two answers — it opens the switches — so it is
   a link rather than a third control competing with them. It keeps its own
   width while the two answers share the rest; see the note below on why those
   two must stay matched. */
.consent__actions .link { flex: 0 0 auto; }

/* One filled button, "Accept all" — a deliberate call, made after the two
   answers had briefly been given the same variant. Both answers are the same
   size, height, padding and type; the fill is the only thing that separates
   them, and "Only essential" is a full button rather than a link, so refusing
   is one press either way. Worth knowing if this is ever revisited: EDPB and
   CNIL guidance asks for reject to be as prominent as accept, and a filled
   accept against an outlined reject is the shape regulators look at. */
.consent__actions .sklad-button { flex: 1 1 auto; }

@media (min-width: 1024px) {
  .consent__inner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-large);
    padding-block: var(--space-medium-plus);
  }

  .consent__copy { flex: 1 1 auto; }
  .consent__actions { flex: 0 0 auto; flex-wrap: nowrap; }
  /* width:auto as well as flex:0 0 auto — .sklad-button is width:100% at base,
     so the flex value alone left each answer the full width of the row and the
     three of them ran 469px past the viewport. */
  .consent__actions .sklad-button { flex: 0 0 auto; width: auto; }
}

/* --- preferences ------------------------------------------------------- */

/* Opened out, the notice stops being a one-line message and becomes a form,
   so it stops being a row. Left as row + space-between it put the categories
   hard left and the buttons hard right with 269px of nothing between them.
   Stacked, on a readable measure, with the answers under what they answer. */
@media (min-width: 1024px) {
  .consent.is-prefs .consent__inner {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-large);
  }

  /* Sized by the longest description, so a category's switch stays near the
     words it belongs to rather than at the far edge of the notice. */
  .consent.is-prefs .consent__prefs { max-width: 34rem; }
  .consent.is-prefs .consent__actions { justify-content: flex-end; }
}

.consent__prefs { display: none; }
.consent.is-prefs .consent__prefs { display: block; }
.consent.is-prefs .consent__copy { display: none; }

.consent__group {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-medium);
  padding-block: var(--space-medium);
  border-block-start: 1px solid var(--ui-100);
}

.consent__group:first-child { border-block-start: 0; }
.consent__group__title { color: var(--ui-950); }
.consent__group__text { color: var(--ui-600); max-width: 60ch; }

/* An always-on category still shows its control, disabled — hiding it would
   hide what the site does. */
.consent__group .toggle:disabled + .toggle__label { cursor: not-allowed; opacity: 0.6; }

/* Withdrawing consent has to be as reachable as giving it, so it sits at the
   end of the legal row, where the locale switcher used to. The drawer still
   carries its own locale switcher. */

/* A failed payment arrives on the same endpoint as a successful one, so it is
   the same page with a different notice — not a page of its own. What changes
   is that it must offer a way forward, because the basket is still live. */
.order-received__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-small-plus);
  margin-block-start: var(--space-large);
}

.woocommerce-notice--error {
  color: var(--status-error);
}

/* --------------------------------------------------------------------------
   LOADING — skeletons and the busy state.

   `cursor: wait` was the whole loading vocabulary, which says nothing on a
   touch device and nothing to a screen reader. Ported from the reference,
   which uses two motions for two different situations:

     shimmer-effect  a slow opacity pulse, for a block standing in for content
                     that has not arrived (a skeleton card in a grid)
     shimmer         a sweep across a placeholder, for a small field being
                     filled in place

   Both stop under prefers-reduced-motion — the grey block still reads as
   "not yet", which is the part that carries the meaning.
   -------------------------------------------------------------------------- */

@keyframes shimmer-effect { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } }
@keyframes shimmer { to { transform: translateX(100%); } }

.skeleton-box,
.skeleton-line,
.skeleton-pill,
.card-product--skeleton .card-product__medias,
.card-product--skeleton .card-product__title,
.card-product--skeleton .card-product__desc,
.card-product--skeleton .sklad-price {
  background-color: var(--ui-200);
  animation: shimmer-effect 2.4s infinite ease-in-out;
  animation-fill-mode: both;
  color: transparent;
  border-radius: 2px;
}

/* A skeleton card holds the real card's geometry, so the grid does not reflow
   when the answer arrives. The 4:5 media ratio is the whole point. */
.card-product--skeleton { pointer-events: none; }
.card-product--skeleton .card-product__medias { aspect-ratio: 4 / 5; }
.card-product--skeleton .card-product__title,
.card-product--skeleton .card-product__desc { min-height: 1em; }
.card-product--skeleton .sklad-price { min-height: 1em; width: 5ch; }

.skeleton-line { height: 1em; }
.skeleton-pill { height: 20px; width: 20px; border-radius: 999px; }

/* The sweep variant, for a field filling in place rather than a block
   standing in for a whole card. */
.skeleton-box,
.skeleton-line,
.skeleton-pill {
  position: relative;
  overflow: hidden;
}

.skeleton-box::after,
.skeleton-line::after,
.skeleton-pill::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / 45%), transparent);
  animation: shimmer 1.6s infinite;
}

/* Busy, but the real content is still on screen — dim it and lock it rather
   than replacing it, so nothing jumps. */
.is-loading { cursor: wait; }
.is-loading > * { pointer-events: none; }

.facets.is-loading .facets__blocks,
.line-item.is-loading .line-item__actions,
.grid-products.is-loading .grid-products__list {
  opacity: 0.5;
  transition: opacity var(--duration-fast) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-box,
  .skeleton-line,
  .skeleton-pill,
  .card-product--skeleton .card-product__medias,
  .card-product--skeleton .card-product__title,
  .card-product--skeleton .card-product__desc,
  .card-product--skeleton .sklad-price { animation: none; }

  .skeleton-box::after,
  .skeleton-line::after,
  .skeleton-pill::after { animation: none; content: none; }
}

/* --------------------------------------------------------------------------
   FOOTER SOCIAL — the follow row, as marks rather than words.

   A row of glyphs instead of a stacked list of names: the destinations are
   recognised by their marks, and four words down the column cost more height
   than the column has to give. Each link keeps an aria-label, so what is a
   picture for sighted readers is still a name to a screen reader, and each
   is padded to a 44px target rather than the 24px the glyph occupies.
   -------------------------------------------------------------------------- */

/* The mark sits with its name rather than replacing it: four glyphs alone
   ask the reader to recognise every brand, and the column has room for both.
   The list keeps the footer menu's own vertical rhythm — this row is one of
   four columns, so it has to read like the other three. */
/* No colour of its own. These are footer links like any other, so they take
   .footer-menu__link's colour and hover — `color: inherit` was picking up the
   footer's white and making them the loudest thing in the column. */
.footer-social__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-small);
}

.footer-social__link .icon { --icon-size: 16px; }

/* --------------------------------------------------------------------------
   GRID PUSH — an editorial banner sitting inside the product listing.

   Named for the reference's own .grid-products__push. It is a grid item like
   any card, so it takes card slots rather than interrupting the grid: 12 of
   the 24 desktop columns is exactly two cards plus the gap between them, and
   on mobile 12 of 12 is the full row.

   Its height is not declared. The row is already sized by the cards beside it,
   and stretching to that row is what keeps the listing on its rhythm — a
   fixed aspect ratio here would either crop the row or leave a step in it.
   -------------------------------------------------------------------------- */

/* One card slot, not two. A banner the width of two products reads as an
   interruption of the grid; at a card's width it reads as one of the things
   in it, which is what a tile in a listing should be. It also fixes mobile,
   where two slots meant the banner took the entire row. */
.grid-products__push { grid-column: span 6; }

/* A push can also take the whole row, for something that has to carry more
   than a line of copy. */
.grid-products__push--wide { grid-column: 1 / -1; }

.push-banner {
  position: relative;
  display: flex;
  /* 4:5, the same ratio and therefore the same box as the product photo it
     sits beside — not the card's full height. A banner that ran to the bottom
     of the card was taller than every image in the row and broke the line the
     photographs make across it.

     (It also carries no --design-ratio min-height: that ratio is 4.95 on
     mobile against the 392 board rather than 1.34 against the 1448 one, so
     calc(260 * ratio) resolved to 1287px and the banner grew to four times a
     card.) */
  aspect-ratio: 4 / 5;
  align-self: start;
  overflow: hidden;
  background-color: var(--surface-muted);
  color: var(--ui-00);
}

.push-banner__media {
  position: absolute;
  inset: 0;
}

.push-banner__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-slow) var(--ease-out);
}

/* The copy sits on photography, so it gets a scrim rather than trusting the
   image to be dark where the words are. */
.push-banner::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgb(0 0 0 / 55%), rgb(0 0 0 / 10%) 60%, transparent);
  pointer-events: none;
}

.push-banner__content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-xsmall);
  margin-block-start: auto;
  padding: var(--space-large);
}

.push-banner__eyebrow { color: var(--ui-00); opacity: 0.8; }
.push-banner__title { margin: 0; max-width: 20ch; }

/* The whole tile is the link, so the label is a marker rather than a second
   target — it carries the underline the rest of the system uses. */
.push-banner__more { --link-color: var(--ui-00); --link-underline-color: var(--ui-00); }

@media (hover: hover) and (pointer: fine) {
  .push-banner:hover .push-banner__media img { transform: scale(1.03); }
}

@media (max-width: 1023px) {
  .push-banner__content { padding: var(--space-medium); }
}

/* --------------------------------------------------------------------------
   NEWSLETTER MODAL — the signup as an interruption.

   This one IS a modal, unlike the cookie notice: it asks for something rather
   than requiring an answer, so it takes focus, dims the page and closes on
   Escape or on the overlay. That also means it owes the reader a real way out
   — the close button and "No thanks" are both plain, unhurried, and neither
   is disguised as the secondary half of a choice.

   It never opens over the cookie notice. Consent is a decision the page is
   waiting on; stacking a sales ask on top of it is how a site ends up with
   neither answered.
   -------------------------------------------------------------------------- */

.newsletter-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-large);
}

.newsletter-modal__overlay {
  position: absolute;
  inset: 0;
  background-color: rgb(0 0 0 / 45%);
  animation: newsletter-modal-fade var(--duration-medium) var(--ease-out);
}

.newsletter-modal__panel {
  position: relative;
  display: flex;
  width: 100%;
  max-width: calc(760 * var(--design-ratio));
  max-height: 100%;
  overflow: auto;
  background-color: var(--ui-00);
  animation: newsletter-modal-in var(--duration-medium) var(--ease-out);
}

@keyframes newsletter-modal-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes newsletter-modal-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .newsletter-modal__overlay,
  .newsletter-modal__panel { animation: none; }
}

.newsletter-modal__close {
  position: absolute;
  top: var(--space-small-plus);
  right: var(--space-small-plus);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  color: var(--ui-950);
}

.newsletter-modal__close .icon { --icon-size: 20px; }

/* The image is decoration, so it is the half that goes when there is no room
   for both — the form is the reason the panel exists. */
.newsletter-modal__media { display: none; }

.newsletter-modal__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.newsletter-modal__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-medium);
  padding: var(--space-xlarge) var(--space-large) var(--space-large);
  flex: 1 1 auto;
  min-width: 0;
}

@media (min-width: 1024px) {
  .newsletter-modal__media { display: block; flex: 0 0 45%; background-color: var(--surface-muted); }
  .newsletter-modal__body { padding: var(--space-xxlarge) var(--space-xlarge); justify-content: center; }
}

/* --------------------------------------------------------------------------
   SHOWCASE — a collection headline over one continuous row that opens with a
   lifestyle shot and continues into the products from it.

   The UX is Westwing's; the design is ours. What makes it work is that the
   room shot is a slide rather than a panel beside the carousel: the eye starts
   in the setting and scrolls straight into the pieces that furnish it, in one
   gesture, instead of reading a banner and then deciding whether to engage
   with a separate list.

   The heading is two lines doing two jobs — what the edit is, then whose it
   is — so they take different faces rather than different sizes of one.
   -------------------------------------------------------------------------- */

.showcase__header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-small) var(--space-medium);
  margin-block-end: var(--space-large);
}

.showcase__title {
  margin: 0;
  display: flex;
  flex-direction: column;
  /* One sentence over two lines, so one size for both. Declared here rather
     than on each line: two numbers kept equal by hand drift the first time
     somebody changes one of them. */
  --text-showcase-lead: 1.75rem;
}

/* `em` inside the lead is set in the serif's italic. Sklad ships one
   upright master, so the browser synthesises the slant — acceptable for a
   single word, but it is a faux italic, not a drawn one. Swap in a real cut
   here if one arrives. */
.showcase__title__lead em { font-style: italic; }

.showcase__title__lead {
  font-family: var(--font-serif-condensed);
  font-size: var(--text-showcase-lead, 1.75rem);
  line-height: 1.15;
  /* Display-sized serif that is not on the .title-* scale, so the scale's
     tracking does not reach it — and at 32px the difference showed: it set
     visibly looser than every heading around it. The one place outside the
     scale that needed saying twice; the rest of the serif that is not a title
     is 14-20px UI text, where the face's default is right. */
  letter-spacing: var(--tracking-display);
  font-weight: var(--font-weight-regular);
  color: var(--ui-950);
  font-variant-ligatures: no-contextual;
}

.showcase__title__name {
  /* The same face as the lead above it. It is a single master, so asking for
     bold here would have the browser synthesise one; the weight matches the
     lead's for that reason. Set as written rather than forced to capitals, and
     at the lead's own size: the wordmark is the only thing setting this line
     apart from the one above it. */
  font-family: var(--font-serif-condensed);
  font-size: var(--text-showcase-lead, 1.75rem);
  line-height: 1.15;
  font-weight: var(--font-weight-regular);
  letter-spacing: var(--tracking-display);
  color: var(--ui-950);
}

/* Sits on the baseline of the second line, not under the block — it belongs
   to the name, not to the section. */
.showcase__link { align-self: flex-end; margin-block-end: 0.35em; }

/* A block, not a track. Everything in the row is present at once — the point
   is the edit read as one composition, and a scroller hides part of it behind
   a gesture. The feature keeps its lead position and the products follow. */
.showcase__grid {
  display: grid;
  /* Two up even on the narrowest screen, matching the listing — stacked, four
     full cards under the room shot ran to 2,512px, which is a scroll rather
     than a block. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gutters);
}

.showcase__feature { grid-column: 1 / -1; }

/* The room shot leads and is the tall element: at Westwing it is a portrait
   image running the full height of a 2x2 block of products, not a wide banner
   above a single row. Measured off their homepage at 1440: feature 678x1003
   (0.68), cards 323x431 in two rows of two. Stretching it across both rows is
   what gives the section its height, so it carries no ratio of its own on
   desktop — the cards beside it set the height and it fills that. */
.showcase__feature {
  position: relative;
  overflow: hidden;
  align-self: stretch;
  aspect-ratio: 4 / 5;
}

.showcase__feature img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--duration-slow) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .showcase__feature:hover img { transform: scale(1.03); }
}

@media (min-width: 768px) {
  /* The room shot takes the top, the four products a 2x2 under it. */
  .showcase__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .showcase__feature { grid-column: 1 / -1; aspect-ratio: 16 / 9; }
}

@media (min-width: 1024px) {
  .showcase__title { --text-showcase-lead: 2rem; }

  /* Feature at ~51% beside two card columns, and spanning both card rows —
     the block is as tall as two products stacked, which is the whole point of
     the composition. */
  .showcase__grid {
    grid-template-columns: 2.1fr repeat(2, minmax(0, 1fr));
    grid-template-rows: auto auto;
  }

  .showcase__feature {
    grid-column: 1;
    grid-row: 1 / span 2;
    aspect-ratio: auto;
  }
}

/* ============================================================
   IMAGE LOADING STATE
   ============================================================
   Every image frame already reserves its space through aspect-ratio, so
   nothing shifts when a photo arrives. What was missing is anything to look
   at in the meantime: the frame stayed the page's own white, so a slow
   connection showed a blank rectangle and then a sudden photo.

   A tint on the image itself is what fills it. The browser paints the
   element's background as soon as the box exists and covers it the moment
   the first pixels decode, so it needs no class, no script and no state to
   clean up — an image that fails to load keeps the tint instead of showing
   a broken frame.

   Scoped to the frames the catalogue draws. A logo or an inline icon has no
   reserved box and would take the tint as a visible grey plate.
   ============================================================ */

.card-product__slide img,
.mini-slider-item__media img,
.media img,
.hero__media img,
.diptych-media__image img,
.diptych-media__background img,
.push-banner__media img,
.card-story__media img,
.product-gallery__item img,
.collection-preview__media img,
.line-item__media img,
.mini-cart__media img {
  background-color: var(--ui-50);
}

/* The tint would otherwise sit behind a transparent PNG for good. */
.card-product__slide img[src$=".png"],
.media img[src$=".png"] {
  background-color: transparent;
}


/* --- Search results outside the product grid -------------------------------
   The sheet's Categories, Stories and Pages tabs. Neither the theme nor the
   static design ever said what a result in these scopes looks like — the tabs
   were four buttons that changed nothing — so this is the plainest thing that
   answers the question: the name, linked, one per line.
   -------------------------------------------------------------------------- */

.search-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  grid-column: 1 / -1;
  margin: 0;
  padding: 0;
  list-style: none;
}


/* The gallery figure has to hold the badges it now carries. The inset comes
   from a variable the card declares on itself, so the figure has to declare it
   too or the badges sit flush in the corner. */
.product-gallery__item {
  position: relative;
  --card-product-padding-overlay: var(--space-medium);
}

.product-gallery__item > .card-product__overlay { z-index: 2; }

/* --- Scrim + badge offset for the overlaid trail on a phone -----------------
   The trail sits over the gallery and stays put while the images move past it:
   .product-gallery is a scroll-snap track on a phone and every figure takes a
   turn under the trail. So the scrim belongs to every figure, not to the first
   one — it used to ride on .product-gallery__item--overlay, and the second
   swipe put white breadcrumbs on an unshaded photo.

   That class still marks the first figure (or the lone placeholder when a
   product has no photos — see sklad_render_single_product()), because the
   badges are its own business: sklad_product_badges_html() prints them on
   index 0 only, so only that figure moves them clear of the trail.

   Scrim ported from .hero--has-media::before — the top scrim a transparent
   header gets over a hero — same eased 16-stop ramp so it does not band,
   darkest at the top edge and fading to nothing. Resized for a product
   thumbnail instead of a full-bleed hero (that one runs 33% / min 320px on an
   85svh hero) and raised from 12% to 45% opacity: the header's version only
   has to clear a hairline on a photo an editor already picked for having a
   dark top; a product photo carries no such guarantee, so this scrim has to
   hold contrast for white text on any crop by itself, the way the billboard
   hero's own scrim does for its white text (see .hero--billboard above). */
@media (max-width: 1023px) {
  .product-gallery__item::before {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    z-index: 1;
    height: 45%;
    min-height: 140px;
    pointer-events: none;
    opacity: 0.45;
    background: linear-gradient(
      0deg,
      rgb(from var(--ui-950) r g b / 0%) 0%,
      rgb(from var(--ui-950) r g b / 1%) 6.67%,
      rgb(from var(--ui-950) r g b / 4%) 13.33%,
      rgb(from var(--ui-950) r g b / 8%) 20%,
      rgb(from var(--ui-950) r g b / 15%) 26.67%,
      rgb(from var(--ui-950) r g b / 23%) 33.33%,
      rgb(from var(--ui-950) r g b / 33%) 40%,
      rgb(from var(--ui-950) r g b / 44%) 46.67%,
      rgb(from var(--ui-950) r g b / 56%) 53.33%,
      rgb(from var(--ui-950) r g b / 67%) 60%,
      rgb(from var(--ui-950) r g b / 77%) 66.67%,
      rgb(from var(--ui-950) r g b / 85%) 73.33%,
      rgb(from var(--ui-950) r g b / 92%) 80%,
      rgb(from var(--ui-950) r g b / 96%) 86.67%,
      rgb(from var(--ui-950) r g b / 99%) 93.33%,
      var(--ui-950) 100%
    );
  }

  /* The badge sat --card-product-padding-overlay (16px) from the top; the
     overlaid trail now occupies that corner instead, so the badge moves down
     by the trail's own footprint — padding-block-start (--space-medium-plus,
     20px) plus text-medium's line-height (18px), the same 20 + 18 = 38px
     PR #261 already measured for this row — plus the gap the badge already
     had. Only the top offset changes; left/right/bottom stay on the shared
     --card-product-padding-overlay margin. */
  .product-gallery__item--overlay .card-product__badges {
    margin-block-start: calc(var(--space-medium-plus) + var(--text-medium-lh) + var(--card-product-padding-overlay));
  }
}

/* --- Author ------------------------------------------------------------------
   Who wrote the article, and their own archive.

   New rather than ported: the static design system has no author anywhere, so
   there was nothing to match. Built from pieces that already exist — the
   article's measure, .title-h6 for the name, the same .link the rest of the
   page uses — so it reads as part of the same set rather than as a component
   from somewhere else.

   The portrait is square. A 4:5 frame is the product ratio and carries product
   meaning; a face is not a piece of furniture.
   --------------------------------------------------------------------------- */

.author-card {
  /* The same track .article__inner sits in, not a max-width of its own. The
     article's measure is built from grid columns, so a fixed width here would
     agree with it today and drift the moment the grid changes. */
  grid-column: 1 / -1;
  display: flex;
  gap: var(--space-medium);
  align-items: flex-start;
  margin-block-start: var(--space-xxlarge);
  padding: var(--space-large);
  border: 1px solid var(--ui-100);
  border-radius: var(--radius-control);
  background-color: var(--ui-00);
}

.author-card__media {
  flex: 0 0 auto;
  width: 72px;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 50%;
}

/* <picture> is a source picker, not a box: without this the img inside has no
   box to fill and the frame collapses. Same trap the product media hit. */
.author-card__media picture { display: contents; }

.author-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.author-card__body { min-width: 0; }

.author-card__eyebrow {
  margin: 0;
  color: var(--ui-600);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.author-card__name { margin: var(--space-xxsmall) 0 0; }

.author-card__bio {
  margin: var(--space-small) 0 0;
  color: var(--ui-800);
}

.author-card__more { margin: var(--space-small) 0 0; }

/* --- Author archive header -------------------------------------------------- */

.author-header {
  /* Stacked and centred: photograph, name, biography. The archive has one
     subject and nothing beside it to line up against, so a left-aligned row
     left the name floating against a wide empty right half. */
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-medium);
  padding-block-start: var(--space-large);
}

.author-header__media {
  flex: 0 0 auto;
  width: 112px;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 50%;
}

.author-header__media picture { display: contents; }

.author-header__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.author-header__body { min-width: 0; }

.author-header__eyebrow {
  margin: 0;
  color: var(--ui-600);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.author-header__name { margin: var(--space-xxsmall) 0 0; }

.author-header__bio {
  margin: var(--space-small) 0 0;
  /* Centred text needs a measure or the line runs the width of the page and
     the eye loses the start of each line. */
  max-width: 58ch;
  margin-inline: auto;
  color: var(--ui-800);
}

@media (min-width: 1024px) {
  .author-card { grid-column: 6 / 20; }
}

@media (max-width: 1023px) {
  /* Stacked below the breakpoint: side by side, the portrait leaves the name a
     column too narrow to sit on one line. */
  .author-card {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* --- Carousel with nothing to scroll -----------------------------------------
   Set by the carousel script when the track's scrollable distance is zero: the
   slides already fit, so the arrows have nowhere to go. Hidden rather than left
   disabled, because they only appear on hover and a control that appears and
   then refuses is read as a fault.
   --------------------------------------------------------------------------- */

.is-static .product-row__nav,
.is-static .mini-slider__nav { display: none; }

/* --- Empty states ------------------------------------------------------------
   Centred, all of them, as one rule rather than a decision taken per screen.

   An empty state is the one case where a page has a single subject and nothing
   beside it: the cart's summary column, the wishlist's grid, the account's
   second column and the listing's filter rail are all absent precisely because
   there is nothing to show. Left alignment on any of them leaves a short line
   of text against an empty right half.

   This was arrived at one screen at a time — the account's empty orders, then
   the basket drawer — with a different justification each time and the cart
   page left alone on the reasoning that its summary sat beside it. Measured,
   that summary is not rendered when the cart is empty. There was no second
   column on any of them.

   The buttons already carry `width: max-content`, so centring the container is
   all that is needed to centre them too.
   --------------------------------------------------------------------------- */

.cart-empty__inner,
.wishlist-empty,
.grid-products__empty,
.mini-cart__empty {
  align-items: center;
  text-align: center;
}

/* ==========================================================================
   MADE IN SERBIA — the steps list

   The reference's accordion-edito block: a narrow centred column carrying
   the same accordion the FAQ uses. Only this frame is new — the accordion
   itself, its borders and its motion, stay the FAQ's.
   ========================================================================== */

/* The same reading measure the statement hero caps at, so the two columns
   share their edges down the page instead of stepping in and out. */
.mis-steps {
  max-width: 620px;
  margin-inline: auto;
  padding-block: var(--space-xxlarge);
}

.mis-steps__title {
  margin: 0 0 var(--space-large-plus);
}

/* The last step's bottom rule closes the list, as on the FAQ. */
.mis-steps__list { border-block-end: 1px solid var(--beige-200); }

/* ==========================================================================
   CHECKLIST — the buying help page's pre-order list

   Three headed lists side by side rather than one long column. The items
   split by when you actually check them — the spot the piece stands in, the
   way in, the piece itself — and someone scanning for one of the three
   shouldn't have to read the other two to find it.

   Structure comes from the page content: each <h3> is a column and each <li>
   a line, so adding a column is adding a heading in the editor, not a rule
   here. Nothing in this block is a new size or colour — the column rule and
   its muted heading are the ones .product-features and .faq__group already
   use, on the page that already carries both.
   ========================================================================== */

.checklist { padding-block: var(--space-xxlarge); }

.checklist__intro { max-width: 620px; }

.checklist__lead {
  margin-block-start: var(--space-medium);
  color: var(--text-secondary);
}

/* auto-fit rather than a fixed three: a fourth heading in the content wraps
   onto its own row instead of squeezing four columns into the space of three. */
.checklist__groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-large) var(--space-xlarge);
  margin-block-start: var(--space-xlarge);
}

.checklist__group {
  padding-block-start: var(--space-medium);
  border-block-start: 1px solid var(--beige-200);
}

.checklist__group-title {
  margin: 0 0 var(--space-medium);
  color: var(--ui-500);
}

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

.checklist__item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-small);
  padding-block: var(--space-small);
}

.checklist__item + .checklist__item { border-block-start: 1px solid var(--beige-200); }

/* Nudged down to the first line's cap height rather than centred on the line
   box, so the mark beside a two-line item still sits next to its first word. */
.checklist__mark {
  flex: 0 0 auto;
  display: flex;
  margin-block-start: 0.15em;
  color: var(--text-tertiary);
}

