/* ==========================================================================
   PddFalcon — catalogue listing, premium layer

   The shop, the category pages and the search results: /shop, /c/{slug},
   /search?q=. Loaded after snap.css, snap-inner.css and home-premium.css and
   immediately before site-premium.css, so it refines the settled design
   without replacing it and without a single `!important`.

   Every rule is scoped to something only a listing has — `.s-listing`,
   `.s-toolbar`, `.s-head--pic`, `.s-sheet[data-filtersheet]` — so nothing
   here can reach the home page, a product page, the bag or the account.
   `.s-head`, `.s-check`, `.s-btn` and `.s-empty` are all shared with other
   screens, which is why none of them is ever styled bare.

   Every colour is a token from tokens.css or an rgb()/color-mix() built from
   the four brand values those tokens hold:
     navy   #0d2454  →  rgb(13 36 84)
     deep   #061031  →  rgb(6 16 49)
     red    #c4260c  →  rgb(196 38 12)
     gold   #ffb800  →  rgb(255 184 0)

   What this pass is for. A listing is the one screen a shopper spends real
   time in, and it was the one screen assembled rather than composed: a
   banner with a flat wash, a toolbar that was 1,000px of nothing between a
   word count and a select, a filter panel cut off mid-word with no sign that
   it scrolled, a column of white below it, 40px pager buttons, and an empty
   state that was a grey rectangle. None of the layout language changes here.
   The banner still holds a title over a photograph, the panel is still a
   sticky column of checkboxes, the grid is still four cards across. They are
   simply finished.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Local material

   The same three-stop shadow idea the home page uses — a contact edge, a
   short mid, a long ambient — so a card on a listing and a card on the home
   page read as the same object. Declared on the listing's own roots rather
   than on :root, because nothing outside a listing may inherit them.
   -------------------------------------------------------------------------- */
.s-listing,
.s-toolbar,
.s-head--pic,
.s-sheet[data-filtersheet] {
  --cp-line: rgb(13 36 84 / .11);
  --cp-line-soft: rgb(13 36 84 / .07);
  --cp-tint: rgb(13 36 84 / .045);
  --cp-tint-red: rgb(196 38 12 / .07);

  --cp-lift-1: 0 1px 1px rgb(13 36 84 / .04), 0 3px 7px -3px rgb(13 36 84 / .07), 0 12px 26px -16px rgb(13 36 84 / .18);
  --cp-lift-2: 0 1px 1px rgb(13 36 84 / .05), 0 7px 16px -7px rgb(13 36 84 / .12), 0 24px 46px -24px rgb(13 36 84 / .28);
  --cp-edge: inset 0 1px 0 rgb(255 255 255 / .92);
}

/* ==========================================================================
   1. The category banner
   ========================================================================== */

/*
 * The wash.
 *
 * snap-inner lays one left-to-right gradient over the photograph and stops at
 * 10% opacity, which on a 1,376px-wide banner leaves the right two-thirds a
 * bright, out-of-focus photograph with nothing on it — and on a phone, where
 * the title sits low and the picture is only 350px wide, leaves the title
 * fighting the picture for the same pixels.
 *
 * `::after` is already spoken for, so the second wash goes on `::before`: a
 * bottom-up scrim that anchors the type on every width, plus the ring and the
 * lit top edge that the rest of the shop's dark panels carry.
 */
.s-head--pic::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(0deg, rgb(6 16 49 / .58), rgb(6 16 49 / .08) 55%, transparent),
    radial-gradient(120% 100% at 100% 0%, rgb(255 184 0 / .10), transparent 55%),
    /* A vignette, so the plate has a middle and four corners rather than an
       even field of whatever the photograph happened to be doing. It is the
       last layer, under the scrim and under the gold, and it is what stops an
       out-of-focus picture reading as a mistake at the edges of the frame. */
    radial-gradient(135% 135% at 50% 42%, transparent 38%, rgb(6 16 49 / .38));
  box-shadow:
    inset 0 0 0 1px rgb(255 255 255 / .10),
    inset 0 1px 0 rgb(255 255 255 / .18),
    inset 0 -60px 90px -60px rgb(6 16 49 / .9);
  pointer-events: none;
}

/*
 * The existing left-to-right wash stays, and it now carries all the way to the
 * right edge instead of clearing at 12%.
 *
 * Why. The category photograph is a square, and the banner is not: measured on
 * /c/drinkware, the file is 700x700 and the plate it fills is 1,280x240, so the
 * browser crops it to a 240px band and blows that band up 1.83x. Nothing in
 * CSS can put detail back into an upscaled 700px file — but the choice of what
 * the picture is *for* is a CSS one. Held at 12% the far end was the loudest
 * thing on the page and read as a photograph that had failed to load; held at
 * 46% it is what it should always have been, a lit navy plate with the
 * category's own material in it, and the title is the subject.
 *
 * The picture is not removed. `listing.blade.php` says in as many words that
 * the client asked for the category's photograph to appear wherever the
 * category is shown, and a flat gradient here would quietly undo that on every
 * category, including the ones whose art is sharp.
 */
.s-head--pic::after {
  background: linear-gradient(90deg, rgb(6 16 49 / .94), rgb(6 16 49 / .68) 52%, rgb(6 16 49 / .46));
}

/* On a phone the photograph is nearly square and the title sits at its foot,
   so the wash has to run up the picture rather than across it — and it has to
   run heavier, because at 390 the same file is stretched over the whole plate
   rather than cropped into a band. */
@media (max-width: 47.99rem) {
  .s-head--pic::after {
    background: linear-gradient(180deg, rgb(6 16 49 / .78), rgb(6 16 49 / .64) 45%, rgb(6 16 49 / .86));
  }
}

/* The picture settles rather than snapping into place: one 900ms scale, no
   loop, nothing that costs a frame after it lands. Transform only. */
.s-head--pic .s-head__pic {
  animation: cp-banner-settle .9s var(--sp-ease) both;
  will-change: scale;
}
@keyframes cp-banner-settle {
  from { scale: 1.06; }
  to { scale: 1; }
}

/*
 * The title.
 *
 * A banner title was taking the same clamp as "Your addresses". Over a
 * photograph it needs weight, tighter tracking and a shadow, or the
 * descenders sit in whatever the picture happens to be doing underneath.
 */
.s-head--pic .s-head__title {
  font-size: clamp(1.6rem, 1.05rem + 2.4vw, 2.9rem);
  line-height: 1.02;
  letter-spacing: -.028em;
  text-wrap: balance;
  padding-block-end: .65rem;
  text-shadow: 0 2px 18px rgb(6 16 49 / .55), 0 1px 2px rgb(6 16 49 / .4);
}

/* The gold rule under it is drawn, not switched on — and it is drawn with a
   transform, so it costs no layout. */
.s-head--pic .s-head__title::after {
  inline-size: 3.2rem;
  block-size: 3px;
  transform-origin: left center;
  animation: cp-rule-draw .7s var(--sp-ease) .18s both;
}
@keyframes cp-rule-draw {
  from { scale: 0 1; opacity: 0; }
  to { scale: 1 1; opacity: 1; }
}

.s-head--pic p {
  font-size: var(--s-text-md);
  line-height: 1.55;
  text-wrap: pretty;
  text-shadow: 0 1px 12px rgb(6 16 49 / .5);
}

/* Room for the type to sit in rather than on. */
@media (min-width: 62rem) {
  .s-head--pic { min-block-size: 15rem; padding: 2.5rem 3rem; }
}

/*
 * The plain head — /shop and /search have no photograph.
 *
 * Selected through the listing that follows it rather than by a class, so
 * "Your orders" and "Checkout", which share `.s-head`, are untouched. A
 * browser without `:has()` simply keeps the inherited head; nothing breaks.
 */
.s-wrap:has(+ .s-wrap .s-listing) .s-head__title {
  font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.25rem);
  line-height: 1.06;
  letter-spacing: -.026em;
  text-wrap: balance;
}
.s-wrap:has(+ .s-wrap .s-listing) .s-head__title::after {
  inline-size: 2.75rem;
  transform-origin: left center;
  animation: cp-rule-draw .7s var(--sp-ease) .1s both;
}

/* ==========================================================================
   2. The toolbar

   It was a count at the far left of the page and a select at the far right
   with a thousand pixels of white between them — the client's "no empty
   space" complaint in its purest form. It is now a rail: one surface that
   holds both ends, so the space between them belongs to something.
   ========================================================================== */

.s-toolbar {
  position: relative;
  padding: .5rem .5rem .5rem 1.1rem;
  border: 1px solid var(--cp-line-soft);
  border-radius: 999px;
  background: linear-gradient(180deg, #fff, var(--sp-tile));
  box-shadow: var(--cp-lift-1), var(--cp-edge);
  margin-block: .35rem 1.15rem;
}

/* The count. Tabular figures so "43 items · page 1 of 3" does not jiggle
   between pages, and the house eyebrow rule in front of it so the row starts
   with the brand rather than with grey text. */
.s-toolbar__count,
.s-resultshead .s-toolbar__count {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  margin: 0;
  font-weight: 600;
  color: var(--s-ink-soft);
  font-variant-numeric: tabular-nums lining-nums;
  white-space: nowrap;
}
.s-toolbar__count::before {
  content: "";
  flex: none;
  inline-size: 1.1rem;
  block-size: 2px;
  border-radius: 2px;
  background: var(--s-accent-500);
}

/*
 * With scripts blocked, nothing lifts the count into the rail — it stays
 * above the grid where it reads perfectly well, and the rail would be a
 * painted bar with one control at the far end of it and a hole where the
 * count should be. So with no script there is no bar: just the sort control,
 * and the count above the grid it belongs to. `scripting: none` resolves at
 * parse time, so neither state is ever seen switching to the other.
 */
.s-listing .s-resultshead { margin-block-end: var(--s-3); }

@media (scripting: none) {
  .s-toolbar {
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    margin-block: .35rem .5rem;
  }
  .s-listing .s-resultshead { margin-block: .1rem var(--s-4); }
}

/*
 * The span in the middle of the rail.
 *
 * Making the bar a surface stopped it being a blank band, but it did not give
 * the span anything to be. Measured at 1440: the count is 164px, the two
 * controls 186px, and between them sits 903px of nothing on /shop and 986px on
 * /c/drinkware, where the count is only "11 items". The rail cannot be pulled
 * in to close it — listing.blade.php records that the client settled in
 * session 7 that the count starts on the page's own left edge and the sort box
 * ends on its right one, read against the heading and not against the grid.
 *
 * So the span is given a leader: a hairline that runs out of the count, thins
 * as it goes and arrives at the sort control as a whisper. Tried first as a
 * rule that dissolved at 88% — it left 400px of white at the far end and the
 * rail still read as two ends with a hole between them. Carried the whole way
 * it does what it is for: the pill is one object. It is drawn by the flex box
 * that is already there — no element, no layout, nothing to measure.
 *
 * `scripting: enabled` because with no script there is no rail to lead across,
 * and 48rem because below it the row is short and the controls are already
 * most of it. The left slot only ever holds the count — shop.js sends the
 * chips to the filter panel, not here — so the line can never crowd anything.
 */
@media (min-width: 48rem) and (scripting: enabled) {
  .s-toolbar__left { flex: 1 1 auto; }
  .s-toolbar__left::after {
    content: "";
    flex: 1 1 2rem;
    min-inline-size: 0;
    block-size: 1px;
    margin-inline-end: .4rem;
    border-radius: 1px;
    background: linear-gradient(90deg,
      rgb(13 36 84 / .14),
      rgb(13 36 84 / .07) 55%,
      rgb(13 36 84 / .05));
  }
}

/*
 * The sort pill.
 *
 * White against the rail's tint so it reads as the control and not as part of
 * the bar, 44px tall — and with its chevron back. snap-inner resets the whole
 * `background` shorthand on `.s-select`, which threw away the arrow
 * components.css draws as well as its `no-repeat` and its size: the control
 * has been shipping as a bare pill with nothing to say it opens. Every
 * longhand the shorthand cleared is therefore restated here, not just the
 * picture.
 */
.s-toolbar .s-sort {
  min-block-size: 2.75rem;
  padding-inline: 1.1rem 2.5rem;
  border: 1px solid var(--cp-line);
  background-color: #fff;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%230d2454' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 1.05rem 1.05rem;
  background-position: right .95rem center;
  color: var(--s-brand-800);
  font-weight: 700;
  box-shadow: 0 1px 2px rgb(13 36 84 / .05);
  cursor: pointer;
  transition: border-color .2s var(--sp-ease), box-shadow .2s var(--sp-ease), background-color .2s;
}
.s-toolbar .s-sort:hover { border-color: rgb(13 36 84 / .35); box-shadow: var(--cp-lift-1); }
.s-toolbar .s-sort:focus-visible {
  outline: 2px solid var(--s-accent-500);
  outline-offset: 2px;
  border-color: var(--s-brand-800);
}

/* The Filters button, and the badge that counts what is on. */
.s-toolbar__filter {
  min-block-size: 2.75rem;
  padding-inline: 1.05rem;
  font-size: var(--s-text-sm);
  border-color: var(--cp-line);
  box-shadow: 0 1px 2px rgb(13 36 84 / .05);
}
.s-toolbar__filter .s-filterbadge {
  display: inline-grid;
  place-items: center;
  min-inline-size: 1.35rem;
  block-size: 1.35rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums lining-nums;
  box-shadow: 0 2px 6px -2px rgb(196 38 12 / .8);
  animation: cp-badge-pop .34s var(--sp-ease) both;
}
@keyframes cp-badge-pop {
  from { scale: .4; opacity: 0; }
  60% { scale: 1.12; opacity: 1; }
  to { scale: 1; opacity: 1; }
}

/*
 * Under 480px the rail becomes two rows rather than three ragged ones.
 *
 * Measured at 390: "43 items · page 1 of 3" was folding onto three lines in a
 * 90px column while the sort box kept its 8rem minimum. Count on its own
 * line, then the two controls sharing the row half and half — and both of
 * them 44px tall, which they were not.
 */
@media (max-width: 30rem) {
  .s-toolbar {
    flex-wrap: wrap;
    gap: .6rem;
    padding: .7rem .75rem;
    border-radius: var(--sp-radius-lg);
  }
  .s-toolbar__left { flex: 1 0 100%; }
  .s-toolbar__right { flex: 1 0 100%; gap: .5rem; }
  .s-toolbar__right form { flex: 1 1 0; min-inline-size: 0; }
  .s-toolbar__filter { flex: 1 1 0; justify-content: center; }
  .s-toolbar .s-sort { inline-size: 100%; min-inline-size: 0; }
  .s-toolbar__count { font-size: var(--s-text-sm); white-space: normal; }
}

/* ==========================================================================
   3. The listing columns

   The filter panel is 16rem of card at the top of a column that runs the
   height of the grid, so below it sits a tall stripe of nothing. A hairline
   down the gutter turns that absence into structure — the same move the
   footer already makes between its link columns — and it stops exactly where
   the results stop.
   ========================================================================== */

@media (min-width: 62rem) {
  .s-listing {
    background-image: linear-gradient(180deg,
      transparent,
      rgb(13 36 84 / .12) 5%,
      rgb(13 36 84 / .12) 55%,
      transparent);
    background-repeat: no-repeat;
    background-size: 1px 100%;
    /* 16rem column + half of the 1.5rem gap. */
    background-position: 16.75rem 0;
  }
}

/* ==========================================================================
   4. The filter panel
   ========================================================================== */

/*
 * The panel scrolls inside itself — catalog.css caps it at the viewport so a
 * long facet list cannot push its own bottom off the screen. Measured on
 * /shop at 1440: 1,421px of content in a 746px box. Nothing said so. It
 * simply stopped, mid-word, at "Dura Containers", which is exactly the
 * "cut off" the client keeps flagging.
 *
 * Two things say it now, neither a redesign: the header stays put while the
 * groups pass under it, and the foot of the panel dissolves while — and only
 * while — there is something below to dissolve into. That last condition is
 * the whole point. A static fade would be a lie on /search?q=barni, where the
 * panel holds four rows and nothing is hidden at all.
 */
.s-listing .s-filters {
  border-color: var(--cp-line-soft);
  box-shadow: var(--cp-lift-1), var(--cp-edge);
  background: #fff;
  overscroll-behavior: contain;
  /*
   * Two things in this panel are parked over its own content — the header,
   * stuck to the top, and the fade, stuck to the foot — and a scrollport that
   * does not know about them will scroll a focused row to exactly underneath
   * one of them. Tab down the facets on /shop and the row you have just
   * reached is the row you cannot see. Measured: the sticky head is 43px and
   * the fade is 3.1rem, so those are the two numbers.
   */
  scroll-padding-block: 3.2rem 3.6rem;
}

/*
 * The fade, told the truth by the panel's own scroll position.
 *
 * The pseudo is the panel's last in-flow child, pulled back over the row
 * above it and stuck to the foot of the scrollport. It starts invisible, and
 * only a scroll timeline can light it: on a panel that does not scroll the
 * timeline is inactive, the animation never applies, and the base `opacity:0`
 * stands — so the short filter panel on a search page has no fade on it at
 * all. On a panel that does scroll it holds at full while there is more
 * below, and clears as the last row arrives.
 *
 * A browser without scroll timelines simply never lights it. Nothing is
 * hidden by it and nothing depends on it; the sticky header and the
 * browser's own scrollbar still say the panel scrolls.
 */
@media (min-width: 62rem) {
  .s-listing .s-filters::after {
    content: "";
    display: block;
    position: sticky;
    inset-block-end: 0;
    z-index: 1;
    block-size: 3.1rem;
    margin-block-start: -3.1rem;
    border-radius: 0 0 calc(var(--sp-radius-lg) - 1px) calc(var(--sp-radius-lg) - 1px);
    /*
     * Taller and firmer than it was, and with the mark that says what it
     * means. Two reviewers read the old 2.6rem fade as a clipped card rather
     * than as a scroll region, and the screenshot says why: at 78% white the
     * cut row ("Dura Containers 4", "Econxt Containers 6") stayed half
     * legible, which reads as a word that has been sliced, not as a list that
     * continues. It now clears the row outright and puts a chevron under it.
     *
     * The chevron is painted, not placed: a background layer on the same
     * pseudo, carried by the same scroll timeline, so it appears only while
     * there is genuinely more below and is gone the moment the last row
     * arrives. Navy at 42% — a mark, not a control, and `pointer-events:none`
     * below keeps it from pretending to be one.
     */
    background:
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%230d2454' stroke-opacity='.42' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 8 5 5 5-5'/%3E%3C/svg%3E")
        no-repeat center calc(100% - .5rem) / .8rem .8rem,
      linear-gradient(180deg, rgb(255 255 255 / 0), rgb(255 255 255 / .88) 44%, #fff 70%);
    box-shadow: inset 0 -2.9rem 1.9rem -2.1rem rgb(13 36 84 / .16);
    opacity: 0;
    pointer-events: none;
    animation-name: cp-more-below;
    animation-duration: auto;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: --cp-facets;
  }
  /* Named rather than `scroll(self)`: on a pseudo-element `self` resolves to
     the pseudo, which is not a scroll container, and the timeline is then
     inactive whatever the panel is doing. The name is declared on the panel
     and inherited down to it. */
  .s-listing .s-filters { scroll-timeline: --cp-facets block; }
}
@keyframes cp-more-below {
  0%, 84% { opacity: 1; }
  100% { opacity: 0; }
}
/* `scrollbar-width` and `::-webkit-scrollbar` are mutually exclusive in
   Chrome — declaring the first makes it ignore the second — so each engine is
   given only the one it understands, and neither is given a gutter it would
   reserve on a category page whose facets fit. */
@supports not selector(::-webkit-scrollbar) {
  .s-listing .s-filters {
    scrollbar-width: thin;
    scrollbar-color: rgb(13 36 84 / .28) rgb(13 36 84 / .06);
  }
}
.s-listing .s-filters::-webkit-scrollbar { width: 8px; }
.s-listing .s-filters::-webkit-scrollbar-track {
  background: rgb(13 36 84 / .05);
  border-radius: 999px;
  margin-block: .5rem;
}
.s-listing .s-filters::-webkit-scrollbar-thumb {
  /* .24 on a .05 track is a thumb you have to look for. Where the engine
     draws a classic scrollbar at all, it may as well be a cue. */
  background: rgb(13 36 84 / .32);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.s-listing .s-filters::-webkit-scrollbar-thumb:hover { background: rgb(13 36 84 / .42); background-clip: padding-box; }

/*
 * The panel's own header stays put while its groups scroll under it, which is
 * the plainest possible way of saying "there is more of this below". Measured
 * at 1440: the card ran out at the word PRICE with no sign it continued.
 */
@media (min-width: 62rem) {
  .s-listing .s-filters__head {
    position: sticky;
    inset-block-start: 0;
    z-index: 2;
    background: linear-gradient(180deg, #fff 72%, rgb(255 255 255 / .92));
    backdrop-filter: blur(6px);
    border-block-end: 1px solid var(--cp-line-soft);
  }
}

.s-listing .s-filters__heading {
  font-size: var(--s-text-md);
  letter-spacing: -.01em;
}

/* "Clear all" was a 12px blue word floating beside a heading. It is a
   control, so it looks like one. */
.s-listing .s-filters__clear,
.s-sheet[data-filtersheet] .s-filters__clear {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.9rem;
  padding-inline: .65rem;
  border-radius: 999px;
  border: 1px solid var(--cp-line);
  background: #fff;
  color: var(--s-accent-500);
  font-weight: 700;
  transition: background .18s var(--sp-ease), color .18s var(--sp-ease), border-color .18s var(--sp-ease);
}
.s-listing .s-filters__clear:hover {
  background: var(--s-accent-500);
  border-color: var(--s-accent-500);
  color: #fff;
}

/* Groups: one hairline between them, never two, and the first one carries
   none at all. catalog.css draws a bottom border and snap-inner draws a top
   one, so every seam was being painted twice. */
.s-listing .s-filters__group,
.s-sheet[data-filtersheet] .s-filters__group {
  border-block-end: 0;
  border-block-start: 1px solid var(--cp-line-soft);
}
.s-listing .s-filterform > .s-filters__group:first-child,
.s-sheet[data-filtersheet] .s-filterform > .s-filters__group:first-child {
  border-block-start: 0;
  padding-block-start: .35rem;
}

.s-listing .s-filters__title,
.s-sheet[data-filtersheet] .s-filters__title {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-block-end: .5rem;
  font-size: .68rem;
  letter-spacing: .14em;
}
.s-listing .s-filters__title::before,
.s-sheet[data-filtersheet] .s-filters__title::before {
  content: "";
  flex: none;
  inline-size: .95rem;
  block-size: 2px;
  border-radius: 2px;
  background: currentColor;
}

/*
 * Ticking a box.
 *
 * The row was 26px of text with a hover that changed one word to red. It is
 * now a tile the width of the panel: it lights under the pointer, and once
 * ticked it holds a red ground, a red edge and heavier type — three signals,
 * so the state is never carried by hue alone.
 *
 * The tile is drawn by a pseudo-element rather than by padding because the
 * Category rows carry an inline `padding-inline-start` for their depth, and
 * an inline declaration wins. This way the indent stays exactly what the
 * server asked for and the tile still bleeds to the panel's edge.
 */
.s-listing .s-filters .s-check,
.s-sheet[data-filtersheet] .s-check {
  position: relative;
  isolation: isolate;
  gap: .55rem;
  min-block-size: 2.35rem;
  padding-block: .3rem;
  padding-inline-end: .35rem;
  border-radius: 10px;
  font-size: var(--s-text-sm);
  color: var(--s-ink-soft);
  transition: color .18s var(--sp-ease);
}
.s-listing .s-filters .s-check::before,
.s-sheet[data-filtersheet] .s-check::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset-block: 0;
  inset-inline: -.6rem;
  border-radius: 10px;
  background: transparent;
  transition: background .18s var(--sp-ease);
}
@media (hover: hover) {
  .s-listing .s-filters .s-check:hover::before,
  .s-sheet[data-filtersheet] .s-check:hover::before { background: var(--cp-tint); }
}
.s-listing .s-filters .s-check:hover,
.s-sheet[data-filtersheet] .s-check:hover { color: var(--s-brand-800); }

/* Ticked. */
.s-listing .s-filters .s-check:has(input:checked),
.s-sheet[data-filtersheet] .s-check:has(input:checked) {
  color: var(--s-brand-800);
  font-weight: 700;
}
.s-listing .s-filters .s-check:has(input:checked)::before,
.s-sheet[data-filtersheet] .s-check:has(input:checked)::before {
  background: var(--cp-tint-red);
  box-shadow: inset 2px 0 0 var(--s-accent-500);
}

/* The box itself answers the press. */
.s-listing .s-filters .s-check input[type="checkbox"],
.s-sheet[data-filtersheet] .s-check input[type="checkbox"] {
  flex: none;
  border-radius: 4px;
  transition: scale .16s var(--sp-ease);
}
.s-listing .s-filters .s-check:active input[type="checkbox"],
.s-sheet[data-filtersheet] .s-check:active input[type="checkbox"] { scale: .88; }
.s-listing .s-filters .s-check input:focus-visible,
.s-sheet[data-filtersheet] .s-check input:focus-visible {
  outline: 2px solid var(--s-accent-500);
  outline-offset: 3px;
}

/* The facet count: a figure in a column of figures, so it gets a pill and
   tabular numerals and stops being mistaken for part of the label. */
.s-listing .s-filters .s-check__count,
.s-sheet[data-filtersheet] .s-check__count {
  flex: none;
  min-inline-size: 1.45rem;
  padding: .1rem .35rem;
  border-radius: 999px;
  background: var(--sp-tile);
  color: var(--s-muted);
  font-weight: 600;
  font-variant-numeric: tabular-nums lining-nums;
  text-align: center;
  transition: background .18s var(--sp-ease), color .18s var(--sp-ease);
}
.s-listing .s-filters .s-check:has(input:checked) .s-check__count,
.s-sheet[data-filtersheet] .s-check:has(input:checked) .s-check__count {
  background: var(--s-accent-500);
  color: #fff;
}

/*
 * The price range.
 *
 * Two number boxes and a Go button on one 224px row left the boxes 71px wide,
 * and Chrome's spinner overlaid the placeholder — the panel was literally
 * showing "Ma" with an arrow through it. The spinners come off (the pair is
 * a range, not a stepper) and Go takes its own full-width row.
 */
.s-listing .s-filters__range,
.s-sheet[data-filtersheet] .s-filters__range {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: .45rem;
  margin-block-start: .65rem;
}
.s-listing .s-filters__range > span,
.s-sheet[data-filtersheet] .s-filters__range > span {
  color: var(--s-muted);
  text-align: center;
}
.s-listing .s-filters__range .s-input,
.s-sheet[data-filtersheet] .s-filters__range .s-input {
  min-inline-size: 0;
  min-block-size: 2.6rem;
  padding-inline: .75rem;
  border-radius: 10px;
  font-size: var(--s-text-sm);
  font-variant-numeric: tabular-nums lining-nums;
  appearance: textfield;
}
.s-listing .s-filters__range .s-input::-webkit-outer-spin-button,
.s-listing .s-filters__range .s-input::-webkit-inner-spin-button,
.s-sheet[data-filtersheet] .s-filters__range .s-input::-webkit-outer-spin-button,
.s-sheet[data-filtersheet] .s-filters__range .s-input::-webkit-inner-spin-button {
  appearance: none;
  margin: 0;
}
.s-listing .s-filters__range .s-btn,
.s-sheet[data-filtersheet] .s-filters__range .s-btn {
  grid-column: 1 / -1;
  min-block-size: 2.6rem;
  justify-content: center;
}

/*
 * The "Show results" button inside the panel.
 *
 * The template and the script both say it exists only for the no-JavaScript
 * case — with the script running, every tick has already applied itself, and
 * the phone sheet carries "Show N results" in its own footer besides. Nothing
 * was actually hiding it, so both panels shipped a large red button that
 * repeated a decision the shopper had already made.
 *
 * `scripting: enabled` resolves at parse time, so there is no flash of a
 * button that then vanishes; the `js-on` class filters.js sets is the
 * fallback for browsers without the media feature. With scripts off, neither
 * matches and the button is exactly where it has to be.
 */
@media (scripting: enabled) {
  .s-listing .s-filters__submit,
  .s-sheet[data-filtersheet] .s-filters__submit { display: none; }
}
html.js-on .s-listing .s-filters__submit,
html.js-on .s-sheet[data-filtersheet] .s-filters__submit { display: none; }

/* ==========================================================================
   5. The applied-filter chips
   ========================================================================== */

/*
 * One set of chips, not two.
 *
 * `initListing()` *clones* the chip row into the filter panel and the phone
 * sheet and leaves the original where the server rendered it — inside the
 * results, above the grid. So a filtered listing showed the same "In stock
 * only ×" twice: once at the top of the panel, once floating over the first
 * row of cards. The template's own note says the chips are "not drawn out on
 * the page any more", so the copy that is not supposed to be there is the one
 * that goes.
 *
 * Same two-step gate as the submit button, and for the same reason: with
 * scripts off nothing clones anything, neither rule matches, and the chips
 * stay exactly where they read perfectly well — above the grid they describe.
 */
@media (scripting: enabled) {
  .s-listing [data-results] > [data-chips] { display: none; }
}
html.js-on .s-listing [data-results] > [data-chips] { display: none; }

.s-listing .s-chip,
.s-sheet[data-filtersheet] .s-chip {
  padding: .3rem .3rem .3rem .7rem;
  border: 1px solid rgb(13 36 84 / .12);
  background: color-mix(in srgb, var(--s-brand-100) 55%, #fff);
  box-shadow: 0 1px 2px rgb(13 36 84 / .05);
  animation: cp-chip-in .3s var(--sp-ease) both;
  transition: border-color .18s var(--sp-ease), background .18s var(--sp-ease);
}
@keyframes cp-chip-in {
  from { opacity: 0; scale: .9; }
  to { opacity: 1; scale: 1; }
}
.s-listing .s-chip:hover,
.s-sheet[data-filtersheet] .s-chip:hover { border-color: rgb(196 38 12 / .35); }

.s-listing .s-chip__x,
.s-sheet[data-filtersheet] .s-chip__x {
  position: relative;
  inline-size: 1.4rem;
  block-size: 1.4rem;
  background: rgb(13 36 84 / .07);
  transition: background .18s var(--sp-ease), color .18s var(--sp-ease), scale .18s var(--sp-ease);
}
/* The visible circle stays small; the thing a thumb has to hit does not. */
.s-listing .s-chip__x::after,
.s-sheet[data-filtersheet] .s-chip__x::after {
  content: "";
  position: absolute;
  inset: -.55rem;
  border-radius: 50%;
}
.s-listing .s-chip__x:hover,
.s-sheet[data-filtersheet] .s-chip__x:hover {
  background: var(--s-accent-500);
  color: #fff;
  scale: 1.08;
}
.s-listing .s-chip__x:focus-visible,
.s-sheet[data-filtersheet] .s-chip__x:focus-visible {
  outline: 2px solid var(--s-accent-500);
  outline-offset: 2px;
}

/* ==========================================================================
   6. The grid

   The cards are the same cards. What changes is the rhythm: they arrive in
   sequence rather than all at once, they answer a pointer with depth and a
   tap with a press, and nothing a finger has to find is under 44px.
   ========================================================================== */

.s-listing .s-grid {
  align-items: stretch;
  margin-block-start: .15rem;
}

/*
 * The arrival.
 *
 * A keyframe, not a transition on a JS-set class — so a page with scripts
 * blocked still paints every card, and a page whose script has not run yet
 * never shows a blank grid. The delay ladder stops at the tenth card: past
 * the fold nobody is watching, and a shopper who scrolls fast should not be
 * chasing cards that are still arriving.
 */
.s-listing .s-grid > .sp-card {
  animation: cp-card-in .44s var(--sp-ease) both;
}
@keyframes cp-card-in {
  from { opacity: 0; translate: 0 12px; }
  to { opacity: 1; translate: 0 0; }
}
.s-listing .s-grid > .sp-card:nth-child(1) { animation-delay: .02s; }
.s-listing .s-grid > .sp-card:nth-child(2) { animation-delay: .06s; }
.s-listing .s-grid > .sp-card:nth-child(3) { animation-delay: .10s; }
.s-listing .s-grid > .sp-card:nth-child(4) { animation-delay: .14s; }
.s-listing .s-grid > .sp-card:nth-child(5) { animation-delay: .17s; }
.s-listing .s-grid > .sp-card:nth-child(6) { animation-delay: .20s; }
.s-listing .s-grid > .sp-card:nth-child(7) { animation-delay: .23s; }
.s-listing .s-grid > .sp-card:nth-child(8) { animation-delay: .26s; }
.s-listing .s-grid > .sp-card:nth-child(9) { animation-delay: .28s; }
.s-listing .s-grid > .sp-card:nth-child(n + 10) { animation-delay: .30s; }

/* Surfaces: the home page's shadow language, so a card is the same object
   wherever it is shown. */
.s-listing .s-grid > .sp-card {
  border-color: var(--cp-line-soft);
  box-shadow: var(--cp-lift-1), var(--cp-edge);
}
@media (hover: hover) {
  .s-listing .s-grid > .sp-card:hover {
    box-shadow: var(--cp-lift-2), var(--cp-edge);
    border-color: rgb(13 36 84 / .14);
    translate: 0 -5px;
  }
}
/* A tap is a press. Touch has no hover to borrow, so this is the only answer
   a phone gets, and it has to be immediate. */
.s-listing .s-grid > .sp-card:active {
  translate: 0 -1px;
  scale: .988;
  transition-duration: .09s;
}
/* Keyboard: the whole card is the thing that has focus, so the whole card
   shows it. */
.s-listing .s-grid > .sp-card:focus-within {
  border-color: rgb(196 38 12 / .35);
  box-shadow: var(--cp-lift-2), var(--cp-edge), 0 0 0 3px rgb(196 38 12 / .16);
}

/* Figures in a column of figures. */
.s-listing .s-grid .sp-card__price strong,
.s-listing .s-grid .sp-card__price s,
.s-listing .s-grid .sp-card__price em {
  font-variant-numeric: tabular-nums lining-nums;
}
.s-listing .s-grid .sp-card__price strong { letter-spacing: -.015em; }
.s-listing .s-grid .sp-card__name { text-wrap: pretty; }

/* Thumb-sized. The card's own call to action was 38px. */
.s-listing .s-grid .sp-card__btn { block-size: 2.75rem; }

/*
 * One card in a row of eleven was arriving as a solid red block.
 *
 * `product-card.blade.php` renders an `<a>` — "Choose options" — for a product
 * with variants and a `<button>` — "Add to cart" — for one without, and
 * snap.css:530 fills every `button.sp-card__btn` with the accent. On a home
 * rail, where a card stands more or less alone, that is a call to action. In a
 * grid it is not: measured on /c/drinkware, ten cards carry the house tile and
 * the eleventh, "Classic Straw Glass", carries a red fill, so the one product
 * that is simplest to buy is the one that looks like an error in the row.
 *
 * The fix is weight, not colour. Same tile, same 44px, so a row scans as a
 * row — and the word stays accent red, which is the one thing that genuinely
 * differs, at 7.8:1 on the tile. The fill is not lost; it comes back on
 * hover, where it is an answer to the shopper rather than a flare in the page.
 * Scoped to the listing grid, so the home rails keep the face home-premium
 * gave them.
 */
.s-listing .s-grid button.sp-card__btn:not(.is-muted) {
  background: var(--sp-tile);
  color: var(--s-accent-500);
  box-shadow: none;
}
@media (hover: hover) {
  .s-listing .s-grid button.sp-card__btn:not(.is-muted):hover {
    background: var(--s-accent-500);
    color: #fff;
  }
}
/* Touch has no hover to borrow: the press itself is the answer. */
.s-listing .s-grid button.sp-card__btn:not(.is-muted):active {
  background: var(--s-accent-500);
  color: #fff;
}

/* The heart and the quick-view eye keep their 34px circles — three of them
   at 44px on a 172px card would be a card made of buttons — but the area a
   thumb actually has to hit is grown to 44px underneath them. Phone and
   tablet only: at desktop widths quick view is a full-width bar already. */
@media (max-width: 61.99rem) {
  .s-listing .s-grid .sp-card__wish,
  .s-listing .s-grid .sp-card__quick { position: absolute; }
  .s-listing .s-grid .sp-card__wish::after,
  .s-listing .s-grid .sp-card__quick::after {
    content: "";
    position: absolute;
    inset: -.35rem;
    border-radius: 50%;
  }
}

.s-listing .s-grid a:focus-visible,
.s-listing .s-grid button:focus-visible {
  outline: 2px solid var(--s-accent-500);
  outline-offset: 3px;
  border-radius: 8px;
}

/* ==========================================================================
   7. Filtering, while it happens

   filters.js puts `is-loading` on the results while it fetches. Nothing was
   reading it, so a tick on a slow connection looked like a tick that had not
   registered. A hairline that sweeps the top of the column and results that
   step back say the shop heard it — both transform and opacity, nothing that
   reflows.
   ========================================================================== */

.s-listing [data-results] { position: relative; }

.s-listing [data-results] > * {
  transition: opacity .22s var(--sp-ease);
}
.s-listing [data-results].is-loading > * {
  opacity: .42;
  pointer-events: none;
}

/*
 * The hairline is fixed to the top of the window rather than to the top of
 * the results column. Measured: tick a box halfway down /shop and the top of
 * that column is 600px above the fold — a progress bar nobody can see is not
 * a progress bar. The dimmed grid is the main answer; this is the one that
 * works when the grid is only in the corner of the eye.
 */
.s-listing [data-results]::before {
  content: "";
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: calc(var(--s-z-header, 100) + 5);
  block-size: 3px;
  background: linear-gradient(90deg, var(--s-accent-500), var(--s-cool-500), var(--s-accent-500));
  opacity: 0;
  scale: 0 1;
  transform-origin: left center;
  pointer-events: none;
}
.s-listing [data-results].is-loading::before {
  opacity: 1;
  animation: cp-progress 1.1s var(--sp-ease) infinite;
}
@keyframes cp-progress {
  0% { scale: 0 1; transform-origin: left center; }
  50% { scale: 1 1; transform-origin: left center; }
  51% { scale: 1 1; transform-origin: right center; }
  100% { scale: 0 1; transform-origin: right center; }
}

/* ==========================================================================
   8. The pager

   40px targets, a "Previous" that looked like a broken link, and no line
   between the last row of cards and the numbers. It is now a foot to the
   grid rather than three pills adrift under it.
   ========================================================================== */

.s-listing .s-pager {
  gap: .4rem;
  margin-block-start: var(--s-8);
  padding-block-start: var(--s-6);
  border-block-start: 1px solid var(--cp-line-soft);
}
.s-listing .s-pager a,
.s-listing .s-pager span {
  min-inline-size: 2.75rem;
  block-size: 2.75rem;
  padding-inline: .8rem;
  border-radius: 999px;
  border: 1px solid var(--cp-line-soft);
  background: #fff;
  color: var(--s-brand-800);
  font-weight: 700;
  font-variant-numeric: tabular-nums lining-nums;
  box-shadow: 0 1px 2px rgb(13 36 84 / .05);
  transition: translate .2s var(--sp-ease), box-shadow .2s var(--sp-ease), border-color .2s var(--sp-ease), background .2s;
}
@media (hover: hover) {
  .s-listing .s-pager a:hover {
    translate: 0 -2px;
    border-color: rgb(13 36 84 / .28);
    box-shadow: var(--cp-lift-1);
  }
}
.s-listing .s-pager a:active { translate: 0 0; scale: .96; }

/* The page you are on: filled, lifted and heavier — never the fill alone. */
.s-listing .s-pager [aria-current],
.s-listing .s-pager .is-current {
  background: var(--s-brand-800);
  border-color: var(--s-brand-800);
  color: #fff;
  font-weight: 800;
  box-shadow: 0 8px 18px -10px rgb(13 36 84 / .85), inset 0 1px 0 rgb(255 255 255 / .18);
}

/* A step you cannot take reads as recessed rather than as a live control. */
.s-listing .s-pager .is-disabled {
  opacity: .45;
  background: var(--sp-tile);
  border-color: transparent;
  box-shadow: none;
  color: var(--s-muted);
}

.s-listing .s-pager a:focus-visible {
  outline: 2px solid var(--s-accent-500);
  outline-offset: 3px;
}

@media (max-width: 30rem) {
  .s-listing .s-pager { gap: .3rem; }
  .s-listing .s-pager a,
  .s-listing .s-pager span { padding-inline: .55rem; font-size: var(--s-text-sm); }
}

/* ==========================================================================
   9. Nothing matched

   A 250px grey rectangle with three lines centred in it. Same three lines,
   but the panel now has a light in it, the mark has a ring, and the way out
   is a control instead of an underlined phrase.
   ========================================================================== */

.s-listing .s-empty {
  position: relative;
  overflow: hidden;
  padding: clamp(2.75rem, 6vw, 4rem) 1.5rem;
  border: 1px solid var(--cp-line-soft);
  background: linear-gradient(180deg, #fff, var(--sp-tile) 70%);
  box-shadow: var(--cp-lift-1), var(--cp-edge);
}
.s-listing .s-empty::before {
  content: "";
  position: absolute;
  inset-block-start: -35%;
  inset-inline-start: 50%;
  translate: -50% 0;
  inline-size: min(30rem, 90%);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(196 38 12 / .07), transparent 70%);
  pointer-events: none;
}
.s-listing .s-empty > * { position: relative; }

.s-listing .s-empty__mark {
  position: relative;
  inline-size: 4.5rem;
  block-size: 4.5rem;
  background: #fff;
  color: var(--s-accent-500);
  box-shadow: var(--cp-lift-2), var(--cp-edge);
}
.s-listing .s-empty__mark svg { inline-size: 2rem; block-size: 2rem; }
/* One quiet ring, breathing. Transform and opacity only. */
.s-listing .s-empty__mark::after {
  content: "";
  position: absolute;
  inset: -.55rem;
  border-radius: 50%;
  border: 1px solid rgb(196 38 12 / .22);
  animation: cp-ring 3.4s var(--sp-ease) infinite;
}
@keyframes cp-ring {
  0% { scale: .92; opacity: .9; }
  70% { scale: 1.14; opacity: 0; }
  100% { scale: 1.14; opacity: 0; }
}

.s-listing .s-empty__title {
  font-size: clamp(1.25rem, 1.05rem + .8vw, 1.6rem);
  letter-spacing: -.02em;
  margin-block-start: .35rem;
}
.s-listing .s-empty__body {
  font-size: var(--s-text-md);
  line-height: 1.6;
  max-inline-size: 34rem;
  text-wrap: pretty;
}
/* The link keeps its underline — it is set inline on the element and that is
   fine, an underlined link in a sentence is the right thing. It simply stops
   being grey-blue body text and starts being the shop's red, with a hit box a
   thumb can find. */
.s-listing .s-empty__body a {
  display: inline-block;
  padding-block: .3rem;
  color: var(--s-accent-500);
  font-weight: 700;
  text-underline-offset: 3px;
  text-decoration-thickness: 2px;
}
.s-listing .s-empty__body a:hover { color: var(--s-accent-600); }
.s-listing .s-empty__body a:focus-visible { outline: 2px solid var(--s-accent-500); outline-offset: 3px; border-radius: 4px; }

/* ==========================================================================
   10. The phone filter sheet
   ========================================================================== */

.s-sheet[data-filtersheet] {
  box-shadow: 0 -20px 60px -20px rgb(6 16 49 / .45), 0 -1px 0 rgb(13 36 84 / .08);
}

/* A grip, because a panel that comes up from the bottom of a phone should
   look like one you can push back down. */
.s-sheet[data-filtersheet] .s-sheet__head {
  position: relative;
  padding-block: 1.15rem .8rem;
  border-block-end: 1px solid var(--cp-line-soft);
}
.s-sheet[data-filtersheet] .s-sheet__head::before {
  content: "";
  position: absolute;
  inset-block-start: .45rem;
  inset-inline-start: 50%;
  translate: -50% 0;
  inline-size: 2.5rem;
  block-size: 4px;
  border-radius: 999px;
  background: rgb(13 36 84 / .16);
}
.s-sheet[data-filtersheet] .s-icon-btn {
  min-inline-size: 2.75rem;
  min-block-size: 2.75rem;
}

.s-sheet[data-filtersheet] .s-sheet__body {
  padding-inline: 1.15rem;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgb(13 36 84 / .22) transparent;
}
/* snap-inner gives every group 1.1rem of its own inline padding, which inside
   the sheet's 1.15rem body padding indented every checkbox by 36px. */
.s-sheet[data-filtersheet] .s-filters__group { padding-inline: 0; }
.s-sheet[data-filtersheet] .s-filterform { padding-inline: 0; padding-block-end: .5rem; }

/* Rows a thumb can hit, and chips likewise. */
.s-sheet[data-filtersheet] .s-check { min-block-size: 2.75rem; font-size: var(--s-text-md); }
.s-sheet[data-filtersheet] .s-chip { min-block-size: 2.4rem; padding-inline-start: .85rem; font-size: var(--s-text-sm); }
.s-sheet[data-filtersheet] .s-chip__x { inline-size: 1.7rem; block-size: 1.7rem; }
.s-sheet[data-filtersheet] .s-sheet__applied { padding-block-end: .35rem; }

.s-sheet[data-filtersheet] .s-sheet__foot {
  background: linear-gradient(180deg, #fff, var(--sp-tile));
  border-block-start: 1px solid var(--cp-line-soft);
  box-shadow: 0 -8px 20px -14px rgb(13 36 84 / .35);
}
.s-sheet[data-filtersheet] .s-sheet__foot .s-btn {
  min-block-size: 2.85rem;
  justify-content: center;
}
.s-sheet[data-filtersheet] [data-sheet-count] {
  font-variant-numeric: tabular-nums lining-nums;
}

/* ==========================================================================
   11. Reduced motion

   Everything this sheet added, cancelled — the banner settle, the drawn
   rules, the card ladder, the chip pop, the badge pop, the breathing ring,
   the progress hairline and every hover transition. Nothing here is load
   bearing: with all of it off, the listing is the same listing, still.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .s-head--pic .s-head__pic,
  .s-head--pic .s-head__title::after,
  .s-wrap:has(+ .s-wrap .s-listing) .s-head__title::after,
  .s-toolbar__filter .s-filterbadge,
  .s-listing .s-chip,
  .s-listing .s-grid > .sp-card,
  .s-listing .s-empty__mark::after,
  .s-listing [data-results].is-loading::before,
  .s-sheet[data-filtersheet] .s-chip {
    animation: none;
  }

  /* The scroll-linked fade at the foot of the filter panel goes with them.
     It is a hint, not a mechanism: the sticky header and the panel's own
     scrollbar still say there is more below it. */
  .s-listing .s-filters::after { animation: none; opacity: 0; }

  .s-listing .s-grid > .sp-card,
  .s-listing .s-filters .s-check,
  .s-listing .s-filters .s-check::before,
  .s-listing .s-filters .s-check input[type="checkbox"],
  .s-listing .s-filters .s-check__count,
  .s-listing .s-filters__clear,
  .s-listing .s-chip,
  .s-listing .s-chip__x,
  .s-listing .s-pager a,
  .s-listing .s-pager span,
  .s-listing [data-results] > *,
  .s-toolbar .s-sort,
  .s-sheet[data-filtersheet] .s-check,
  .s-sheet[data-filtersheet] .s-check::before,
  .s-sheet[data-filtersheet] .s-chip,
  .s-sheet[data-filtersheet] .s-chip__x {
    transition: none;
  }

  /* The rules are simply drawn, at full width, from the first frame. */
  .s-head--pic .s-head__title::after,
  .s-wrap:has(+ .s-wrap .s-listing) .s-head__title::after { scale: 1 1; opacity: 1; }

  .s-listing .s-grid > .sp-card:hover,
  .s-listing .s-grid > .sp-card:active,
  .s-listing .s-pager a:hover,
  .s-listing .s-pager a:active { translate: 0 0; scale: 1; }

  /* Filtering still has to say it is working. Opacity only. */
  .s-listing [data-results].is-loading::before { opacity: 1; scale: 1 1; }
}
