/* NightGirl · ng-polish.css · build 2.14.1 ─────────────────────────────
   Is this file actually live? Two ways to tell without guessing:
     · open the file in a browser and search for "SECOND PASS"
     · in the console on any page:
         getComputedStyle(document.documentElement)
           .getPropertyValue("--ng-polish-version")
   ──────────────────────────────────────────────────────────────────── */
:root{ --ng-polish-version: "2.14.1"; }

/* ═══════════════════════════════════════════════════════════════════════
   NightGirl — Storefront Polish Layer
   Loaded AFTER storefront.css (see index.html). Every rule here is a
   deliberate design override; the compiled bundle is left untouched so a
   future rebuild never silently drops these,and reverting is one line.

   Pass 1 — Shop index (home)
     01  Unified hairline border  rgb(45 44 45)
     02  Solid black header (default + shrunk)
     04  Sticky category chips — purple at rest,glow on hover
     06  Colorway swatches — hover interaction
     07  Compact product cards — 200px,legible overlay
   ═══════════════════════════════════════════════════════════════════════ */

:root{
  --ng-line: rgb(45 44 45);
  --ng-violet: #8a2be2;
  --ng-orchid: #ba55d3;
}


/* ─── 01 · Unified hairline border ─────────────────────────────────────── */

header,header.header-shrunk{
  border-bottom-color: var(--ng-line);
}

.container{
  border-left-color: var(--ng-line);
  border-right-color: var(--ng-line);
}

/* These two used a gradient border-image; it must be cleared for a solid
   colour to show. Mobile keeps its existing `border:none`. */
.compact-product-category,.new-arrivals-section{
  border-image: none;
  border-color: var(--ng-line);
}

/* All magazine cards share the line (the Inner Circle newsletter tile sits
   in the same row,so it follows for consistency). Hover states below keep
   the purple response. */
.mag-card,.mag-newsletter{
  border-color: var(--ng-line);
}
.mag-card:hover{
  border-color: rgba(138, 43, 226, .45);
}

.compact-product-card{
  border-color: var(--ng-line);
}
.compact-product-card:hover{
  border-color: rgba(138, 43, 226, .5);
}
@media (max-width: 768px) {
  .compact-product-card,.compact-product-card:hover{ border-color: var(--ng-line); }
}


/* ─── 02 · Solid black header ──────────────────────────────────────────── */
/* .sticky-search-bar is intentionally NOT touched — it keeps its glass. */

header,header.header-shrunk{
  background: #000000;
}
@media (max-width: 768px) {
  header,header.header-shrunk{ background: #000000; }
}


/* ─── 04 · Sticky category chips ───────────────────────────────────────── */
/* Rest = the old hover fill (translucent violet),minus the glow.
   Hover = same fill + the glow. */

.sticky-cat-link{
  color: #fff;
  background: rgba(138, 43, 226, .26);
  border-color: rgba(138, 43, 226, .45);
  box-shadow: 0 0 0 0 rgba(138, 43, 226, 0), 0 0 0 rgba(138, 43, 226, 0), 0 0 0 rgba(138, 43, 226, 0);
}
.sticky-cat-link i{ color: #fff; }

.sticky-cat-link:hover,.sticky-cat-link:focus-visible{
  color: #fff;
  background: rgba(138, 43, 226, .26);
  border-color: rgba(138, 43, 226, .45);
  box-shadow: 0 0 0 3px rgba(138, 43, 226, .1), 0 0 18px rgba(138, 43, 226, .45), 0 4px 16px rgba(138, 43, 226, .22);
  outline: none;
}


/* ─── 06 · Colorway swatches — hover interaction ───────────────────────── */
/* Hovering the group unveils a thin violet capsule from the centre and
   eases the swatches apart. Hovering a single swatch puts an offset orchid
   ring around it,sweeps a light sheen across it,and softly recedes its
   siblings — focus,not size. */

.arrival-colors,.compact-colorway-options{
  position: relative;
  isolation: isolate;
  width: -moz-fit-content;
  width: fit-content;
  height: auto;
  padding: 3px 7px;
  border-radius: 999px;
  transition: gap .5s var(--ease-luxe, cubic-bezier(.19, 1, .22, 1));
}
.arrival-colors{
  margin: -3px auto 3px;          /* padding offset → zero layout shift */
}

.arrival-colors::before,.compact-colorway-options::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  border: 1px solid rgba(186, 85, 211, .32);
  background:
    radial-gradient(120% 140% at 50% 50%, rgba(138, 43, 226, .22), rgba(138, 43, 226, .06) 70%, transparent),
    rgba(8, 6, 12, .55);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  clip-path: inset(0 50% 0 50% round 999px);
  opacity: 0;
  transition:
    clip-path .6s var(--ease-luxe, cubic-bezier(.19, 1, .22, 1)),
    opacity .35s ease;
  pointer-events: none;
}

.arrival-colors:hover,.compact-colorway-options:hover{ gap: 7px; }

.arrival-colors:hover::before,.compact-colorway-options:hover::before{
  clip-path: inset(0 0 0 0 round 999px);
  opacity: 1;
}

.arrival-colors .color-dot,.compact-colorway-options .compact-color-option{
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
  transition:
    box-shadow .5s var(--ease-luxe, cubic-bezier(.19, 1, .22, 1)),
    opacity .35s ease,
    filter .35s ease,
    border-color .35s ease;
}

/* sheen */
.arrival-colors .color-dot::before,.compact-colorway-options .compact-color-option::before{
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, .75) 50%, transparent 70%);
  transform: translateX(-130%);
  transition: transform 0s;
  pointer-events: none;
}
.arrival-colors .color-dot:hover::before,.compact-colorway-options .compact-color-option:hover::before{
  transform: translateX(130%);
  transition: transform .75s var(--ease-smooth, cubic-bezier(.25, .46, .45, .94));
}

/* siblings recede */
.arrival-colors:hover .color-dot:not(:hover),.compact-colorway-options:hover .compact-color-option:not(:hover){
  opacity: .55;
  filter: saturate(.65) brightness(.85);
}

/* focused swatch — offset ring + halo */
.arrival-colors .color-dot:hover,.compact-colorway-options .compact-color-option:hover{
  border-color: rgba(255, 255, 255, .9);
  box-shadow:
    0 0 0 2px rgba(6, 6, 8, .95),
    0 0 0 3px var(--ng-orchid),
    0 0 12px 2px rgba(186, 85, 211, .55);
}


/* ─── 07 · Compact product cards ───────────────────────────────────────── */

.compact-product-card,.compact-product-card[style]{
  height: 200px !important;
}

/* Progressive blur under the text — the lower part of the photo softens
   into the scrim so type always sits on a calm,dark base. */
.compact-product-image-container::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 58%;
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  -webkit-mask-image: linear-gradient(to top, #000 38%, transparent);
  mask-image: linear-gradient(to top, #000 38%, transparent);
  pointer-events: none;
}

.compact-product-card::after{
  height: 78%;
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, .96) 0%,
    rgba(0, 0, 0, .88) 26%,
    rgba(0, 0, 0, .6) 48%,
    rgba(0, 0, 0, .22) 70%,
    transparent 100%
  );
}

/* Card info grid (compact cards) — see section 09 for the shared
   price / swatch row and section 10 for titles. */
.compact-product-info{
  bottom: 0;
  padding: 0 10px 10px;
}

.compact-product-title{
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .2px;
  line-height: 1.3;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .85);
}

.compact-product-price{
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: .2px;
  color: #c86be6;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}

.compact-product-info .compact-add-to-cart-btn{
  height: 24px;
  margin-top: 2px;
  letter-spacing: 1.2px;
}

@media (max-width: 768px) {
  .compact-product-info{ padding: 0 8px 8px; }
  .compact-product-title{ font-size: 10.5px; }
  .compact-product-price{ font-size: 12px; }
  .compact-product-info .compact-add-to-cart-btn{ height: 22px; }
}


/* ═══════════════════════════════════════════════════════════════════════
   Pass 2 — Shop index (home)
     08  Subcategory headers — no rule underneath
     09  One price / swatch row for every card
     10  Product titles — centred,single line,auto-fitted (ng-fit-titles.js)
     11  New Arrival cards — plain line border,no glow
   ═══════════════════════════════════════════════════════════════════════ */


/* ─── 08 · Subcategory headers ─────────────────────────────────────────── */

.compact-subcategory-header{
  border-bottom: none;
}
@media (min-width: 769px) {
  /* without the rule the old 14px cushion reads as a gap — tighten it */
  .compact-subcategory-header{ padding-bottom: 4px; }
}


/* ─── 09 · Price / swatch row — identical on every card ───────────────── */
/* Row 1: title (full width,centred)
   Row 2: price left · swatches right
          → no swatches: the price centres across the whole row
   Row 3: Claim button (full width) */

.compact-product-info,.arrival-info{
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 6px;
  row-gap: 6px;
  text-align: center;
}

.compact-product-title,.arrival-title{
  grid-column: 1 / -1;
  margin: 0;
}

.compact-product-price,.arrival-price{
  grid-column: 1;
  justify-self: start;
  margin: 0;
  line-height: 1;
}

.compact-product-info .compact-colorway-options,.arrival-info .arrival-colors{
  grid-column: 2;
  justify-self: end;
  margin: -3px -7px -3px 0;      /* capsule padding bleeds, edges stay aligned */
  gap: 5px;
}
.compact-product-info .compact-colorway-options:hover,.arrival-info .arrival-colors:hover{ gap: 7px; }

.compact-product-info .compact-add-to-cart-btn,.arrival-info .arrival-add-btn{
  grid-column: 1 / -1;
}

/* No swatches → hide the empty holder and centre the price.
   (:has() rules are kept separate so an older browser that can't parse
   them still gets the :empty and .ng-no-swatches fallbacks,which
   ng-fit-titles.js sets.) */
.compact-colorway-options:empty,.arrival-colors:empty{ display: none; }
.compact-colorway-options:not(:has(.compact-color-option)){ display: none; }
.arrival-colors:not(:has(.color-dot)){ display: none; }

.ng-no-swatches .compact-product-price,.ng-no-swatches .arrival-price{
  grid-column: 1 / -1;
  justify-self: center;
}
.compact-product-info:not(:has(.compact-color-option)) .compact-product-price{
  grid-column: 1 / -1;
  justify-self: center;
}
.arrival-info:not(:has(.color-dot)) .arrival-price{
  grid-column: 1 / -1;
  justify-self: center;
}

@media (max-width: 768px) {
  .compact-product-info,.arrival-info{ row-gap: 5px; }
  .compact-product-info .compact-colorway-options,.arrival-info .arrival-colors{ gap: 4px; }
}


/* ─── 10 · Product titles — centred,one line,fitted per item ─────────── */
/* ng-fit-titles.js measures each title and scales its font just enough to
   sit on a single line (never below ~70% of the design size). These rules
   are the no-JS baseline: one centred line with an ellipsis. */

.compact-product-title,.arrival-title,.product-card .product-title,.recommendation-title,.compact-rec-title{
  display: block;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
  -webkit-line-clamp: unset;
  line-clamp: unset;
}

/* A fixed line box per card type: a title that has been scaled down stays
   vertically centred in the same row height as its neighbours,so every
   card's title,price row and button line up. */
.compact-product-title{ line-height: 15px; height: 15px; }
.arrival-title{ line-height: 15px; height: 15px; }
@media (max-width: 768px) {
  .compact-product-title,.arrival-title{ line-height: 14px; height: 14px; }
}


/* ─── 11 · New Arrival cards — line border,no glow ────────────────────── */
/* Standard-tier cards drop the neon frame and match the compact cards.
   Special tiers (exclusive,limited,signature,seasonal) keep their
   coloured frame so the tier still reads. */

.arrival-card.tier-standard{
  border-color: var(--ng-line);
  box-shadow: none;
}
.arrival-card.tier-standard:hover{
  border-color: rgba(138, 43, 226, .5);
  box-shadow: 0 8px 28px rgba(138, 43, 226, .06);
}
@media (max-width: 768px) {
  .arrival-card.tier-standard,.arrival-card.tier-standard:hover{
    border-color: var(--ng-line) !important;
    box-shadow: none;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   Pass 3 — Shop page
     12  Tier frames — standard = hairline,special tiers = neon,minimal glow
     13  Shop grid cards — full-bleed image restored,shared info layout
     14  You May Also Like — refresh transition,View More,purple Claim
     15  Sidebar categories — icon + label on one line
     16  Filter bento
     17  Smart "See More"
   Runtime behaviour lives in assets/ng-shop.js.
   ═══════════════════════════════════════════════════════════════════════ */

:root{
  /* Icons: Font Awesome Free (CC BY 4.0), inlined as masks */
  --ng-ic-layers: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M232.5 5.2c14.9-6.9 32.1-6.9 47 0l218.6 101c8.5 3.9 13.9 12.4 13.9 21.8s-5.4 17.9-13.9 21.8l-218.6 101c-14.9 6.9-32.1 6.9-47 0l-218.6-101C5.4 145.8 0 137.3 0 128s5.4-17.9 13.9-21.8zM48.1 218.4l164.3 75.9c27.7 12.8 59.6 12.8 87.3 0L464 218.4l34.1 15.8c8.5 3.9 13.9 12.4 13.9 21.8s-5.4 17.9-13.9 21.8l-218.6 101c-14.9 6.9-32.1 6.9-47 0l-218.6-101C5.4 273.8 0 265.3 0 256s5.4-17.9 13.9-21.8L48 218.4zM13.9 362.2 48 346.4l164.3 75.9c27.7 12.8 59.6 12.8 87.3 0l164.3-75.9 34.1 15.8c8.5 3.9 13.9 12.4 13.9 21.8s-5.4 17.9-13.9 21.8l-218.6 101c-14.9 6.9-32.1 6.9-47 0l-218.5-101C5.4 401.8 0 393.3 0 384s5.4-17.9 13.9-21.8'/%3E%3C/svg%3E");
  --ng-ic-sort: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='m246.6 374.6-96 96c-12.5 12.5-32.8 12.5-45.3 0l-96-96c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L96 370.7V64c0-17.7 14.3-32 32-32s32 14.3 32 32v306.7l41.4-41.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3zM320 480c-17.7 0-32-14.3-32-32s14.3-32 32-32h32c17.7 0 32 14.3 32 32s-14.3 32-32 32zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32h160c17.7 0 32 14.3 32 32s-14.3 32-32 32zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32h224c17.7 0 32 14.3 32 32s-14.3 32-32 32z'/%3E%3C/svg%3E");
  --ng-ic-size: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M209.1 516.2c-18.7 18.7-49.1 18.7-67.9 0L28.1 403.1c-18.7-18.7-18.7-49.1 0-67.9l17-17 73.5 73.5c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9L79 284.3l33.9-33.9 50.9 50.9c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-50.9-50.9 33.9-33.9 73.5 73.5c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-73.5-73.5 33.9-33.9 50.9 50.9c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-50.9-50.9L316.3 47l73.5 73.5c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-73.5-73.5 17-17c18.7-18.7 49.1-18.7 67.9 0l113.4 112.8c18.7 18.7 18.7 49.1 0 67.9z'/%3E%3C/svg%3E");
  --ng-ic-color: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M512 256v2.7c-.4 36.5-33.6 61.3-70.1 61.3H344c-26.5 0-48 21.5-48 48 0 3.4.4 6.7 1 9.9 2.1 10.2 6.5 20 10.8 29.9 6.1 13.8 12.1 27.5 12.1 42 0 31.8-21.6 60.7-53.4 62-3.5.1-7 .2-10.6.2-141.4 0-256-114.6-256-256S114.6 0 256 0s256 114.6 256 256m-384 32a32 32 0 1 0-64 0 32 32 0 1 0 64 0m0-96a32 32 0 1 0 0-64 32 32 0 1 0 0 64m160-96a32 32 0 1 0-64 0 32 32 0 1 0 64 0m96 96a32 32 0 1 0 0-64 32 32 0 1 0 0 64'/%3E%3C/svg%3E");
  --ng-ic-price: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M32.5 96v149.5c0 17 6.7 33.3 18.7 45.3l192 192c25 25 65.5 25 90.5 0l149.5-149.5c25-25 25-65.5 0-90.5l-192-192C279.2 38.7 263 32 246 32H96.5c-35.3 0-64 28.7-64 64m112 16a32 32 0 1 1 0 64 32 32 0 1 1 0-64'/%3E%3C/svg%3E");
  --ng-ic-featured: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M309.5-18.9c-4.1-8-12.4-13.1-21.4-13.1s-17.3 5.1-21.4 13.1l-73.6 144.2-159.9 25.4c-8.9 1.4-16.3 7.7-19.1 16.3s-.5 18 5.8 24.4l114.4 114.5-25.2 159.9c-1.4 8.9 2.3 17.9 9.6 23.2s16.9 6.1 25 2l144.4-73.4L432.4 491c8 4.1 17.7 3.3 25-2s11-14.2 9.6-23.2l-25.3-159.9 114.4-114.5c6.4-6.4 8.6-15.8 5.8-24.4s-10.1-14.9-19.1-16.3L383 125.3z'/%3E%3C/svg%3E");
  --ng-ic-newest: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M338.8-9.9c11.9 8.6 16.3 24.2 10.9 37.8L271.3 224H416c13.5 0 25.5 8.4 30.1 21.1s.7 26.9-9.6 35.5l-288 240c-11.3 9.4-27.4 9.9-39.3 1.3s-16.3-24.2-10.9-37.8L176.7 288H32c-13.5 0-25.5-8.4-30.1-21.1s-.7-26.9 9.6-35.5l288-240c11.3-9.4 27.4-9.9 39.3-1.3'/%3E%3C/svg%3E");
  --ng-ic-best: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M160.5-26.4c9.3-7.8 23-7.5 31.9.9 12.3 11.6 23.3 24.4 33.9 37.4 13.5 16.5 29.7 38.3 45.3 64.2 5.2-6.8 10-12.8 14.2-17.9 1.1-1.3 2.2-2.7 3.3-4.1C297 44.3 306.8 32 319.9 32c13.4 0 22.8 11.9 30.8 22.1q1.95 2.55 3.9 4.8c10.3 12.4 24 30.3 37.7 52.4 27.2 43.9 55.6 106.4 55.6 176.6 0 123.7-100.3 224-224 224S0 411.7 0 288c0-91.1 41.1-170 80.5-225 19.9-27.7 39.7-49.9 54.6-65.1 8.2-8.4 16.5-16.7 25.5-24.2zM225.7 416c25.3 0 47.7-7 68.8-21 42.1-29.4 53.4-88.2 28.1-134.4-4.5-9-16-9.6-22.5-2l-25.2 29.3c-6.6 7.6-18.5 7.4-24.7-.5-17.3-22.1-49.1-62.4-65.3-83-5.4-6.9-15.2-8-21.5-1.9-18.3 17.8-51.5 56.8-51.5 104.3 0 68.6 50.6 109.2 113.7 109.2z'/%3E%3C/svg%3E");
  --ng-ic-low: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='m246.6 374.6-96 96c-12.5 12.5-32.8 12.5-45.3 0l-96-96c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L96 370.7V64c0-17.7 14.3-32 32-32s32 14.3 32 32v306.7l41.4-41.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3zM320 32h32c17.7 0 32 14.3 32 32s-14.3 32-32 32h-32c-17.7 0-32-14.3-32-32s14.3-32 32-32m0 128h96c17.7 0 32 14.3 32 32s-14.3 32-32 32h-96c-17.7 0-32-14.3-32-32s14.3-32 32-32m0 128h160c17.7 0 32 14.3 32 32s-14.3 32-32 32H320c-17.7 0-32-14.3-32-32s14.3-32 32-32m0 128h224c17.7 0 32 14.3 32 32s-14.3 32-32 32H320c-17.7 0-32-14.3-32-32s14.3-32 32-32'/%3E%3C/svg%3E");
  --ng-ic-high: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M150.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-96 96c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L96 141.3V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.3l41.4 41.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-96-96zM320 480h32c17.7 0 32-14.3 32-32s-14.3-32-32-32h-32c-17.7 0-32 14.3-32 32s14.3 32 32 32m0-128h96c17.7 0 32-14.3 32-32s-14.3-32-32-32h-96c-17.7 0-32 14.3-32 32s14.3 32 32 32m0-128h160c17.7 0 32-14.3 32-32s-14.3-32-32-32H320c-17.7 0-32 14.3-32 32s14.3 32 32 32m0-128h224c17.7 0 32-14.3 32-32s-14.3-32-32-32H320c-17.7 0-32 14.3-32 32s14.3 32 32 32'/%3E%3C/svg%3E");
  --ng-ic-rated: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M313 87.2c9.2-7.3 15-18.6 15-31.2 0-22.1-17.9-40-40-40s-40 17.9-40 40c0 12.6 5.9 23.9 15 31.2l-68.4 107.6c-10 15.7-31.3 19.6-46.2 8.4l-59.5-44.5c4.5-6.4 7.1-14.3 7.1-22.7 0-22.1-17.9-40-40-40s-40 17.9-40 40c0 21.8 17.5 39.6 39.2 40l32.6 217.5c4.7 31.3 31.6 54.5 63.3 54.5h273.8c31.7 0 58.6-23.2 63.3-54.5L520.8 176c21.7-.4 39.2-18.2 39.2-40 0-22.1-17.9-40-40-40s-40 17.9-40 40c0 8.4 2.6 16.3 7.1 22.7l-59.4 44.6c-14.9 11.2-36.2 7.3-46.2-8.4z'/%3E%3C/svg%3E");
  --ng-ic-sale: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M192 128a96 96 0 1 0-192 0 96 96 0 1 0 192 0m256 256a96 96 0 1 0-192 0 96 96 0 1 0 192 0m-9.4-297.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-384 384c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0z'/%3E%3C/svg%3E");
}


/* ─── 12 · Tier frames ─────────────────────────────────────────────────── */
/* The old frame stacked a 1px ring,a 14px outer bloom and an inset bloom.
   Now: standard tier is the platform hairline; special tiers get a crisp
   neon line with just enough glow to read as lit — nothing spills. */

.product-card .product-image-container,.product-card:hover .product-image-container{
  border: 0 !important;
  box-shadow: none !important;
}

.recommendation-card,.rec-card,.shop-page .product-card:not(.product-card-ghost){
  border: 1px solid var(--ng-line) !important;
  box-shadow: none;
}
.recommendation-card:hover,.rec-card:hover,.shop-page .product-card:not(.product-card-ghost):hover{
  border-color: rgba(138, 43, 226, .5) !important;
  box-shadow: 0 8px 28px rgba(138, 43, 226, .06) !important;
}

:is(.arrival-card,.recommendation-card,.rec-card,.product-card):is(.tier-signature,.tier-seasonal,.tier-exclusive,.tier-limited):not(.product-card-ghost){
  border: 1px solid color-mix(in srgb, var(--neon) 72%, transparent) !important;
  box-shadow:
    0 0 6px -2px color-mix(in srgb, var(--neon) 55%, transparent),
    inset 0 0 6px -4px color-mix(in srgb, var(--neon) 45%, transparent) !important;
}
:is(.arrival-card,.recommendation-card,.rec-card,.product-card):is(.tier-signature,.tier-seasonal,.tier-exclusive,.tier-limited):not(.product-card-ghost):hover{
  border-color: color-mix(in srgb, var(--neon) 92%, transparent) !important;
  box-shadow:
    0 0 10px -2px color-mix(in srgb, var(--neon) 62%, transparent),
    inset 0 0 8px -4px color-mix(in srgb, var(--neon) 55%, transparent) !important;
}
@media (max-width: 768px) {
  .shop-page .product-card:not(.product-card-ghost):not(.tier-signature,.tier-seasonal,.tier-exclusive,.tier-limited):hover{ border-color: var(--ng-line) !important; box-shadow: none !important; }
}


/* ─── 13 · Shop grid cards ─────────────────────────────────────────────── */
/* The tier-frame rule set `position:relative` on the image container,which
   knocked it out of its `absolute; inset:0` slot — the photo shrank to a
   189px band and the text fell below it. Restored: full-bleed photo,info
   overlaid on a scrim,exactly like the home page cards. */

.shop-page .product-card .product-image-container{
  position: absolute !important;
  inset: 0;
}

/* progressive blur under the text (same as the home compact cards) */
.shop-page .product-card .product-image-container::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 50%;
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  -webkit-mask-image: linear-gradient(to top, #000 38%, transparent);
  mask-image: linear-gradient(to top, #000 38%, transparent);
  pointer-events: none;
  z-index: 1;
}
.shop-page .product-card:not(.product-card-ghost)::after{
  height: 62% !important;
  background: linear-gradient(to top, rgba(0,0,0,.96) 0%, rgba(0,0,0,.86) 28%, rgba(0,0,0,.5) 55%, transparent 100%) !important;
}

/* Tier tag moves to the top-left,stacked under the NEW/SALE badge,so it
   never collides with the title. */
.shop-page .product-card .tier-tag{
  top: 8px;
  bottom: auto;
  left: 8px;
}
.shop-page .product-card .tier-tag:has(+ .product-badge){ top: 30px; }
@media (max-width: 768px) {
  .shop-page .product-card .tier-tag{ top: 10px; left: 10px; }
  .shop-page .product-card .tier-tag:has(+ .product-badge){ top: 34px; }
}

/* Info: title · price-left / swatches-right (price centres alone) · Claim */
.shop-page .product-card .product-details{
  display: grid;
  /* max-content on the price column, so the price is never the thing that
     gives way; the swatch column takes what is left and may shrink to
     nothing. Previously this was `minmax(0,1fr) auto`, which is the reverse
     — the swatches took their full width and the price was squeezed into
     the remainder, where a discounted price wrapped or ran under them. */
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: center;
  column-gap: 8px;
  row-gap: 7px;
  padding: 0 12px 12px !important;
  text-align: center;
}
.shop-page .product-card .product-title{
  grid-column: 1 / -1;
  margin: 0 !important;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .2px;
  color: #fff;
  line-height: 15px !important;
  height: 15px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .85);
}
.shop-page .product-card .product-pricing{
  grid-column: 1;
  justify-self: start;
  margin: 0 !important;
  gap: 5px;
  line-height: 1;
}
.shop-page .product-card .current-price{ color: #c86be6; text-shadow: 0 1px 3px rgba(0, 0, 0, .9); }

/* Price,was-price and discount — one line,one baseline.
   The previous treatment stacked a 8.5px struck figure ABOVE the real
   price with a rose −% chip absolutely positioned at left:calc(100% + 6px).
   Three things were wrong with it: the eye landed on the old price first
   because it had the top line; 8.5px is below reading size; and the chip
   was out of flow in a colour (#ffa3ab) that appears nowhere else in the
   platform,so it hung off the side of the card looking bolted on.

   Now: current price,then the struck original,then the saving — left to
   right,on one baseline,all in flow,all in the brand's own palette. The
   row reserves no extra height,so a card with a discount and a card
   without one are exactly the same shape. */
.shop-page .product-card .product-pricing{
  display: flex !important;
  flex-flow: row nowrap;          /* one line, always — see .product-colors below */
  align-items: baseline;
  justify-content: flex-start;
  column-gap: 6px;
  min-width: 0;
  /* Fixed, so a card with a discount chip and a card without are the same
     height and every price in the row sits on one baseline. This replaces
     the old 28px two-line reserve, which held that space on every card
     whether it was used or not. */
  min-height: 15px;
  line-height: 1;
  white-space: nowrap;
}
.shop-page .product-card .product-details:not(:has(.color-dot)) .product-pricing{
  justify-content: center;
}

.shop-page .product-card .current-price{
  order: 1;
  line-height: 1;
  color: #c86be6;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}

.shop-page .product-card .original-price{
  order: 2;
  position: static;
  display: inline-block;
  height: auto;
  font-size: 10px !important;
  font-weight: 400;
  line-height: 1;
  letter-spacing: .2px;
  color: rgba(255, 255, 255, .42) !important;
  text-decoration: line-through;
  text-decoration-color: rgba(255, 255, 255, .34);
  text-decoration-thickness: 1px;
}

/* The saving,as a quiet brand-toned tag rather than a rose badge. It is
   in normal flow now,so it can never overhang the card edge,and
   text-decoration:none keeps the strike-through off it. */
.shop-page .product-card .original-price[data-ng-off]::after{
  content: "\2212" attr(data-ng-off) "%";
  display: inline-block;
  margin-left: 5px;
  padding: 2px 4px;
  border: 1px solid rgba(186, 85, 211, .34);
  border-radius: 3px;
  background: rgba(138, 43, 226, .13);
  color: #d6a8ef;
  font-size: 8px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: .6px;
  text-decoration: none;
  vertical-align: 1px;
}

.shop-page .product-card .product-colors{ align-self: end; }
.shop-page .product-card .product-colors{
  grid-column: 2;
  justify-self: end;
  margin: -3px -7px -3px 0 !important;
  gap: 5px !important;
  min-width: 0;
  /* width:fit-content further down would otherwise size the swatch row to
     its content and, with justify-self:end, let it hang LEFT out of its
     track and straight under the price. Capping it at the track width is
     what actually keeps the two apart. */
  max-width: 100%;
  overflow: hidden;
}

/* The price column wins the squeeze.
   On a 141px card a discounted price (~83px) and a full swatch row (~70px)
   both want more than the ~139px available,so one of them loses. A wrapped
   or overlapped price breaks the baseline across the whole grid; one fewer
   swatch does not,and the full range is one click away on the product
   page. So when a card carries a discount chip it shows three swatches. */
.shop-page .product-card .product-details:has(.original-price[data-ng-off]) .product-colors > :nth-child(n + 4){
  display: none;
}
.shop-page .product-card .arrival-add-btn{ grid-column: 1 / -1; height: 24px; letter-spacing: 1.2px; }
.shop-page .product-card .product-details:not(:has(.color-dot)) .product-pricing{
  grid-column: 1 / -1;
  justify-self: center;
}
.shop-page .product-card .product-colors:not(:has(.color-dot)){ display: none !important; }
@media (max-width: 768px) {
  .shop-page .product-card .product-details{ padding: 0 10px 10px !important; }
  .shop-page .product-card .product-title{ font-size: 12px !important; line-height: 16px !important; height: 16px; }
}

/* Swatch hover — the same interaction as the home cards */
.shop-page .product-card .product-colors{
  position: relative;
  isolation: isolate;
  width: -moz-fit-content;
  width: fit-content;
  max-width: 100%;
  padding: 3px 7px;
  border-radius: 999px;
  transition: gap .5s var(--ease-luxe, cubic-bezier(.19, 1, .22, 1));
}
.shop-page .product-card .product-colors::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  border: 1px solid rgba(186, 85, 211, .32);
  background: radial-gradient(120% 140% at 50% 50%, rgba(138, 43, 226, .22), rgba(138, 43, 226, .06) 70%, transparent), rgba(8, 6, 12, .55);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  clip-path: inset(0 50% 0 50% round 999px);
  opacity: 0;
  transition: clip-path .6s var(--ease-luxe, cubic-bezier(.19, 1, .22, 1)), opacity .35s ease;
  pointer-events: none;
}
.shop-page .product-card .product-colors:hover{ gap: 7px !important; }
.shop-page .product-card .product-colors:hover::before{ clip-path: inset(0 0 0 0 round 999px); opacity: 1; }
.shop-page .product-card .product-colors .color-dot{
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
  transition: box-shadow .5s var(--ease-luxe, cubic-bezier(.19, 1, .22, 1)), opacity .35s ease, filter .35s ease, border-color .35s ease;
}
.shop-page .product-card .product-colors .color-dot::before{
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, .75) 50%, transparent 70%);
  transform: translateX(-130%);
  pointer-events: none;
}
.shop-page .product-card .product-colors .color-dot:hover::before{
  transform: translateX(130%);
  transition: transform .75s var(--ease-smooth, cubic-bezier(.25, .46, .45, .94));
}
.shop-page .product-card .product-colors:hover .color-dot:not(:hover){ opacity: .55; filter: saturate(.65) brightness(.85); }
.shop-page .product-card .product-colors .color-dot:hover{
  border-color: rgba(255, 255, 255, .9) !important;
  box-shadow: 0 0 0 2px rgba(6, 6, 8, .95), 0 0 0 3px var(--ng-orchid), 0 0 12px 2px rgba(186, 85, 211, .55);
}


/* ─── 14 · You May Also Like ───────────────────────────────────────────── */

/* "View More" — the star is gone (label itself is set in storefront.js) */
.more-like-it-btn i{ display: none !important; }

/* Claim = the platform's Claim button.

   The `.shop-page` scope was the last thing holding the rail's two states
   apart. Section 26 unified the rail's layout across every page that shows
   one, but this rule kept the purple Claim to the shop page, so the same
   card in the same rail offered a purple Claim next to the shop grid and a
   grey one on the product and cart pages — the rail was one component
   everywhere except in the only part of it a shopper is meant to press.
   Its sibling `View` is deliberately quiet; Claim is the action, and it
   looks like the action wherever the rail appears. */
.rec-action-btn.rec-add-cart-btn{
  background: linear-gradient(135deg, rgba(138, 43, 226, .1), rgba(138, 43, 226, .15));
  border: 1px solid rgba(138, 43, 226, .4);
  color: #fff;
}
.rec-action-btn.rec-add-cart-btn:hover{
  background: linear-gradient(135deg, rgba(76, 175, 80, .15), rgba(76, 175, 80, .25));
  border-color: rgba(76, 175, 80, .6);
  color: #fff;
}
.rec-add-cart-btn i{ color: inherit; }

/* Refresh transition,driven by ng-shop.js:
   .ng-rec-out  → content dissolves,card frames stay as skeletons + shimmer
   (React swaps the set while hidden,images decode behind the skeleton)
   .ng-rec-in   → new cards resolve in,staggered left to right */
.recommendations-grid .recommendation-card > *{
  transition: opacity .3s ease, filter .3s ease, transform .3s ease;
}
.recommendations-grid.ng-rec-out .recommendation-card > *{
  opacity: 0;
  filter: blur(6px);
  transform: translateY(6px) scale(.985);
}
.recommendations-grid.ng-rec-out .recommendation-card,.recommendations-grid.ng-rec-in .recommendation-card{
  background:
    linear-gradient(rgba(255,255,255,.055), rgba(255,255,255,.055)) 50% calc(100% - 64px) / 62% 8px no-repeat,
    linear-gradient(rgba(186,85,211,.16), rgba(186,85,211,.16)) 50% calc(100% - 46px) / 32% 8px no-repeat,
    linear-gradient(rgba(255,255,255,.04), rgba(255,255,255,.04)) 14px calc(100% - 14px) / calc(50% - 18px) 22px no-repeat,
    linear-gradient(rgba(138,43,226,.14), rgba(138,43,226,.14)) calc(100% - 14px) calc(100% - 14px) / calc(50% - 18px) 22px no-repeat,
    linear-gradient(180deg, #0f0e12, #09090b);
}
.recommendations-grid .recommendation-card::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(105deg, transparent 30%, rgba(186, 85, 211, .09) 42%, rgba(255, 255, 255, .07) 50%, rgba(186, 85, 211, .09) 58%, transparent 70%);
  background-size: 260% 100%;
  background-position: 130% 0;
  transition: opacity .3s ease;
}
.recommendations-grid.ng-rec-out .recommendation-card::before{
  opacity: 1;
  animation: ngShimmer 1.15s linear infinite;
}
@keyframes ngShimmer { from { background-position: 130% 0; } to { background-position: -130% 0; } }

.recommendations-grid.ng-rec-in .recommendation-card > *{
  animation: ngRecIn .6s cubic-bezier(.19, 1, .22, 1) both;
}
@keyframes ngRecIn {
  from { opacity: 0; filter: blur(8px); transform: translateY(10px) scale(.98); }
  to   { opacity: 1; filter: blur(0);   transform: none; }
}
.recommendations-grid.ng-rec-in .recommendation-card:nth-child(2)  > *{ animation-delay: .06s; }
.recommendations-grid.ng-rec-in .recommendation-card:nth-child(3)  > *{ animation-delay: .12s; }
.recommendations-grid.ng-rec-in .recommendation-card:nth-child(4)  > *{ animation-delay: .18s; }
.recommendations-grid.ng-rec-in .recommendation-card:nth-child(5)  > *{ animation-delay: .24s; }
.recommendations-grid.ng-rec-in .recommendation-card:nth-child(6)  > *{ animation-delay: .30s; }
.recommendations-grid.ng-rec-in .recommendation-card:nth-child(7)  > *{ animation-delay: .36s; }
.recommendations-grid.ng-rec-in .recommendation-card:nth-child(8)  > *{ animation-delay: .42s; }

.rec-refresh-btn[aria-busy="true"]{ pointer-events: none; color: #fff; }
.rec-refresh-btn[aria-busy="true"] i{ animation: ngSpin .8s linear infinite; }
@keyframes ngSpin { to { transform: rotate(360deg); } }


/* ─── 15 · Sidebar ─────────────────────────────────────────────────────── */
/* Header = the section-header band (.category-header): violet-black
   gradient,gradient hairline,travelling light,shimmering title with a
   glowing icon. Rows: every glyph sits in the same 38px chip,labels are
   brighter,rows are separated by space instead of faint rules,and the
   current category gets a violet rail. "All Products" (ng-shop.js) leads
   the list and returns to the unfiltered shop. */

/* The chip tokens live on the page wrapper. The Edit has no such
   wrapper, so they are declared on its sidebar instead - without them
   `background: var(--ng-chip) ...` is invalid at computed-value time and
   the row silently loses its chip. The account dashboard is the same case
   (section 48C), and it is named HERE rather than given a fourth copy of
   the three data-URIs: the chips have to be the same artwork on all four
   rails, and three copies of a 400-byte SVG string is already two more
   chances for them to drift apart than there should be. */
.shop-page,
.dashboard-sidebar,
.blog-sidebar{ --ng-chip: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38'%3E%3Crect x='.5' y='.5' width='37' height='37' rx='5' fill='rgba%28138%2C43%2C226%2C.07%29' stroke='rgba%28138%2C43%2C226%2C.30%29'/%3E%3C/svg%3E"); --ng-chip-hover: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38'%3E%3Crect x='.5' y='.5' width='37' height='37' rx='5' fill='rgba%28138%2C43%2C226%2C.16%29' stroke='rgba%28186%2C85%2C211%2C.65%29'/%3E%3C/svg%3E"); --ng-chip-active: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38'%3E%3Crect x='.5' y='.5' width='37' height='37' rx='5' fill='rgba%28138%2C43%2C226%2C.26%29' stroke='rgb%28186%2C85%2C211%29'/%3E%3C/svg%3E"); }
.product-page{ --ng-chip: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38'%3E%3Crect x='.5' y='.5' width='37' height='37' rx='5' fill='rgba%28138%2C43%2C226%2C.07%29' stroke='rgba%28138%2C43%2C226%2C.30%29'/%3E%3C/svg%3E"); --ng-chip-hover: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38'%3E%3Crect x='.5' y='.5' width='37' height='37' rx='5' fill='rgba%28138%2C43%2C226%2C.16%29' stroke='rgba%28186%2C85%2C211%2C.65%29'/%3E%3C/svg%3E"); --ng-chip-active: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38'%3E%3Crect x='.5' y='.5' width='37' height='37' rx='5' fill='rgba%28138%2C43%2C226%2C.26%29' stroke='rgb%28186%2C85%2C211%29'/%3E%3C/svg%3E"); }

/* header */
.shop-page .sidebar-header,
.product-page .sidebar-header,
/* The Edit's sidebar header is this same component - see section 41F. */
.blog-sidebar .sidebar-header{
  overflow: hidden;
  gap: 7px !important;
  padding: 16px 16px 15px !important;
  background: linear-gradient(135deg, rgba(8, 4, 14, .98), rgba(12, 6, 20, .97), rgba(8, 4, 14, .98)) !important;
  border-bottom: 1px solid;
  border-image: var(--border-blend) 1;
}
.shop-page .sidebar-header::after,
.product-page .sidebar-header::after,
/* The Edit's sidebar header is this same component - see section 41F. */
.blog-sidebar .sidebar-header::after{
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  width: 26%;
  height: 2px;
  border-radius: 1px;
  background: linear-gradient(90deg, transparent, var(--purple, #8a2be2), transparent);
  pointer-events: none;
  animation: ngSidePulse 9s ease-in-out infinite;
}
@keyframes ngSidePulse {
  0%, 100% { left: -26%; opacity: 0; }
  10%      { opacity: 1; }
  50%      { left: 100%; opacity: 1; }
  60%      { opacity: 0; }
}
.shop-page .sidebar-title,
.product-page .sidebar-title,
/* The Edit's sidebar header is this same component - see section 41F. */
.blog-sidebar .sidebar-title{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 13px !important;
  font-weight: 500 !important;
  letter-spacing: 2.6px !important;
  background: linear-gradient(105deg, #ffffffc0, #fff 40%, #e8bfff, #fff 70%, #ffffffb0);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: sectionTitleShim 6s ease-in-out infinite;
}
.shop-page .sidebar-title::before,
.product-page .sidebar-title::before,
/* The Edit's sidebar header is this same component - see section 41F. */
.blog-sidebar .sidebar-title::before{
  content: "";
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  background: var(--purple, #8a2be2);
  -webkit-mask: var(--ng-ic-layers) center / contain no-repeat;
  mask: var(--ng-ic-layers) center / contain no-repeat;
  filter: drop-shadow(0 0 6px rgba(186, 85, 211, .55));
}
.shop-page .sidebar-subtitle,
.product-page .sidebar-subtitle,
/* The Edit's sidebar header is this same component - see section 41F. */
.blog-sidebar .sidebar-subtitle{
  font-size: 9.5px !important;
  font-weight: 300 !important;
  letter-spacing: .9px !important;
  color: rgba(255, 255, 255, .4) !important;
}

/* list */
.shop-page .category-list,
.product-page .category-list,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list{
  --sp-k: .22;
  padding: 12px 10px 16px !important;
}
.shop-page .category-list li + li,
.product-page .category-list li + li,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list li + li{ margin-top: 3px; }
.shop-page .category-list .category-item,
.product-page .category-list .category-item,
.shop-page .category-list li + li .category-item,
.product-page .category-list li + li .category-item,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item,
.blog-sidebar .category-list li + li .category-item{
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
  gap: 13px;
  min-height: 48px;
  padding: 5px 12px 5px 7px !important;
  border: 1px solid transparent !important;
  border-radius: 3px !important;
  background: var(--ng-chip) 7px 50% / 38px 38px no-repeat, linear-gradient(90deg, rgba(255, 255, 255, .018), rgba(255, 255, 255, 0));
  transition: background .35s ease, border-color .35s ease, box-shadow .35s ease, transform .35s var(--ease-luxe, ease);
}
/* glyph centred in the 38px chip — every label starts on the same x */
.shop-page .category-list .category-item::before,
.product-page .category-list .category-item::before,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item::before{
  flex: 0 0 auto;
  margin-inline: calc((38px - var(--cw) * var(--sp-k)) / 2);
}
.shop-page .category-list .category-name,
.product-page .category-list .category-name,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-name{
  text-align: left;
  font-size: 10px !important;
  font-weight: 500 !important;
  letter-spacing: 2px !important;
  color: rgba(255, 255, 255, .82) !important;
}
.shop-page .category-list .category-item:hover,
.product-page .category-list .category-item:hover,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item:hover{
  transform: translateX(2px);
  border-color: rgba(138, 43, 226, .3) !important;
  background: var(--ng-chip-hover) 7px 50% / 38px 38px no-repeat, linear-gradient(90deg, rgba(138, 43, 226, .15), rgba(138, 43, 226, .02));
}
.shop-page .category-list .category-item.active,
.product-page .category-list .category-item.active,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item.active{
  border-color: rgba(186, 85, 211, .45) !important;
  background: var(--ng-chip-active) 7px 50% / 38px 38px no-repeat, linear-gradient(90deg, rgba(138, 43, 226, .28), rgba(138, 43, 226, .04));
  box-shadow: inset 2px 0 0 #ba55d3, 0 6px 18px rgba(138, 43, 226, .12);
}
.shop-page .category-list .category-item.active .category-name,
.product-page .category-list .category-item.active .category-name,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item.active .category-name{ color: #fff !important; }

/* "All Products" — injected by ng-shop.js,separated from the categories */
.category-list > li.ng-cat-all-li{
  margin-bottom: 9px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--ng-line);
}
.category-list > li.ng-cat-all-li + li{ margin-top: 0; }
.category-item.ng-cat-all{ --cx: 0px; --cw: 118px; }
.category-item.ng-cat-all::before{
  background-image: url("ng-cat-all.png") !important;
  background-size: calc(118px * var(--sp-k)) calc(128px * var(--sp-k)) !important;
  background-position: 0 0 !important;
}


/* ─── 16 · Filter bar ──────────────────────────────────────────────────── */
/* One box,two rows:
     SORT   [ segmented track — Featured · Newest · … · Sale ]
     SIZE   [keys] │ COLOR [dots] │ PRICE [chips]
   No per-facet panels,no headers,no icons on the options — just the
   label icons. Colour names appear as a tooltip on hover. */

.shop-page .shop-filter-grid{
  position: relative;
  display: flex !important;
  flex-direction: column;
  gap: 0 !important;
  margin: 4px 0 24px !important;
  padding: 0 !important;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: linear-gradient(135deg, #060607, #0b0b0d);
}
.shop-page .shop-filter-grid::before{
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 32%;
  height: 1px;
  background: linear-gradient(90deg, rgba(138, 43, 226, .8), transparent);
  pointer-events: none;
}
.shop-page .shop-filter-grid > .sfg-row{
  display: flex !important;
  align-items: center;
  flex-wrap: wrap;
  column-gap: 10px;
  row-gap: 10px;
  padding: 10px 14px !important;
  border-bottom: 1px solid var(--ng-line) !important;
}
.shop-page .shop-filter-grid > .sfg-row:last-child{ border-bottom: none !important; }
.shop-page .shop-filter-grid .sfg-divider{
  display: block !important;
  flex: 0 0 auto;
  width: 1px;
  height: 18px;
  margin: 0 3px !important;
  background: var(--ng-line);
}

/* labels */
.shop-page .shop-filter-grid .sfg-label{
  display: inline-flex !important;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  margin: 0 !important;
  padding: 0 !important;
  font-size: 8px !important;
  font-weight: 600 !important;
  letter-spacing: 2px !important;
  color: rgba(255, 255, 255, .7) !important;
}
.shop-page .shop-filter-grid .sfg-label::after{ display: none !important; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(1) > .sfg-label{ min-width: 52px; }
.shop-page .shop-filter-grid .sfg-label::before{
  content: "";
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  background: var(--ng-orchid);
  -webkit-mask: var(--ng-ic, none) center / contain no-repeat;
  mask: var(--ng-ic, none) center / contain no-repeat;
}
.shop-page .shop-filter-grid > .sfg-row:nth-child(1) > .sfg-label{ --ng-ic: var(--ng-ic-sort); }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(1){ --ng-ic: var(--ng-ic-size); }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(4){ --ng-ic: var(--ng-ic-color); }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(7){ --ng-ic: var(--ng-ic-price); }

.shop-page .shop-filter-grid .sfg-options{
  display: flex !important;
  flex-wrap: nowrap !important;
  align-items: center;
  gap: 3px !important;
  flex: 0 0 auto !important;
  min-width: 0;
}

/* shared control */
.shop-page .shop-filter-grid .sfg-btn{
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  height: 26px;
  min-width: 0;
  margin: 0;
  padding: 0 9px !important;
  border: 1px solid var(--ng-line) !important;
  border-radius: 2px !important;
  background: #0e0e10 !important;
  color: rgba(255, 255, 255, .58);
  box-shadow: none;
  font-size: 8.5px;
  font-weight: 500;
  letter-spacing: 1px;
  white-space: nowrap;
  cursor: pointer;
  transition: color .3s ease, border-color .3s ease, background .3s ease, box-shadow .3s ease;
}
.shop-page .shop-filter-grid .sfg-btn:hover{
  color: #fff;
  border-color: rgba(138, 43, 226, .5) !important;
  background: rgba(138, 43, 226, .1) !important;
}
.shop-page .shop-filter-grid .sfg-btn.active{
  color: #fff;
  border-color: rgba(186, 85, 211, .6) !important;
  background: linear-gradient(135deg, rgba(138, 43, 226, .45), rgba(186, 85, 211, .2)) !important;
  box-shadow: 0 4px 14px rgba(138, 43, 226, .22);
}
.shop-page .shop-filter-grid .sfg-btn:focus-visible{ outline: 1px solid var(--ng-orchid); outline-offset: 2px; }

/* SORT — one segmented track that fills the row */
.shop-page .shop-filter-grid > .sfg-row:nth-child(1) > .sfg-options{
  flex: 1 1 auto !important;
  gap: 2px !important;
  padding: 3px;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: #0b0b0d;
}
.shop-page .shop-filter-grid > .sfg-row:nth-child(1) .sfg-btn{
  flex: 1 1 auto;
  height: 28px;
  padding: 0 10px !important;
  border-color: transparent !important;
  background: transparent !important;
  letter-spacing: 1.3px;
}
.shop-page .shop-filter-grid > .sfg-row:nth-child(1) .sfg-btn:hover{ background: rgba(255, 255, 255, .04) !important; border-color: transparent !important; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(1) .sfg-btn.active{
  border-color: rgba(186, 85, 211, .55) !important;
  background: linear-gradient(135deg, rgba(138, 43, 226, .45), rgba(186, 85, 211, .2)) !important;
}

/* SIZE — square keys */
.shop-page .shop-filter-grid .sfg-btn.sfg-btn-sm{
  width: 26px;
  min-width: 26px !important;
  padding: 0 !important;
  font-weight: 600;
  letter-spacing: .4px;
}
.shop-page .shop-filter-grid .sfg-btn.sfg-btn-sm:first-child{ width: auto; padding: 0 8px !important; }

/* COLOUR — dots with a tooltip */
.shop-page .shop-filter-grid .sfg-btn.sfg-color-btn{
  width: 26px;
  padding: 0 !important;
  gap: 0;
  border-radius: 50% !important;
  border-color: transparent !important;
  background: transparent !important;
  box-shadow: none;
  font-size: 0;
  letter-spacing: 0;
}
.shop-page .shop-filter-grid .sfg-color-swatch,.shop-page .shop-filter-grid .sfg-color-btn:first-child::before{
  width: 16px !important;
  height: 16px !important;
  border: 0 !important;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .2), inset 0 -3px 5px rgba(0, 0, 0, .3) !important;
  transition: box-shadow .35s ease;
}
.shop-page .shop-filter-grid .sfg-color-btn:first-child::before{
  content: "";
  display: block;
  flex: 0 0 auto;
  background: conic-gradient(#1a1a1a, #f0f0f0, #c8a888, #e8a0c0, #c85060, #ba55d3, #1a1a1a);
}
.shop-page .shop-filter-grid .sfg-color-btn:hover{ background: transparent !important; border-color: transparent !important; }
.shop-page .shop-filter-grid .sfg-color-btn:hover .sfg-color-swatch,.shop-page .shop-filter-grid .sfg-color-btn:first-child:hover::before{
  box-shadow: 0 0 0 2px #0b0b0d, 0 0 0 3px rgba(186, 85, 211, .75) !important;
}
.shop-page .shop-filter-grid .sfg-color-btn.active{ background: transparent !important; border-color: transparent !important; box-shadow: none; }
.shop-page .shop-filter-grid .sfg-color-btn.active .sfg-color-swatch,.shop-page .shop-filter-grid .sfg-color-btn:first-child.active::before{
  box-shadow: 0 0 0 2px #0b0b0d, 0 0 0 3.5px #ba55d3, 0 0 10px rgba(186, 85, 211, .6) !important;
}
.shop-page .shop-filter-grid .sfg-options:has(.sfg-color-btn.active:not(:first-child)) .sfg-color-btn:first-child.active::before{
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .2), inset 0 -3px 5px rgba(0, 0, 0, .3) !important;
}
.shop-page .shop-filter-grid .sfg-color-btn[data-ng-name]::after{
  content: attr(data-ng-name);
  position: absolute;
  bottom: calc(100% + 7px);
  left: 50%;
  z-index: 20;
  padding: 4px 8px;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: #0b0b0d;
  color: #fff;
  font-size: 8px;
  font-weight: 500;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, 4px);
  transition: opacity .2s ease, transform .2s ease;
}
.shop-page .shop-filter-grid .sfg-color-btn[data-ng-name]:hover::after,.shop-page .shop-filter-grid .sfg-color-btn[data-ng-name]:focus-visible::after{
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Row 2 — three groups side by side,each label sitting above its
   controls (one compact 2-line strip instead of three separate rows). */
.shop-page .shop-filter-grid > .sfg-row:nth-child(2){
  display: grid !important;
  grid-template-columns: auto 1px auto 1px minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: 18px;
  row-gap: 7px;
  align-items: center;
  justify-content: start;
}
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(1){ grid-area: 1 / 1; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(2){ grid-area: 2 / 1; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(3){ grid-area: 1 / 2 / 3 / 3; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(4){ grid-area: 1 / 3; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(5){ grid-area: 2 / 3; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(6){ grid-area: 1 / 4 / 3 / 5; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(7){ grid-area: 1 / 5; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(8){ grid-area: 2 / 5; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) .sfg-divider{ height: 100%; margin: 0 !important; }
.shop-page .shop-filter-grid > .sfg-row:nth-child(2) > .sfg-label{ min-width: 0; }

/* narrower content: Price drops to its own line under Size | Colour */
@media (max-width: 1340px){
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) {
    grid-template-columns: auto 1px minmax(0, 1fr);
    grid-template-rows: auto auto auto auto;
  }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(6) { display: none !important; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(7) { grid-area: 3 / 1 / 4 / 4; margin-top: 4px !important; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(8) { grid-area: 4 / 1 / 5 / 4; }
}
/* phones: every control row scrolls sideways instead of wrapping */
@media (max-width: 768px){
  .shop-page .shop-filter-grid { margin: 4px 12px 20px !important; }
  .shop-page .shop-filter-grid > .sfg-row { padding: 10px 12px !important; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(1) { flex-wrap: nowrap; }
  .shop-page .shop-filter-grid .sfg-options {
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .shop-page .shop-filter-grid .sfg-options::-webkit-scrollbar { display: none; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(1) > .sfg-options { flex: 1 1 auto !important; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(1) .sfg-btn { flex: 0 0 auto; }
  .shop-page .shop-filter-grid .sfg-color-btn[data-ng-name]::after { display: none; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) { grid-template-columns: minmax(0, 1fr); }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) .sfg-divider { display: none !important; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(1) { grid-area: 1 / 1; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(2) { grid-area: 2 / 1; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(4) { grid-area: 3 / 1; margin-top: 4px !important; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(5) { grid-area: 4 / 1; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(7) { grid-area: 5 / 1; margin-top: 4px !important; }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(8) { grid-area: 6 / 1; }
}


/* ─── 17 · Smart "See More" ────────────────────────────────────────────── */
/* ng-shop.js pages the grid three rows at a time and sets
   data-ng-state="more" (purple,with a remaining-count pill) or "end"
   (grey,inert) on the button — recalculated on every filter,category,resize or list change. */

.shop-page .load-more-btn[data-ng-state]{
  transition: color .45s ease, border-color .45s ease, background .45s ease, box-shadow .45s ease !important;
}
.shop-page .load-more-btn[data-ng-state="more"]{
  color: #fff !important;
  border-color: rgba(186, 85, 211, .55) !important;
  background: linear-gradient(135deg, rgba(138, 43, 226, .24), rgba(186, 85, 211, .12)) !important;
  box-shadow: 0 0 0 1px rgba(138, 43, 226, .1);
  cursor: pointer;
}
.shop-page .load-more-btn[data-ng-state="more"]:hover{
  background: linear-gradient(135deg, rgba(138, 43, 226, .36), rgba(186, 85, 211, .18)) !important;
  box-shadow: 0 0 0 1px rgba(138, 43, 226, .18), 0 8px 26px rgba(138, 43, 226, .22);
}
.shop-page .load-more-btn[data-ng-state="more"] i{ color: var(--ng-orchid); }
.shop-page .load-more-btn[data-ng-state="more"][data-ng-remaining]::after{
  content: attr(data-ng-remaining) " more";
  margin-left: 10px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(138, 43, 226, .32);
  border: 1px solid rgba(186, 85, 211, .45);
  font-size: 8.5px;
  letter-spacing: 1px;
}
.shop-page .load-more-btn[data-ng-state="end"],.shop-page .load-more-btn[data-ng-state="end"]:hover{
  color: rgba(255, 255, 255, .26) !important;
  border-color: var(--ng-line) !important;
  background: rgba(255, 255, 255, .015) !important;
  box-shadow: none !important;
  cursor: not-allowed;
}
.shop-page .load-more-btn[data-ng-state="end"] i{ color: rgba(255, 255, 255, .18); }

.products-grid .product-card[data-ng-paged-out]{ display: none !important; }
.products-grid.ng-paging .product-card-ghost{ display: none !important; }
.products-grid .product-card.ng-reveal{
  animation: ngCardIn .6s cubic-bezier(.19, 1, .22, 1) both;
  animation-delay: calc(var(--ng-d, 0) * 45ms);
}
@keyframes ngCardIn {
  from { opacity: 0; transform: translateY(14px); filter: blur(6px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}


/* ─── Motion safety ────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce){
  .arrival-colors, .compact-colorway-options,
  .arrival-colors::before, .compact-colorway-options::before,
  .arrival-colors .color-dot, .compact-colorway-options .compact-color-option,
  .arrival-colors .color-dot::before, .compact-colorway-options .compact-color-option::before,
  .sticky-cat-link {
    transition: none !important;
  }
  .recommendations-grid.ng-rec-in .recommendation-card > *,
  .recommendations-grid.ng-rec-out .recommendation-card::before,
  .products-grid .product-card.ng-reveal,
  .rec-refresh-btn[aria-busy="true"] i { animation: none !important; }
}


/* ═══════════════════════════════════════════════════════════════════════
   Pass 4 — platform-wide consistency
     18  Rarity frames: one calm line,no hover glow,softer gold
     19  Rarity tag top-left on EVERY card,not only the shop grid
     20  One hairline everywhere: header,containers,footer,sections
     21  Category sidebar — definition,rhythm,contrast
     22  Product page header: content left,sort controls on one row
     23  Reviews: full width when there are none
   ═══════════════════════════════════════════════════════════════════════ */


/* ─── 18 · Rarity frames ───────────────────────────────────────────────── */
/* The frame is identity,not an event: it reads the same at rest and under
   the cursor. The glow is what made a grid of exclusives feel noisy,so it
   is gone — the line alone carries the tier. */

:root{
  /* The old gold (#fbc86e) sat at full saturation and pulled the eye off
     the photography. Champagne: the same metal, two stops calmer, so a
     row of exclusives reads as a set rather than six alarms. */
  --tier-exclusive: #d8b881;
}

:is(.arrival-card,.recommendation-card,.rec-card,.product-card,.compact-product-card)
  :is(.tier-signature,.tier-seasonal,.tier-exclusive,.tier-limited),.compact-product-card):is(.tier-signature,.tier-limited):not(.product-card-ghost),.tier-limited):not(.product-card-ghost):hover{
  border: 1px solid color-mix(in srgb, var(--neon) 66%, transparent) !important;
  box-shadow: none !important;
}

/* The tag keeps the tier colour,but stops shouting it. */
.tier-tag{
  text-shadow: none !important;
  border-color: color-mix(in srgb, var(--neon) 42%, transparent) !important;
  color: color-mix(in srgb, var(--neon) 88%, #fff) !important;
}


/* ─── 19 · Rarity tag,top-left,everywhere ────────────────────────────── */
/* It used to sit bottom-left on every surface except the shop grid,so the
   same product looked different depending on where you met it. One position
   now: top-left,under the NEW/SALE badge when there is one. */

.tier-tag{
  top: 8px;
  bottom: auto;
  left: 8px;
}
:is(.arrival-card,.recommendation-card,.rec-card,.product-card,.compact-product-card)
  :is(.product-badge,.arrival-badge,.compact-badge) ~ .tier-tag,.tier-tag:has(+ .product-badge),.product-badge + .tier-tag,.arrival-badge + .tier-tag,.compact-badge + .tier-tag{ top: 30px; }

@media (max-width: 768px) {
  .tier-tag{ top: 10px; left: 10px; }
  .product-badge + .tier-tag,.arrival-badge + .tier-tag,.compact-badge + .tier-tag,.tier-tag:has(+ .product-badge){ top: 32px; }
}

/* Badges themselves: same corner,same rhythm,on every card type. */
.product-badge,.arrival-badge,.compact-badge{
  top: 8px !important;
  left: 8px !important;
  right: auto !important;
  bottom: auto !important;
  border-radius: 3px !important;
  font-size: 8px !important;
  letter-spacing: 1px !important;
  padding: 3px 8px !important;
}
@media (max-width: 768px) {
  .product-badge,.arrival-badge,.compact-badge{ top: 10px !important; left: 10px !important; }
}


/* ─── 20 · One hairline,platform-wide ─────────────────────────────────── */
/* The shop index already used rgb(45 44 45); every other page still had the
   old translucent purple,so the same frame changed colour as you moved
   through the site. These are the four the brief names — footer top,container sides,header bottom,and the panels that wrap a section.
   Section HEADERS,buttons and product cards are deliberately untouched. */

footer{
  border-top: 1px solid var(--ng-line) !important;
}

.container{
  border-left: 1px solid var(--ng-line) !important;
  border-right: 1px solid var(--ng-line) !important;
}

header,header.header-shrunk{
  border-bottom: 1px solid var(--ng-line) !important;
}

/* Section panels: the boxes that hold a block of content. Anything ending in
   -section,plus the named panels the routes use for the same job. */
:is(.section,.magazine-section,.newsletter-section,.nl-section,.welcome-section,.form-section,.categories-search-section,.new-arrivals-section,.orders-section,.recently-viewed-section,.recommendations-section,.reviews-section,.tracking-box,.rewards-box,.os-body,.order-summary,.sidebar,.dashboard-sidebar,.blog-sidebar,.help-panel,.faq-card,.link-card,.cat-card){
  border-color: var(--ng-line) !important;
  border-image: none !important;
}

/* Section HEADERS keep exactly what they had — .os-header,.sidebar-header,.section-header and .category-header are all left alone on purpose. */


/* ─── 21 · Category sidebar — definition,rhythm,contrast ─────────────── */
/* What was wrong: a 48px row held a 38px chip and a 10px label,so most of
   each row was empty; the labels sat pale against a near-black panel,and
   nothing separated one row from the next. Now the dead width carries the
   product count,rows are tighter and hairline-separated,and the label is
   bright enough to read at a glance. */
.shop-page .category-list,
.product-page .category-list,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list{
  padding: 10px 9px 14px !important;
}
.shop-page .category-list li + li,
.product-page .category-list li + li,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list li + li{ margin-top: 1px; }
.shop-page .category-list .category-item,
.product-page .category-list .category-item,
.shop-page .category-list li + li .category-item,
.product-page .category-list li + li .category-item,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item,
.blog-sidebar .category-list li + li .category-item{
  display: grid !important;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  min-height: 42px;
  padding: 4px 11px 4px 6px !important;
  position: relative;
}

/* A hairline between rows instead of empty space. It stops at the chip so
   the glyph column reads as a continuous rail. */
.shop-page .category-list li + li .category-item::after,
.product-page .category-list li + li .category-item::after,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list li + li .category-item::after{
  content: "";
  position: absolute;
  top: -1px;
  left: 44px;
  right: 8px;
  height: 1px;
  background: var(--ng-line);
  opacity: .55;
  pointer-events: none;
}
.shop-page .category-list .category-item:hover::after,
.product-page .category-list .category-item:hover::after,
.shop-page .category-list .category-item.active::after,
.product-page .category-list .category-item.active::after,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item:hover::after,
.blog-sidebar .category-list .category-item.active::after{ opacity: 0; }
.shop-page .category-list .category-name,
.product-page .category-list .category-name,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-name{
  text-align: left;
  font-size: 10.5px !important;
  font-weight: 500 !important;
  letter-spacing: 1.7px !important;
  line-height: 1.25;
  /* The old .82 white read grey on this panel; .92 is the contrast the rest
     of the platform uses for a primary label. */
  color: rgba(255, 255, 255, .92) !important;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The count: the second tone the list was missing,and a reason for the
   right-hand space to exist. */
.category-list .category-count{
  justify-self: end;
  min-width: 22px;
  padding: 2px 6px;
  border-radius: 999px;
  border: 1px solid rgba(138, 43, 226, .22);
  background: rgba(138, 43, 226, .07);
  font-size: 9px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: .6px;
  text-align: center;
  color: rgba(255, 255, 255, .5);
  transition: color .35s ease, border-color .35s ease, background .35s ease;
}
.category-list .category-item:hover .category-count{
  border-color: rgba(186, 85, 211, .5);
  background: rgba(138, 43, 226, .16);
  color: rgba(255, 255, 255, .85);
}
.category-list .category-item.active .category-count{
  border-color: rgba(186, 85, 211, .75);
  background: rgba(138, 43, 226, .28);
  color: #fff;
}

/* Hover and active gain weight rather than glow: the label brightens,the
   rail thickens,and the row shifts by two pixels. */
.shop-page .category-list .category-item:hover .category-name,
.product-page .category-list .category-item:hover .category-name,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item:hover .category-name{ color: #fff !important; }
.shop-page .category-list .category-item.active,
.product-page .category-list .category-item.active,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item.active{
  box-shadow: inset 3px 0 0 #ba55d3 !important;
}

@media (max-width: 768px) {
  .shop-page .category-list .category-item,.product-page .category-list .category-item{ min-height: 46px; }
  .shop-page .category-list .category-name,.product-page .category-list .category-name{ font-size: 11px !important; }
}

/* The rail runs the full height of the content column by design; without a
   gradient it reads as a flat black slab under the last row. This gives the
   empty tail a direction so it looks intended rather than unfinished. */
.shop-page .category-sidebar,
.product-page .category-sidebar,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar{
  background:
    linear-gradient(180deg, rgba(138, 43, 226, .05) 0%, rgba(138, 43, 226, .012) 34%, transparent 62%),
    #060606f7 !important;
}
.shop-page .category-list li + li .category-item::after,
.product-page .category-list li + li .category-item::after,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list li + li .category-item::after{ opacity: .8; }


/* ─── 22 · Product page section header ─────────────────────────────────── */
/* The reviews header centred its title and let the sort buttons wrap onto a
   second row,which made the band twice as tall as it needed to be. Title
   left,controls right,one row. */

/* Only the band that actually carries controls — the recommendations header
   below it has none and stays centred. */
.product-page .category-header:has(.sort-controls){
  justify-content: space-between;
  gap: 18px;
  flex-wrap: nowrap;
}
.product-page .category-header:has(.sort-controls) .category-header-content{
  align-items: flex-start;
  text-align: left;
  min-width: 0;
}
.product-page .category-header:has(.sort-controls) .sort-controls{
  flex-wrap: nowrap;
  white-space: nowrap;
  flex: 0 0 auto;
}
/* Narrow screens genuinely cannot fit both on one line — there the controls
   scroll sideways rather than stacking,so the band keeps its height. */
@media (max-width: 900px){
  .product-page .category-header:has(.sort-controls) { padding-inline: 18px; }
  .product-page .category-header:has(.sort-controls) .sort-controls {
    overflow-x: auto;
    scrollbar-width: none;
    max-width: 58vw;
  }
  .product-page .category-header:has(.sort-controls) .sort-controls::-webkit-scrollbar { display: none; }
}
@media (max-width: 620px) {
  .product-page .category-header:has(.sort-controls){ flex-wrap: wrap; justify-content: flex-start; }
  .product-page .category-header:has(.sort-controls) .sort-controls{ max-width: 100%; }
}


/* ─── 23 · Reviews: no half-empty grid ─────────────────────────────────── */
/* The grid is two columns. A single card — the empty state,or the write-a-
   review form — used to sit in the left column with a hole beside it. */
.reviews-grid > .ng-review-empty,.reviews-grid > .ng-review-form{
  grid-column: 1 / -1;
}
.reviews-grid > .ng-review-empty{
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 132px;
  text-align: center;
}

/* ─── 24 · Shop & product category sidebar — separation ─────────────────
   The rows were divided by rgba(138,43,226,.06): a purple at six percent,which on the near-black panel renders as nothing at all. The list read as
   one undifferentiated column,and the only time a row had an edge was when
   the pointer was over it.

   The separator now uses the platform line colour (--ng-line,the same
   rgb(45,44,45) the footer,panel and card borders were standardised on in
   the previous pass),so the sidebar is divided at the weight everything
   else on the platform is divided at. Hover and active states sit above
   that as brand purple,so the hierarchy is: always-visible structure in
   grey,state in purple. */

/* SUPERSEDED by section 35 — see there.

   The separator used to be a `border-top: 1px solid var(--ng-line)` on each
   row, fighting section 21's `border: 1px solid transparent !important` and
   needing its own !important to win. It won, and the result was ten
   full-width grey rules stacked down a 232px column already carrying ten
   lit icon tiles: the list stopped reading as a list and started reading as
   a table. The divider now lives on the <li> as a faded hairline; nothing
   here sets a top border any more, so section 21's transparent one stands
   and the rows have no hard edge of their own. */

/* A row's own edges appear on hover and when active,which is what gives
   the "lit from within" feel the hover already had — now with a visible
   resting state underneath it rather than instead of it. */
/* The `li + li` variants are listed explicitly because section 21 styles the
   rows through that same four-part selector. Without them, rows 2..n lose
   this declaration on specificity and end up with a 1px accent bar while the
   first row has 2px — the sort of half-millimetre inconsistency that reads
   as sloppiness without ever being obvious enough to report. */
.shop-page .category-list .category-item,
.product-page .category-list .category-item,
.shop-page .category-list li + li .category-item,
.product-page .category-list li + li .category-item,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item,
.blog-sidebar .category-list li + li .category-item{
  border-left: 2px solid transparent !important;
  transition: background .32s var(--ease-smooth, ease),
              border-color .32s var(--ease-smooth, ease);
}
.shop-page .category-list .category-item:hover,
.product-page .category-list .category-item:hover,
.shop-page .category-list li + li .category-item:hover,
.product-page .category-list li + li .category-item:hover,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item:hover,
.blog-sidebar .category-list li + li .category-item:hover{
  background: rgba(138, 43, 226, .07);
  border-left-color: rgba(186, 85, 211, .55) !important;
}
.shop-page .category-list .category-item.active,
.product-page .category-list .category-item.active,
.shop-page .category-list li + li .category-item.active,
.product-page .category-list li + li .category-item.active,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .category-item.active,
.blog-sidebar .category-list li + li .category-item.active{
  border-left-color: var(--purple-light, #ba55d3) !important;
}

/* The "All Products" row is the list's header, so it keeps a rule under it
   when the rows between them have none. Section 35 gives it the same faded
   treatment at slightly more weight. */
.shop-page .category-list .ng-cat-all-li,
.product-page .category-list .ng-cat-all-li,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .ng-cat-all-li{
  margin-bottom: 6px;
  padding-bottom: 6px;
}

/* ─── 25 · Product page — the details column ────────────────────────────
   Three things wasted the right-hand column and made it read as loose.

   1 · `.product-options` split its width `1fr 1fr`,so COLOUR held 213px
       for three 26px swatches — roughly 120px of nothing — while SIZE got
       the same 213px for five boxes that need about 230px,and wrapped its
       last size onto a line of its own. The columns now size to what they
       hold: colour takes its content,size takes the rest and fits.
   2 · The quantity row pushed its label and its stepper to opposite ends of
       442px,leaving a long empty middle between two small controls.
   3 · The stack's 18px gaps were uniform,so a heading sat as far from the
       thing it labelled as two unrelated blocks sat from each other. */

.product-page .product-options{
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 28px;
  row-gap: 18px;
  align-items: start;
}

/* Sizes lay out as a row that can wrap,rather than filling a fixed track. */
.product-page .option-group .size-options{
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.product-page .option-group .color-options{
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* The label belongs to the control under it,so it sits closer to it than
   the groups sit to each other. */
.product-page .option-group{
  gap: 9px !important;
}
.product-page .option-label{
  margin: 0 !important;
}

/* Quantity: the stepper comes to the label instead of the two of them
   holding opposite ends of the column. */
.product-page .quantity-row{
  justify-content: flex-start !important;
  align-items: center;
  gap: 16px !important;
  height: auto !important;
  padding: 10px 14px !important;
}
.product-page .quantity-label{
  flex: 0 0 auto;
}
.product-page .quantity-controls{
  flex: 0 0 auto;
  margin-left: 0 !important;
}

/* The column's own rhythm: heading to price to description tightens,the
   blocks between stay apart. */
.product-page .product-details{
  gap: 16px;
}
.product-page .product-header{
  gap: 7px !important;
}
.product-page .price-description-row{
  gap: 9px !important;
}

/* ─── 26 · The recommendations rail — one component, one set of rules ───
   "You May Also Like" is one React component (NgRecRail) rendered on the
   shop page, the product page and the bag. Its CSS, though, had been
   written three times — 47 rules scoped `.shop-page`, 54 scoped
   `.product-page`, and 3 unscoped. The bag renders inside `.container`,
   which matches neither scope, so it received the three unscoped rules and
   nothing else: six full-width unstyled rows. The two scoped copies had
   also drifted from each other.

   src/tools/unscope-rec-rail.py takes the shop page's copy as the
   reference, strips its page prefix so it applies wherever the component
   appears, and deletes the product-page copy so there is nothing left to
   diverge. The two rules below are what that pass could not derive from
   either copy. */

/* The rail image was `display: inline` on the shop page and `display:
   block` on the bag — not from any rail rule, but because
   storefront-CartRoute.css happens to carry a global `img { display:
   block }` and storefront.css does not. An inline replaced element sits on
   a text baseline, so `height: 100%` resolved against a line box rather
   than the card and the image collapsed to the height of its alt text.
   The component states it for itself now and no longer depends on which
   page it landed on. */
.recommendation-image {
  display: block;
}

/* The rail is a full-width band: on the shop and product pages its parent
   is the page root, so `width: 85%; max-width: 1200px` gave it 1200px. In
   the bag its parent is a 1275px `.container` with 32px of padding, so the
   same rule gave it 1028px and a visibly narrower grid than the other two.
   Sizing against the viewport instead of the parent makes the band the
   same width wherever it is placed, and the negative margin cancels
   whatever padding the parent applies. */
.recommendations-full-width {
  width: min(1200px, 85vw);
  margin-left: auto;
  margin-right: auto;
}

/* The rail's header is the rail's, not the page's.
   Both the rail and the shop/product pages use `.category-header` for their
   own heading bands. The shop page sets `padding-top: 28px !important` on
   its band, which — same specificity, `!important`, later in the file —
   also landed on the rail's band and made the rail 16px taller there than
   anywhere else. Restating the rail's own geometry at a specificity the
   page rule cannot reach keeps the component looking the same wherever it
   is used, whatever a page does to its own headings. */
.recommendations-full-width .category-header-wrapper .category-header,
.recommendations-full-width .category-header {
  margin: 0 0 28px !important;
  padding: 12px 30px !important;
  min-height: 0 !important;
}

/* The brand tokens belong to the document, not to two page wrappers.
   `--brand`, `--brand-light`, `--surface`, `--border` and the rest were
   declared on `.shop-page` and `.product-page` only, while `:root` carried
   a parallel set under different names (`--purple`, `--purple-light`, …).
   A component rendered outside those two wrappers — the rail in the bag,
   inside `.container` — resolved `var(--brand-light)` to nothing, so its
   prices fell back to inherited white while the same component showed them
   in brand purple two pages over.

   Declaring them at the root fixes it for every component, present and
   future, without touching the page wrappers: those still declare the same
   values locally and simply no longer need to. The values are copied
   verbatim from `.shop-page`, so nothing that already worked changes. */
:root {
  --brand: #8a2be2;
  --brand-light: #ba55d3;
  --text: #fff;
  --muted: rgba(255, 255, 255, .5);
  --dim: rgba(255, 255, 255, .3);
  --border: rgba(138, 43, 226, .3);
  --border-fine: rgba(138, 43, 226, .12);
  --surface: #000;
  --panel: rgba(255, 255, 255, .03);
  --ease: cubic-bezier(.22, 1, .36, 1);
}

/* The rail carries its own tokens.
   `--border-silver` is rgba(180,180,180,.12) at the root but .15 on
   `.product-page`, so the rail's REFRESH button had a slightly brighter
   border on that one page. Declaring the values the rail uses on the rail
   itself means a page can theme its own content freely without the
   component drifting with it. */
.recommendations-full-width {
  --brand: #8a2be2;
  --brand-light: #ba55d3;
  --border: rgba(138, 43, 226, .3);
  --border-fine: rgba(138, 43, 226, .12);
  --border-silver: rgba(180, 180, 180, .12);
  --surface: #000;
  --panel: rgba(255, 255, 255, .03);
  --ease: cubic-bezier(.22, 1, .36, 1);
}

/* VIEW MORE is an <a>, and the product page sets `.product-page a { color:
   inherit }` — so on that page alone the link took the surrounding white
   instead of the rail's own muted tone. Stated here at a specificity the
   page rule cannot reach. */
.recommendations-full-width .more-like-it-btn {
  color: rgba(255, 255, 255, .5);
}
.recommendations-full-width .more-like-it-btn:hover {
  color: #fff;
}

/* The mobile rail: a real carousel, and a real full-bleed.

   Below 768px the rail's own CSS turns the grid into a flex row of
   full-width cards — a carousel — and hands the clipping to
   `.recommendations-carousel-container`. Nothing ever renders that wrapper;
   it exists only in the stylesheet. So on every page the six cards laid out
   as a 2340px row with nothing to contain them, and the page scrolled
   sideways. It showed up on the bag first only because the other two happen
   to clip it further up the tree.

   Making the grid its own scroller fixes it for all three at once, with no
   wrapper and no JavaScript, and scroll-snap gives the swipe the carousel
   was reaching for. */
@media (max-width: 768px) {
  .recommendations-full-width .recommendations-grid {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .recommendations-full-width .recommendations-grid::-webkit-scrollbar {
    display: none;
  }
  .recommendations-full-width .recommendation-card {
    scroll-snap-align: center;
  }

  /* Full-bleed from the viewport rather than from the parent. The bag puts
     the rail inside a `.container` with 32px of padding, so `width: 100%`
     made the band 34px narrower there than on the other two pages. */
  .recommendations-full-width {
    width: 100vw !important;
    max-width: 100vw !important;
    margin-left: calc(50% - 50vw) !important;
    margin-right: calc(50% - 50vw) !important;
  }
}

/* ─── 27 · The breadcrumb — ONE component, one set of rules ─────────────
   Ten routes render this strip of markup, and every pass that ever touched
   it appended another voice rather than editing the one that was there.
   By this pass storefront.css held twelve `.timeline-search` blocks, nine
   `.timeline-breadcrumb-wrapper` blocks and five `.timeline-path` blocks,
   and each of the nine route stylesheets carried its own 25–37 rule copy
   on top — 330 rules in ten files to style one strip.

   They did not merely repeat each other, they contradicted each other, and
   the contradiction was doing real damage:

     · The account page drew a 12px filled dot with two sparkle halos; the
       product page drew a 6px hollow one; the rest drew 8px. Same crumb,
       three appearances, decided by which route chunk happened to load.

     · A mobile rule `@media (max-width:768px){.timeline-breadcrumb{
       flex-direction:column!important}}` sat at byte 135,615. A desktop
       rule `.timeline-breadcrumb{flex-direction:row!important}` sat at
       byte 151,672. Equal specificity, both !important — so the later one
       won at EVERY width and the mobile stack never happened. At 390px the
       240px search field and the crumbs were laid out in one row across
       330px of available space, and "Lookbook" rendered on top of the
       search input. That is not a styling nuance; the breadcrumb was
       broken on a phone, and no amount of further appending would have
       fixed it, because the bug WAS the appending.

   So this is the whole component, and the only definition of it. The
   account page's dot is the reference, per the brief. storefront.css and
   every route stylesheet have been stripped of breadcrumb rules by
   src/tools/strip-chrome-css.py --set=breadcrumb; ng-polish.css loads last,
   so this block is what renders. Order matters and is deliberate:
   structure, then appearance, then fit, then the breakpoints — widest to
   narrowest, so each one narrows the last instead of fighting it.

   Anything that needs to change about the breadcrumb, anywhere on the
   platform, changes here. There is nowhere else for it to be. */

/* ── Structure ──────────────────────────────────────────────────────── */
.timeline-breadcrumb-wrapper{
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 0 28px;
  padding: 14px 22px;
  /* 2.14.0 - the strip was `transparent`, which on /blog, /cart, /account and
     the rest means it sat directly on `.main-content`'s #000: a 1px hairline
     drawing a rectangle around nothing, with no surface of its own to separate
     the crumbs from the page.

     It is a panel again, and a NEUTRAL one - the same family as the section
     cards on the index (`.compact-product-category`, `.new-arrivals-section`:
     `linear-gradient(135deg,#050505fa,#0a0a0af5)`), carried a few steps
     lighter so it actually clears #000 instead of reading as more black.
     Deliberately no purple in it: on this strip the purple belongs to the
     active dot and the focused search field, and a tinted panel competed with
     both. The border stays on `--ng-line`; only the fill changed. */
  background: linear-gradient(135deg, #141414 0%, #0f0f0f 55%, #121212 100%);
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  position: relative;
  z-index: 2;
}

.timeline-breadcrumb{
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  width: 100%;
  min-width: 0;
}

/* The padding/negative-margin pair is not spacing — it is clip-box headroom.
   `overflow: hidden` is what lets a long crumb truncate instead of pushing
   the search field over, but it clips at the padding edge, and the active
   dot's sparkle halos sit 12px OUTSIDE the dot. With a flush box the orb was
   being sliced off top and bottom. CSS has no way to clip one axis and not
   the other (setting either axis to `hidden` forces the other to `auto`), so
   the box is grown 14px vertically and pulled back by the same amount: the
   halos have room, the layout is unchanged, and horizontal truncation still
   works. */
.timeline-path{
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  padding-top: 14px;
  padding-bottom: 14px;
  margin-top: -14px;
  margin-bottom: -14px;
}

/* ── The crumb ──────────────────────────────────────────────────────── */
.timeline-item{
  display: flex;
  align-items: center;
  gap: 8px;
  position: relative;
}

.timeline-link{
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .44);
  text-decoration: none;
  transition: color .3s ease;
}
.timeline-link:hover{ color: rgba(255, 255, 255, .8); }
.timeline-link.current{ letter-spacing: 1.5px; color: #fff; }

/* The dot, as the account page draws it: a faint purple gradient inside a
   purple hairline, growing and filling when the crumb is the current page,
   with two counter-rotating sparkle halos around it. */
.timeline-dot{
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: linear-gradient(135deg, rgba(138,43,226,.3), rgba(186,85,211,.2));
  border: 1px solid rgba(138, 43, 226, .15);
  position: relative;
  flex-shrink: 0;
  transition: width .3s var(--ease-luxe, ease), height .3s var(--ease-luxe, ease),
              background .3s var(--ease-luxe, ease), border-color .3s var(--ease-luxe, ease),
              box-shadow .3s var(--ease-luxe, ease);
}

.timeline-item.active .timeline-dot{
  width: 12px;
  height: 12px;
  background: linear-gradient(135deg, var(--purple, #8a2be2), var(--purple-light, #ba55d3));
  border: 1.5px solid rgba(255, 255, 255, .12);
  box-shadow: 0 0 8px rgba(186,85,211,.25), 0 0 16px rgba(186,85,211,.1),
              inset 0 1px rgba(255,255,255,.15);
}

.timeline-item.active .timeline-dot:before{
  content: "";
  position: absolute;
  inset: -12px;
  border-radius: 50%;
  pointer-events: none;
  opacity: .6;
  background:
    radial-gradient(circle at 20% 50%, rgba(255,255,255,.8) 1px, transparent 1px),
    radial-gradient(circle at 80% 20%, rgba(186,85,211,.6) 1px, transparent 1px),
    radial-gradient(circle at 40% 80%, rgba(255,255,255,.4) 1px, transparent 1px),
    radial-gradient(circle at 90% 70%, rgba(138,43,226,.5) 1px, transparent 1px);
  background-size: 20px 20px, 25px 25px, 18px 18px, 22px 22px;
  will-change: transform;
  animation: sparkleRotate 8s linear infinite;
}

.timeline-item.active .timeline-dot:after{
  content: "";
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  pointer-events: none;
  opacity: .4;
  background:
    radial-gradient(circle at 60% 30%, rgba(255,255,255,.6) 1px, transparent 1px),
    radial-gradient(circle at 10% 90%, rgba(186,85,211,.4) 1px, transparent 1px),
    radial-gradient(circle at 70% 90%, rgba(255,255,255,.3) 1px, transparent 1px);
  background-size: 15px 15px, 20px 20px, 16px 16px;
  will-change: transform;
  animation: sparkleRotate 12s linear infinite reverse;
}

@keyframes sparkleRotate{ 0%{ transform: rotate(0); } to{ transform: rotate(360deg); } }

/* The connector, restored to what the account page drew.
   The first version of this section reduced it to a fixed 2px rule with
   `min-width: 6px` and no `flex-grow`, so it collapsed to a 6px stub on
   every page, and dropped the `:before` entirely, which took the travelling
   highlight with it. Both are back:

     · `flex: 1 1 40px` — the connectors take the slack in the path, which
       is what makes them read as a timeline rather than as punctuation.
       40px is the width they settle at; they grow from there.
     · `position: relative` + `overflow: hidden` — the highlight is a 50%-wide
       band sliding from -100% to 300%, and without these it would be
       invisible (nothing to position against) and unclipped (it would run
       out over the crumbs either side). */
.timeline-line,.timeline-line-extended{
  height: 2px;
  flex: 1 1 40px;
  min-width: 12px;          /* below the 40px basis, so a long crumb can still squeeze them */
  margin: 0 8px;
  border-radius: 1px;
  position: relative;
  overflow: hidden;
  background: linear-gradient(90deg, rgba(138,43,226,.15), rgba(186,85,211,.25), rgba(138,43,226,.15));
}

.timeline-line:before,.timeline-line-extended:before{
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 50%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.15), transparent);
  will-change: transform;
  /* Its own name: the cart and lookbook route sheets — loaded after this
     file and never unloaded — redefine `timelineShimmer` as a `left`
     animation, which turned this compositor-only shimmer back into a
     per-frame layout on every page visited after either of them. */
  animation: ngCrumbShimmer 4s linear infinite !important;
}

@keyframes timelineShimmer{
  0%{ transform: translateX(-100%); }
  to{ transform: translateX(300%); }
}
@keyframes ngCrumbShimmer{
  0%{ transform: translate3d(-100%, 0, 0); }
  to{ transform: translate3d(300%, 0, 0); }
}

/* ── The search field ───────────────────────────────────────────────── */
.timeline-search{
  display: flex;
  align-items: center;
  position: relative;
  height: 30px;
  background: rgba(255, 255, 255, .015);
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  overflow: visible;
  transition: border-color .5s var(--ease-smooth, ease), background .5s var(--ease-smooth, ease);
}

/* The stray line. `.timeline-search:before` drew a 1px animated gradient
   along the bottom edge — a highlight sitting directly on the field's own
   border, which read as a rendering fault rather than as decoration. */
.timeline-search:before{ content: none; display: none; }

.timeline-search:focus-within{
  border-color: rgba(138, 43, 226, .4);
  background: rgba(138, 43, 226, .03);
}

.timeline-search-input{
  flex: 1;
  min-width: 0;
  height: 100%;
  background: transparent;
  border: none;
  outline: none;
  padding: 0 10px;
  font-size: 10px;
  font-weight: 300;
  letter-spacing: .5px;
  text-transform: none;
  color: rgba(255, 255, 255, .72);
  caret-color: var(--purple-light, #ba55d3);
}
.timeline-search-input::placeholder{
  color: rgba(255, 255, 255, .22);
  font-weight: 300;
  letter-spacing: .5px;
  text-transform: none;
}

.timeline-search-btn{
  height: 100%;
  padding: 0 12px;
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, .28);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  transition: color .3s ease;
}
.timeline-search-btn:before{
  content: "";
  position: absolute;
  left: 0; top: 22%; bottom: 22%;
  width: 1px;
  background: linear-gradient(180deg, transparent, rgba(138,43,226,.18), transparent);
}
.timeline-search:focus-within .timeline-search-btn:before{
  background: linear-gradient(180deg, transparent, rgba(186,85,211,.35), transparent);
}
.timeline-search-btn:hover{ color: var(--purple-light, #ba55d3); }
.timeline-search-btn i{ font-size: 10px; transition: transform .3s ease; }
.timeline-search-btn:hover i{ transform: scale(1.08); }

.timeline-search-dot{
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(186, 85, 211, .16);
  flex-shrink: 0;
  margin-left: 11px;
  transition: all .5s var(--ease-smooth, ease);
}
.timeline-search:focus-within .timeline-search-dot{
  background: rgba(186, 85, 211, .65);
  box-shadow: 0 0 6px rgba(186, 85, 211, .3);
}

/* The dropdown is the homepage's dropdown (assets/search-unified.js hands
   both fields to the same setup), and it is built for a ~400px bar. Given a
   380px floor here it hung 141px off the left of a 240px field — a panel
   visibly wider than the control it belongs to, which reads as a bug
   whatever it contains.

   It now takes exactly the field's box: `left: 0; right: 0` and no floor, so
   its width IS the field's width and can never exceed it. Everything below
   is the same panel at that width — same colours, same tabbed head, same
   rows, same browse button — on a tighter metric. The file already had a
   precedent for this in `.sticky-search-wrap`, which compacts the same
   panel for the narrower sticky bar; this is that idea carried further. */
.timeline-search .ng-search-ac,
.timeline-search .ng-search-ac-home,
.timeline-search .search-dropdown{
  position: absolute;
  top: calc(100% + 4px);
  left: 0 !important;
  right: 0 !important;
  width: auto !important;
  min-width: 0 !important;
  max-width: none !important;
  margin-left: 0 !important;
  z-index: 9999;
  box-sizing: border-box;
}

/* ── The panel, at 240px ──────────────────────────────────────────────
   Two tabs, a thumbnail, two lines of text and a count badge have to share
   240px. What goes: the row arrow (decoration, and the row is a link
   whether or not it is drawn), and most of the letter-spacing on the tabs —
   tracking is what makes "JOURNAL" overflow first. What stays: every
   element, every colour, and the proportions between them. */
.timeline-search .ng-hp-tab{
  padding: 11px 4px 10px;
  font-size: 8.5px;
  letter-spacing: 1px;
  gap: 5px;
}
.timeline-search .ng-hp-tab-count{
  min-width: 15px;
  height: 14px;
  padding: 0 4px;
  font-size: 8px;
}

.timeline-search .ng-hp-panel-scroll{
  max-height: 300px;
  padding: 3px 7px 7px;
}

.timeline-search .ng-hp-group{
  gap: 7px;
  padding: 8px 2px 5px;
  font-size: 8px;
  letter-spacing: 1.4px;
}

.timeline-search .ng-hp-item{
  gap: 8px;
  margin-bottom: 5px;
  padding: 6px 7px;
}
.timeline-search .ng-hp-item .ng-ac-img,
.timeline-search .ng-hp-item .ng-ac-blog-icon{
  width: 30px;
  height: 30px;
  flex-shrink: 0;
}
.timeline-search .ng-hp-item .ng-ac-blog-icon i{ font-size: 11px; }

/* The text column is the only part that should absorb the loss, so it needs
   a zero floor — without it a long product name pushes the thumbnail and
   the row overflows instead of the name truncating. */
.timeline-search .ng-hp-item > div:not(.ng-ac-img):not(.ng-ac-blog-icon){
  min-width: 0;
  flex: 1 1 auto;
}
.timeline-search .ng-hp-item .ng-ac-name{
  font-size: 11px;
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.timeline-search .ng-hp-item .ng-ac-meta{
  font-size: 9px;
  margin-top: 2px;
}

/* No room for it, and it never carried meaning the row did not. */
.timeline-search .ng-hp-item .ng-ac-arrow{ display: none; }

.timeline-search .ng-hp-foot{ padding: 7px; }
.timeline-search .ng-hp-browse{
  padding: 9px 6px;
  font-size: 8.5px;
  letter-spacing: 1.2px;
}

/* The flat list renderer (blog and thread pages use it) gets the same
   treatment, since it hangs off the same 240px field. */
.timeline-search .ng-ac-item{
  gap: 8px;
  padding: 7px 9px;
}
.timeline-search .ng-ac-item .ng-ac-name{ font-size: 11px; }
.timeline-search .ng-ac-item .ng-ac-meta{ font-size: 9px; }

/* ── Fit: the link tree fits, every time, whatever the page is called ──
   A product named "Delicate Lace Bralette" used to push the last crumb
   164px past the search field, because the path was allowed to overflow
   its own box and the search had no floor. Now: the section crumbs hold
   their full labels (letting them all shrink produced "H… > INTIMATE … >
   B… > DELICATE LA…", four truncated words that tell you nothing), the
   page's own title takes what is left and truncates with an ellipsis, and
   the search gives back up to 90px before the title starts to suffer. */
.timeline-item{ min-width: 0; flex: 0 0 auto; }
.timeline-item .timeline-link{
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The last crumb SHRINKS but does not GROW. Making it `flex: 1 1 auto` was
   what starved the connectors: the crumb swallowed all the slack, the lines
   fell back to their floor, and the timeline stopped looking like one. The
   connectors take the slack (they are `flex: 1 1 40px` above); the crumb
   gives width back only when there is not enough to go round. */
.timeline-item:last-child{ flex: 0 1 auto; }
/* Small enough that the floor never itself forces an overflow — at the
   tightest width the fixed crumbs leave about 45px — so the ellipsis
   renders inside the link rather than the path clipping mid-character. */
.timeline-item:last-child .timeline-link{ min-width: 5ch; }

.timeline-search{ flex: 0 1 240px; width: 240px; min-width: 150px; }

/* ── Breakpoints, widest to narrowest ──────────────────────────────── */
@media (max-width: 900px){
  .timeline-search{ flex-basis: 180px; width: 180px; min-width: 130px; }
  /* No floor here either — the panel follows the field down to 180px. The
     thumbnail is what stops being worth its width first. */
  .timeline-search .ng-hp-item .ng-ac-img,
  .timeline-search .ng-hp-item .ng-ac-blog-icon{ width: 26px; height: 26px; }
  .timeline-search .ng-hp-tab{ letter-spacing: .6px; padding-left: 2px; padding-right: 2px; }
}

/* Below 768px the row stops working: a usable search field and a legible
   path cannot share 330px. The path goes on its own line and scrolls
   sideways; the field takes the full width underneath it. This is the rule
   that a later desktop `flex-direction:row!important` used to cancel. */
@media (max-width: 768px){
  .timeline-breadcrumb-wrapper{ margin-bottom: 16px; padding: 12px 16px; }
  .timeline-breadcrumb{
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  .timeline-path{
    order: 1;
    overflow-x: auto;
    overflow-y: hidden;
    /* The clip-box headroom the desktop rule adds is not wanted here: the
       halos it makes room for are switched off below this width (see the
       end of this block), and in the stacked layout that extra 14px reaches
       down over the search field underneath, where it would swallow clicks
       along the field's top edge. */
    padding: 2px 0;
    margin-top: 0;
    margin-bottom: 0;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .timeline-path::-webkit-scrollbar{ display: none; }
  /* Scrolling sideways means nothing has to be truncated any more, so the
     crumbs go back to their natural widths and the ellipsis is dropped. */
  .timeline-item,.timeline-item:last-child{ flex: 0 0 auto; }
  .timeline-item .timeline-link{ overflow: visible; text-overflow: clip; }
  .timeline-search{
    order: 2;
    flex: 0 0 auto;
    width: 100%;
    min-width: 0;
    height: 34px;
  }
  /* Stacked and full width below 768px, so the panel has the whole row and
     goes back to its comfortable metric. */
  .timeline-search .ng-hp-item{ gap: 11px; padding: 8px 10px; }
  .timeline-search .ng-hp-item .ng-ac-img,
  .timeline-search .ng-hp-item .ng-ac-blog-icon{ width: 40px; height: 40px; }
  .timeline-search .ng-hp-item .ng-ac-name{ font-size: 12px; }
  .timeline-search .ng-hp-item .ng-ac-meta{ font-size: 10px; }
  .timeline-search .ng-hp-tab{ font-size: 9.5px; letter-spacing: 1.8px; padding: 13px 8px 12px; }
  .timeline-search-input{ font-size: 11px; }
  /* The halos are 12px of animation around a 12px dot on a strip that now
     scrolls; they cost more than they give on a phone. */
  .timeline-item.active .timeline-dot:before,
  .timeline-item.active .timeline-dot:after{ display: none; }
}

@media (max-width: 480px){
  .timeline-breadcrumb-wrapper{ padding: 10px 12px; margin-bottom: 12px; }
  .timeline-search{ height: 32px; }
  .timeline-search-input{ font-size: 10px; padding: 0 10px; }
  .timeline-search-btn{ padding: 0 10px; }
  .timeline-search-dot{ margin-left: 8px; width: 4px; height: 4px; }
  .timeline-dot{ width: 6px; height: 6px; }
  .timeline-item.active .timeline-dot{ width: 10px; height: 10px; }
}

/* ─── 28 · Shop filter bar — icons, and a centred row ───────────────────
   The Sort / Size / Colour / Price rows were a wall of words: a label on
   the left, options ranged after it, nothing for the eye to land on and
   nothing but a border to say which option was chosen. Each option now
   carries its own icon, and the label sits with its options rather than
   apart from them. */

/* The label and its options read as one centred group. The grid gave the
   label a fixed 78px column on the left and let the options start wherever
   that ended, so the three rows never lined up with each other or with the
   grid below them. */
.shop-page .shop-filter-grid .sfg-row {
  justify-items: center;
}
.shop-page .shop-filter-grid .sfg-label {
  justify-self: center;
  text-align: center;
}
.shop-page .shop-filter-grid .sfg-options {
  justify-content: center !important;
}

/* The button: icon and text on one baseline, centred. */
.shop-page .shop-filter-grid .sfg-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  text-align: center;
}
.sfg-btn-text {
  display: inline-block;
}

/* Monochrome until chosen, white when chosen.
   The icon is the state indicator — a row of grey icons with one white one
   says which option is active from across the page, which a 1px border
   change does not. */
.shop-page .shop-filter-grid .sfg-btn-icon {
  font-size: 9.5px;
  line-height: 1;
  color: rgba(255, 255, 255, .34);
  filter: grayscale(1);
  transition: color .3s var(--ease-smooth, ease), filter .3s var(--ease-smooth, ease);
}
.shop-page .shop-filter-grid .sfg-btn:hover .sfg-btn-icon {
  color: rgba(255, 255, 255, .7);
}
.shop-page .shop-filter-grid .sfg-btn.active .sfg-btn-icon {
  color: #fff;
  filter: none;
}
/* The plus on the top price band follows its figure. */
.sfg-btn-icon-after {
  font-size: 7.5px !important;
  margin-left: -2px;
}

/* The size buttons are a fixed-width grid of two-character labels; an icon
   would crowd them, so only "All" carries one. */
.shop-page .shop-filter-grid .sfg-btn-sm {
  gap: 5px;
}

/* ─── 29 · The was-price moves above the title ──────────────────────────
   `.original-price` lives inside `.product-pricing`, which shares its row
   with `.product-colors` — so on a discounted card the struck figure and
   its saving tag sat directly against the colourway swatches and the two
   fought for the same strip of a 141px card.

   `display: contents` dissolves the pricing wrapper so its two children
   become grid items of `.product-details` in their own right. The was-price
   can then take the full width above the title, where nothing competes with
   it, and the live price stays on the row with the swatches where it
   belongs. Ordering rather than explicit row numbers, so a card with no
   discount simply has no such row — no reserved empty track, no gap, and
   the two kinds of card stay the same height. */
.shop-page .product-card .product-pricing {
  display: contents !important;
}

.shop-page .product-card .original-price {
  order: -1;
  grid-column: 1 / -1;
  /* Centred over the title, always. It used to be `start` unless the card
     had no colourway dots, which meant a discounted card was left-aligned
     here and centred three cards along, depending on nothing the shopper
     could see. The title below it is centred; so is this. */
  justify-self: center;
  margin: 0 0 1px;

  /* The details column overlays the bottom of the product photograph — the
     image container runs to within a few pixels of the card's base — so
     this figure is read against whatever happens to be in the shot. A
     struck grey at 42% disappeared over a pale one. The halo is four
     offsets rather than one blur: a single shadow leaves the side facing
     the light unprotected, and on a photograph there is no telling which
     side that is. */
  /* !important because an earlier rule in this file sets the resting colour
     with it; this is the same declaration, lifted for legibility over a
     photograph rather than a panel. */
  color: rgba(255, 255, 255, .62) !important;
  text-shadow:
    0 1px 2px rgba(0, 0, 0, .95),
    0 -1px 2px rgba(0, 0, 0, .95),
    1px 0 2px rgba(0, 0, 0, .95),
    -1px 0 2px rgba(0, 0, 0, .95),
    0 2px 7px rgba(0, 0, 0, .8);
}

/* The saving tag rides along with it and needs the same protection — it is
   drawn on a translucent panel, so the shadow goes under the whole chip
   rather than on its letters. */
.shop-page .product-card .original-price:after {
  text-shadow: 0 1px 2px rgba(0, 0, 0, .9);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .55);
}

.shop-page .product-card .product-title {
  order: 0;
}
.shop-page .product-card .current-price {
  order: 1;
  grid-column: 1;
  justify-self: start;
}
.shop-page .product-card .product-details:not(:has(.color-dot)) .current-price {
  grid-column: 1 / -1;
  justify-self: center;
}
.shop-page .product-card .product-colors {
  order: 2;
}
.shop-page .product-card .arrival-add-btn {
  order: 3;
}

/* ─── 30 · Reviews — the grid never ends on a hole ──────────────────────
   The review grid is two columns, so an odd count always left a blank
   half-width at the end: three reviews, one empty slot. The slot is now
   the section's invitation to write one, which is the thing that was
   missing from the page anyway.

   For a guest it carries the order-number form (POST reviews/guest proves
   the purchase against a paid order before it writes anything), so the
   shoppers who checked out without an account — a first-class path through
   this storefront — can finally leave the verified review they had earned
   and no way to give. */
.ng-review-invite {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 10px;
  border: 1px dashed rgba(138, 43, 226, .26) !important;
  background: linear-gradient(135deg, rgba(138, 43, 226, .07), rgba(186, 85, 211, .03)) !important;
}
.ng-invite-icon {
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(138, 43, 226, .14);
  border: 1px solid rgba(186, 85, 211, .3);
  color: var(--brand-light, #ba55d3);
  font-size: 13px;
}
.ng-invite-title {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: #fff;
}
.ng-invite-copy {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: rgba(255, 255, 255, .58);
}
.ng-invite-btn {
  align-self: stretch;
  padding: 9px 16px;
  border: 1px solid rgba(186, 85, 211, .4);
  border-radius: 3px;
  background: rgba(138, 43, 226, .13);
  color: #fff;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .3s var(--ease-smooth, ease), border-color .3s var(--ease-smooth, ease);
}
.ng-invite-btn:hover {
  background: rgba(138, 43, 226, .24);
  border-color: var(--brand-light, #ba55d3);
}
.ng-invite-alt {
  font-size: 10px;
  letter-spacing: .4px;
  color: rgba(255, 255, 255, .4);
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, .14);
  padding-bottom: 1px;
}
.ng-invite-alt:hover {
  color: var(--brand-light, #ba55d3);
  border-bottom-color: rgba(186, 85, 211, .5);
}

/* The expanded guest form keeps the card's own frame; it is the same card,
   opened, not a different thing appearing in its place. */
.ng-review-invite-form {
  align-items: stretch;
  gap: 9px;
}
.ng-invite-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
@media (max-width: 720px) {
  .ng-invite-fields { grid-template-columns: 1fr; }
}

/* ─── 31 · Size guide, frequently bought together, bag header ───────────
   Three blocks that had fallen behind the contrast and spacing of the rest
   of the platform. */

/* SIZE GUIDE
   Column headings sat at #ba55d3 on near-black — a purple-on-black pairing
   that is the weakest contrast on the page for the text you read first —
   and the measurement columns were dimmer than the size column, so the
   numbers you came for were the hardest to read. Headings go to a legible
   silver with the brand colour kept for the accent rule under them; every
   cell reads at the same weight; rows alternate so the eye tracks across a
   wide table without losing its line. */
.product-page .size-guide .size-chart th,
.product-page .size-chart th {
  color: rgba(255, 255, 255, .72) !important;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  padding: 12px 16px !important;
  border-bottom: 1px solid rgba(186, 85, 211, .34) !important;
  background: rgba(138, 43, 226, .05);
}
.product-page .size-chart td {
  color: rgba(255, 255, 255, .8) !important;
  font-size: 11.5px;
  padding: 11px 16px !important;
  border-bottom: 1px solid var(--ng-line, rgb(45 44 45)) !important;
}
.product-page .size-chart tbody tr:nth-child(even) td {
  background: rgba(255, 255, 255, .017);
}
.product-page .size-chart tbody tr:hover td {
  background: rgba(138, 43, 226, .08);
  color: #fff !important;
}
.product-page .size-chart tbody tr:last-child td {
  border-bottom: none !important;
}
.product-page .size-guide > .category-header-text,
.product-page .size-guide .size-guide-title {
  color: rgba(255, 255, 255, .85) !important;
}

/* FREQUENTLY BOUGHT TOGETHER
   The cards were mostly empty: a tall image well, then a name at 9px in
   40%-white with the price in full-strength magenta underneath — the least
   important line on the card was the loudest thing on it, and the block
   ended with a band of nothing. The name comes up to a readable weight,
   the price settles to the brand tone the rest of the platform uses for
   prices, and the grid stops reserving height it has no content for. */
.product-page .frequently-bought .fbt-grid,
.product-page .frequently-bought [class*="grid"] {
  align-content: start;
  gap: 12px;
}
.product-page .frequently-bought .fbt-name,
.product-page .frequently-bought [class*="name"],
.product-page .frequently-bought [class*="title"] {
  color: rgba(255, 255, 255, .82) !important;
  font-size: 11px !important;
  font-weight: 500 !important;
  letter-spacing: .3px;
  line-height: 1.4;
}
.product-page .frequently-bought [class*="price"] {
  color: var(--brand-light, #ba55d3) !important;
  font-size: 12px !important;
  font-weight: 600 !important;
}
.product-page .frequently-bought [class*="card"],
.product-page .frequently-bought [class*="item"] {
  border: 1px solid var(--ng-line, rgb(45 44 45)) !important;
  background: rgba(255, 255, 255, .015);
  transition: border-color .3s var(--ease-smooth, ease), background .3s var(--ease-smooth, ease);
}
.product-page .frequently-bought [class*="card"]:hover,
.product-page .frequently-bought [class*="item"]:hover {
  border-color: rgba(186, 85, 211, .4) !important;
  background: rgba(138, 43, 226, .06);
}

/* THE BAG HEADER
   "0 items" in 40%-white beside a CLEAR ALL that was nearly invisible —
   the one row that tells you what is in your bag was the quietest thing on
   the page. It gets the same treatment every other section header on the
   platform has: a readable label, a rule underneath, and a control that
   looks like one. */
.cart-items-header {
  display: flex !important;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px !important;
  background: linear-gradient(135deg, rgba(138, 43, 226, .08), rgba(186, 85, 211, .03)) !important;
  border: 1px solid var(--ng-line, rgb(45 44 45)) !important;
  border-bottom-color: rgba(186, 85, 211, .3) !important;
  border-radius: 3px 3px 0 0;
}
.cart-items-header,
.cart-items-header > :first-child {
  color: rgba(255, 255, 255, .88) !important;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 2.4px;
  text-transform: uppercase;
}
.cart-items-header button,
.cart-items-header .clear-cart-btn,
.cart-items-header [class*="clear"] {
  padding: 6px 13px;
  border: 1px solid rgba(255, 255, 255, .12) !important;
  border-radius: 3px;
  background: transparent !important;
  color: rgba(255, 255, 255, .5) !important;
  font-size: 8.5px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  cursor: pointer;
  transition: color .3s var(--ease-smooth, ease), border-color .3s var(--ease-smooth, ease);
}
.cart-items-header button:hover,
.cart-items-header .clear-cart-btn:hover,
.cart-items-header [class*="clear"]:hover {
  color: #ff6b7a !important;
  border-color: rgba(255, 107, 122, .4) !important;
}

/* ─── 32 · The rail sits INSIDE a page container when it has one ────────
   The rail is a full-width band on the shop and product pages, where its
   parent is the page root — so it sizes itself against the viewport and
   centres. In the bag its parent is the page's own `.container`, and
   sizing against the viewport there left it 1200px inside a 1209px content
   box: four and a half pixels narrower than the `.cart-layout` directly
   above it, on both edges. Two blocks that should share an edge missing it
   by a hairline reads worse than a large difference would.

   Given a container to live in, the rail fills it and drops its own cap —
   it takes the column its siblings take, and the seam disappears. */
.container > .recommendations-full-width,
.ch-wrap > .recommendations-full-width,
.cart-page > .container > .recommendations-full-width {
  width: 100% !important;
  max-width: none !important;
  margin-left: 0 !important;
  margin-right: 0 !important;
}


/* ─── 33 · The lookbook, brought onto the platform's vocabulary ──────────
   The page itself is good — the editorial grid, the philosophy split and the
   release carousel all work. What made it read as a page from an earlier
   draft of the site is that it was still speaking the OLD visual language,
   the one sections 01, 19, 20 and 24 replaced everywhere else:

   1 · Four different border colours on one page. The hero and the newsletter
       used `--border-purple` (25% purple), the gallery frame 20% purple, the
       philosophy panel 12%, and a release card `rgba(255,255,255,.06)`.
       Walking down the lookbook, the same 1px frame changed colour four
       times. Everywhere else on the platform it is `--ng-line`.

   2 · Cards still glowed. `.lb-release:hover` lifted a purple box-shadow,
       which is exactly the treatment sections 11 and 19 took off the product
       cards in favour of a border that brightens. `.lb-nl-glow` is a blurred
       purple blob behind the newsletter — the same ornament, unattached to
       any edge.

   3 · Two grids had no separation at all. `.lb-models-grid` is `gap: 0`, so
       four portraits touch; `.lb-gallery` has a 1px gap filled with
       `#8a2be214` — an 8% purple which, on near-black, renders as nothing.
       This is the precise fault section 24 diagnosed in the sidebar, and it
       gets the same answer: divide at the weight the rest of the platform
       divides at.

   4 · The micro-labels drifted. The platform's small uppercase label is 8px
       at 2.4px tracking, weight 600 (section 28, `.sfg-label`); the lookbook
       had 7px/1.5px, 9px/2px and 8px/1.5px depending on where you looked.

   Nothing here changes the layout, the photography treatment or the page's
   character. It is the same page in the platform's materials. */

/* ── 1 · One border colour ──────────────────────────────────────────── */
.lb-hero,
.lb-gallery-frame,
.lb-philosophy,
.lb-newsletter,
.lb-release{
  border-color: var(--ng-line) !important;
  border-image: none !important;
}

/* The hero sits directly on top of the gallery frame and shares its edge,
   so it keeps its open bottom and the frame carries the seam. */
.lb-hero{
  border-radius: 2px 2px 0 0 !important;
}
.lb-gallery-frame{
  border-radius: 2px !important;
}

/* The frame's two decorative veils — a diagonal purple wash over the whole
   grid and a purple gradient hairline along the bottom — were standing in
   for an edge the frame did not have. It has one now. */
.lb-gallery-frame:before,
.lb-gallery-frame:after{
  content: none !important;
  display: none !important;
}

/* ── 2 · Definition instead of glow ─────────────────────────────────── */
.lb-release:hover{
  border-color: rgba(138, 43, 226, .45) !important;
  box-shadow: none !important;
}

.lb-nl-glow{
  display: none !important;
}

/* The newsletter panel keeps its own quiet gradient; without the blob behind
   it the box needs a little more air to stop reading as cramped.
   !important throughout this section: the route chunk's stylesheet is
   injected when /lookbook loads, which puts it AFTER ng-polish.css in the
   cascade at equal specificity, so an unflagged declaration here loses to
   the value it is meant to replace. */
.lb-newsletter{
  min-height: 184px !important;
}
.lb-nl-inner{
  padding: 34px 32px !important;
  gap: 13px !important;
}
.lb-nl-form{
  border-color: var(--ng-line) !important;
}
.lb-nl-form:focus-within{
  border-color: rgba(138, 43, 226, .5) !important;
}

/* ── 3 · Grids that are actually divided ────────────────────────────── */
.lb-gallery{
  background: var(--ng-line) !important;
}

.lb-models-grid{
  gap: 1px !important;
  background: var(--ng-line) !important;
}

/* A model card is a photo on a near-black tile; against the new 1px rule it
   needs its own background or the gap disappears into the card. */
.lb-model-card{
  background: #0a0a0a !important;
}

.lb-model-social a{
  border-color: var(--ng-line) !important;
}
.lb-model-social a:hover{
  border-color: rgba(138, 43, 226, .55) !important;
}

/* The release carousel's page dots: grey at rest like every other inactive
   control on the platform, purple when they mean something. */
.lb-releases-tab{
  background: var(--ng-line) !important;
}
.lb-releases-tab.active{
  background: var(--purple-light, #ba55d3) !important;
}
.lb-releases-nav{
  border-color: var(--ng-line) !important;
}
.lb-releases-nav:hover{
  border-color: rgba(138, 43, 226, .55) !important;
}

/* ── 4 · The micro-label, at one size ───────────────────────────────── */
.lb-cd-label{
  font-size: 8px !important;
  font-weight: 600 !important;
  letter-spacing: 2.4px !important;
  color: rgba(186, 85, 211, .5) !important;
}

.lb-model-role{
  letter-spacing: 2.4px !important;
  font-weight: 600 !important;
}

.lb-philosophy-value-label{
  font-size: 8px !important;
  font-weight: 600 !important;
  letter-spacing: 2.4px !important;
}

/* The release tag matches the product badges exactly (section 19). */
.lb-release-tag{
  border-radius: 3px !important;
  letter-spacing: 1px !important;
  padding: 3px 8px !important;
}

/* The philosophy panel's value divider was a 10% purple — the same
   invisible-on-black line as the grids above. */
.lb-philosophy-values{
  border-top-color: var(--ng-line) !important;
}

/* The countdown reads as a unit of three, so it gets the tracking to match
   its new labels without the digits drifting apart. */
.lb-countdown{
  gap: 14px !important;
}
.lb-cd-val{
  letter-spacing: 1px !important;
}

@media (max-width: 768px){
  /* On a phone the model grid is two columns; the 1px rule still applies,
     and the newsletter goes back to its tighter padding. */
  .lb-nl-inner{ padding: 26px 22px !important; }
  .lb-newsletter{ min-height: 0 !important; }
}


/* THE SHEET URL CARRIES A VERSION, AND IT HAS TO.
   `.htaccess` sends `no-cache, must-revalidate` for css/js but gives images
   `access plus 1 month`, and these sprite URLs had no query string. So when
   this sheet grew from six columns to seven, phones kept the six-column PNG
   for a month and stretched it across `background-size: 700%` — every cell
   landed 6/7 of a column off, and the header's cart drew the wishlist heart
   sitting one cell to its left. The CSS was right and the image was old.
   Bump the version whenever a sheet is re-cut. */

/* ─── 34 · Header navigation — the neon sprites ─────────────────────────
   The six header links now carry the drawn artwork instead of a Font
   Awesome glyph: white at rest, purple on hover and on the current page.

   HOW THE ARTWORK IS ADDRESSED
   `assets/ng-nav-sprite.png` is one sheet, seven columns by two rows — the
   white set on the top row, the purple set directly beneath it, cut from
   the two supplied sheets on identical bounds so the two states register
   exactly. One sheet rather than twelve files is not only fewer requests:
   both states arrive in the same image, so the purple never has to be
   fetched at the moment the pointer lands on a link. A hover that has to
   download its own artwork shows a blank frame the first time, every time,
   and preloading it is a workaround for a problem that a single sheet does
   not have.

   The cell is chosen by `data-nav` on the link (see `ty` and the header in
   main/20-app.jsx). Not by href, which is a routing detail, and not by the
   Font Awesome class, which cannot tell Lookbook from Blog — both were
   drawn with fa-book-open. A link with no `data-nav` is not touched at all;
   Help was the last one left on its glyph and now has its own cell.

   WHY A SECOND LAYER RATHER THAN A SECOND background-position
   The states have to cross-fade, to match the .5s colour transitions the
   rest of the header uses. `background-position` cannot fade between two
   cells — it would slide the sheet across the box. So the white cell sits
   on the <i> itself and the purple cell sits on its ::after, stacked, and
   only the opacity moves. ::after is free because Font Awesome draws its
   glyph in ::before, which is also what we switch off. */

:is(.nav-link, .header-icon-btn)[data-nav] i{
  position: relative;
  display: block;
  width: 37px;
  height: 32px;
  /* no glyph, and no stray text node either */
  font-size: 0;
  line-height: 0;
  background-image: url(/assets/ng-nav-sprite.png?v=2220);
  background-repeat: no-repeat;
  background-size: 700% 200%;                  /* 7 columns, 2 rows */
  background-position: calc(var(--ng-nav-col) * 16.6667%) 0%;
  transition: transform .5s var(--ease-luxe, ease);
}

/* Font Awesome's glyph lives in ::before. */
:is(.nav-link, .header-icon-btn)[data-nav] i:before{
  content: none !important;
}

:is(.nav-link, .header-icon-btn)[data-nav] i:after{
  content: "";
  position: absolute;
  inset: 0;
  background-image: url(/assets/ng-nav-sprite.png?v=2220);
  background-repeat: no-repeat;
  background-size: 700% 200%;
  background-position: calc(var(--ng-nav-col) * 16.6667%) 100%;   /* the purple row */
  opacity: 0;
  transition: opacity .45s ease;
  pointer-events: none;
}

/* Seven columns; the k-th of n cells sits at k/(n-1) of the range, so the
   step went from 20% to 16.6667% when Help arrived and every column above it
   moved. That is the whole cost of adding one: three numbers, no new file,
   and no second request for a state that has to be there before the pointer
   lands. */
[data-nav="shop"]    { --ng-nav-col: 0; }
[data-nav="lookbook"]{ --ng-nav-col: 1; }
[data-nav="blog"]    { --ng-nav-col: 2; }
[data-nav="account"] { --ng-nav-col: 3; }
[data-nav="wishlist"]{ --ng-nav-col: 4; }
[data-nav="cart"]    { --ng-nav-col: 5; }
/* Help shows in the header-actions row in place of Wishlist when nobody is
   signed in, so a logged-out visitor now gets artwork across the whole bar
   rather than five drawings and a glyph. */
[data-nav="help"]    { --ng-nav-col: 6; }

:is(.nav-link, .header-icon-btn)[data-nav]:hover i:after,
:is(.nav-link, .header-icon-btn)[data-nav]:focus-visible i:after,
:is(.nav-link, .header-icon-btn)[data-nav].active i:after,
:is(.nav-link, .header-icon-btn)[data-nav][aria-current="page"] i:after,
:is(.nav-link, .header-icon-btn)[data-nav].ng-nav-current i:after{
  opacity: 1;
}

/* The lift the glyphs had, kept. The colour transition that went with it is
   meaningless now — the artwork is an image, not a font — so the rules that
   set `color` on these icons simply have nothing to act on. */
:is(.nav-link, .header-icon-btn)[data-nav]:hover i{
  transform: translateY(-1px);
}

/* The row is icon-over-label already; the sprite is taller than the 13px
   glyph it replaces, so the gap tightens to keep the whole link inside the
   80px header — and inside the 56px it shrinks to. */
:is(.nav-link, .header-icon-btn)[data-nav]{
  gap: 3px;
  padding-top: 2px;
  padding-bottom: 2px;
}

/* Shrunk header: 56px leaves no room for a 32px icon plus the label, so the
   artwork comes down with it. background-size is a multiple of the box, so
   the cell follows the box and nothing needs recalculating. */
.header-shrunk :is(.nav-link, .header-icon-btn)[data-nav] i{
  width: 27px;
  height: 23px;
  transition: width .5s var(--ease-luxe, ease), height .5s var(--ease-luxe, ease),
              transform .5s var(--ease-luxe, ease);
}

/* The cart badge is positioned against the link, not the glyph, so it needs
   nothing here — but it must stay above the purple layer. */
.cart-link .cart-badge{ z-index: 2; }

@media (max-width: 1024px){
  :is(.nav-link, .header-icon-btn)[data-nav] i{ width: 33px; height: 29px; }
}

/* The nav is hidden below 768px (the mobile tab bar takes over), so there is
   nothing to size there. */

@media (prefers-reduced-motion: reduce){
  :is(.nav-link, .header-icon-btn)[data-nav] i,
  :is(.nav-link, .header-icon-btn)[data-nav] i:after{ transition: none; }
  :is(.nav-link, .header-icon-btn)[data-nav]:hover i{ transform: none; }
}

/* The current page reads like a hovered link that stays put: the label
   brightens and the artwork is purple. `.ng-nav-current` is set by the
   header itself (see ly() in main/20-app.jsx) because the router's Link
   marks neither an active class nor aria-current.

   This half applies to every header link, sprite or not — it did the work
   for Help before Help had artwork, and still covers any link added later
   that has none. */
.nav-link.ng-nav-current,
.header-icon-btn.ng-nav-current{
  color: rgba(255, 255, 255, .95);
}
.nav-link.ng-nav-current:not([data-nav]) i,
.header-icon-btn.ng-nav-current:not([data-nav]) i{
  color: var(--purple-light, #ba55d3);
}


/* ─── 35 · The category sidebar divider, quieted ────────────────────────
   Section 24 gave these rows a full-width `border-top` in `--ng-line`
   (rgb 45,44,45) because the separator section 21 had drawn as a `::after`
   was being cancelled by `.category-item:after{display:none!important}` in
   storefront.css — so the list genuinely had no divider, and a border was
   the quickest thing that would survive that.

   It survived rather too well. Ten opaque full-width rules ran down a 232px
   column that already carries ten lit neon tiles and ten count badges, and
   between them the sidebar stopped reading as a list of categories and
   started reading as a ruled table. The rows were never the thing that
   needed emphasis; the icons already separate them.

   What replaces it is the divider section 21 wanted in the first place:

     · On the <li>, as a background image. No border, so it cannot fight
       section 21's transparent one; no pseudo-element, so the blanket
       `:after{display:none}` cannot silently kill it a second time.
     · Faded at both ends instead of running edge to edge. A line that
       stops short of the panel walls reads as a seam between two rows
       rather than as a rule drawn across the column.
     · Inset past the icon tile, so the glyphs form one continuous rail
       down the left and the divider belongs to the text.
     · White at 6% rather than the platform line grey. Against the near-
       black panel that is present when you look for it and absent when you
       are reading labels, which is the whole job.

   The hover and active states already give a row its own edge — a purple
   left rail and a lit background — so at rest the list needs almost
   nothing, and now has almost nothing. */
.shop-page .category-list li + li,
.product-page .category-list li + li,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list li + li{
  background-image: linear-gradient(
    90deg,
    transparent 0,
    rgba(255, 255, 255, .06) 18%,
    rgba(255, 255, 255, .06) 68%,
    transparent 100%
  );
  background-size: calc(100% - 52px) 1px;   /* starts past the 38px tile and its gap */
  background-position: 44px 0;
  background-repeat: no-repeat;
}

/* The header rule under "All Products" is the one break in the column that
   means something, so it keeps a little more weight — and the same fade, so
   it belongs to the same system rather than looking like a leftover. */
.shop-page .category-list .ng-cat-all-li,
.product-page .category-list .ng-cat-all-li,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .ng-cat-all-li{
  background-image: linear-gradient(
    90deg,
    transparent 0,
    rgba(255, 255, 255, .1) 10%,
    rgba(255, 255, 255, .1) 78%,
    transparent 100%
  );
  background-size: calc(100% - 16px) 1px;
  background-position: 8px 100%;            /* under the row, not over it */
  background-repeat: no-repeat;
}

/* The row after the header already has the header's rule above it. */
.shop-page .category-list .ng-cat-all-li + li,
.product-page .category-list .ng-cat-all-li + li,
/* The Edit's sidebar is this same component - see section 41. */
.blog-sidebar .category-list .ng-cat-all-li + li{
  background-image: none;
}


/* ─── 36 · Footer social — the neon sprites ─────────────────────────────
   Same mechanism as the header navigation in section 34: one sheet, four
   columns by two rows, white on top and purple beneath, so both states are
   in the image and the purple is never fetched at the moment the pointer
   arrives. The cell is chosen by `data-social`, which the footer sets from
   whatever the CMS gives it (see ngSocialSprite in main/20-app.jsx).

   The difference from the header: these sprites are whole buttons. The
   artwork draws its own ring, so the CSS circle that used to hold a Font
   Awesome glyph has to come off, or the link renders two concentric rings
   a pixel apart. Border, background and the hover glow all go; what is left
   is the artwork and the lift.

   A link with no `data-social` keeps the circle and the glyph exactly as
   before — which is what Pinterest does today, since the supplied sheet
   covers Instagram, TikTok, X and YouTube. */

.footer-social .social-link[data-social]{
  position: relative;
  width: 38px;
  height: 34px;
  border: none !important;
  background: none !important;
  box-shadow: none !important;
  border-radius: 0;
  /* the ring is drawn, so the box is only a frame for it */
  background-image: url(/assets/ng-social-sprite.png) !important;
  background-repeat: no-repeat !important;
  background-size: 400% 200% !important;                        /* 4 columns, 2 rows */
  background-position: calc(var(--ng-soc-col) * 33.3333%) 0% !important;
}

/* Font Awesome draws its glyph in ::before, which leaves ::after free for
   the purple layer — the two states cross-fade rather than jumping, because
   background-position cannot be animated between cells without sliding the
   whole sheet across the box. */
.footer-social .social-link[data-social] i{
  display: none;
}

.footer-social .social-link[data-social]:after{
  content: "";
  position: absolute;
  inset: 0;
  background-image: url(/assets/ng-social-sprite.png);
  background-repeat: no-repeat;
  background-size: 400% 200%;
  background-position: calc(var(--ng-soc-col) * 33.3333%) 100%;  /* the purple row */
  opacity: 0;
  transition: opacity .45s ease;
  pointer-events: none;
}

.footer-social .social-link[data-social]:hover:after,
.footer-social .social-link[data-social]:focus-visible:after{
  opacity: 1;
}

/* Four columns: the k-th of n cells sits at k/(n-1) of the range. */
[data-social="instagram"]{ --ng-soc-col: 0; }
[data-social="tiktok"]   { --ng-soc-col: 1; }
[data-social="x"]        { --ng-soc-col: 2; }
[data-social="youtube"]  { --ng-soc-col: 3; }

/* The row mixes sprite buttons (38px, ring drawn in) with glyph buttons
   (30px circle) while any platform is still on a glyph, so it centres on
   the optical middle rather than letting the taller boxes sit the shorter
   ones off-baseline. */
.footer-social{
  align-items: center;
}

@media (prefers-reduced-motion: reduce){
  .footer-social .social-link[data-social]:after{ transition: none; }
}


/* ─── 37 · The apply page ───────────────────────────────────────────────
   Uploads, step motion and palette. The behaviour lives in
   routes/parts/apply-live.jsx; this is what it needs to look like.

   ── Step motion ──────────────────────────────────────────────────────
   Steps are swapped with `display`, so moving between them changes the
   height of content ABOVE the viewport, and Chrome's scroll anchoring
   reacts by shifting the scroll position to keep the visual position
   stable. That adjustment lands before the step-change scroll animation
   begins, so the page jolted 260px in the wrong direction and then glided
   back — measured, frame by frame: 522 → 782 → 750 → 613 → 414 → 261 → 212,
   one direction reversal.

   Anchoring is the right default for a page whose content loads in around
   the reader. It is the wrong one for a wizard that explicitly decides
   where the viewport should be. Off here, and the only thing moving the
   page is the step-change scroll. */
/* On the SCROLLING element. Setting it on `.app-container` (the step cards'
   parent) does the opposite of what is wanted: it excludes that subtree from
   being chosen as the anchor, so the browser picks something further down —
   the footer — and holds THAT still instead, which is how a step change from
   the top of the page threw the viewport 768px to the bottom.

   Anchoring is the right default for a page whose content loads in around
   the reader. It is the wrong one for a wizard that decides for itself where
   the viewport belongs. */
html.ng-apply,
html.ng-apply body{
  overflow-anchor: none;
}

/* ── The chosen-files list ────────────────────────────────────────────
   New markup (ApplyFileRow in parts/apply-live.jsx), so it needs styling
   from nothing. It is built from the platform's own parts: an --ng-line
   hairline, a 2px radius, purple only for state. The thumbnail is the point
   — a portfolio submission is photographs, and a list of file names tells
   the applicant nothing about whether they picked the right ones. */
.apply-files{
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.apply-file{
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  padding: 7px 9px;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: linear-gradient(135deg, #0c0c0e, #101012);
  position: relative;
  overflow: hidden;
  transition: border-color .35s var(--ease-smooth, ease), background .35s var(--ease-smooth, ease);
}

.apply-file-thumb{
  width: 38px;
  height: 38px;
  border-radius: 2px;
  border: 1px solid var(--ng-line);
  background-size: cover;
  background-position: center;
  background-color: #111;
  flex-shrink: 0;
}
.apply-file-thumb-doc{
  display: flex;
  align-items: center;
  justify-content: center;
  border-color: rgba(138, 43, 226, .3);
  background-color: rgba(138, 43, 226, .08);
  color: var(--purple-light, #ba55d3);
  font-size: 13px;
}

.apply-file-meta{
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.apply-file-name{
  font-size: 11px;
  font-weight: 400;
  color: rgba(255, 255, 255, .9);
  letter-spacing: .15px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.apply-file-sub{
  font-size: 9px;
  font-weight: 300;
  letter-spacing: .3px;
  color: rgba(255, 255, 255, .45);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The remove control. It is the only thing on the row that is pressable, so
   it gets the only hover state. */
.apply-file-x{
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: transparent;
  color: rgba(255, 255, 255, .35);
  font-size: 10px;
  cursor: pointer;
  transition: color .3s ease, border-color .3s ease, background .3s ease;
}
.apply-file-x:hover{
  color: #fff;
  border-color: rgba(220, 80, 80, .55);
  background: rgba(220, 80, 80, .12);
}

.apply-file-ok{
  font-size: 11px;
  color: #8ee6a8;
}

/* Progress. A 20MB photo on a phone is a long silence otherwise, so the bar
   sits along the bottom edge of the row it belongs to rather than as a
   separate element competing for space. */
.apply-file-bar{
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: rgba(255, 255, 255, .05);
}
.apply-file-bar > span{
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--purple, #8a2be2), var(--purple-light, #ba55d3));
  transition: width .18s linear;
}

.apply-file-sending{ border-color: rgba(138, 43, 226, .4); }
.apply-file-done{
  border-color: rgba(138, 43, 226, .3);
  background: linear-gradient(135deg, rgba(138, 43, 226, .06), rgba(138, 43, 226, .1));
}
.apply-file-error{
  border-color: rgba(220, 80, 80, .5);
  background: linear-gradient(135deg, rgba(220, 80, 80, .05), rgba(220, 80, 80, .09));
}
.apply-file-error .apply-file-sub{ color: #f6b0b0; }

@media (max-width: 480px){
  .apply-file{ grid-template-columns: 30px minmax(0, 1fr) auto; gap: 9px; }
  .apply-file-thumb{ width: 30px; height: 30px; }
}

/* ── Palette ──────────────────────────────────────────────────────────
   The apply page was built before the platform settled on one hairline
   (section 20) and still spoke the older language: panels framed with the
   `--border-blend` gradient, inputs on `rgba(255,255,255,.08)`, and a
   progress rail whose completed steps were GREEN — the one place on the
   platform where success is not purple. Alongside the index and shop pages
   it reads as a page from a different site.

   Nothing here changes the layout. It is the same form in the platform's
   materials. */

/* Panels: the gradient border-image has to be cleared for a solid colour
   to show at all. */
.f-card,
.f-nav{
  border: 1px solid var(--ng-line) !important;
  border-image: none !important;
}

/* Fields. `rgba(255,255,255,.08)` is a touch brighter than the platform
   line and, being white rather than grey, reads slightly blue against the
   near-black panel. */
.fld-input,
.fld-select,
.fld-textarea{
  border-color: var(--ng-line) !important;
}
.fld-input:focus,
.fld-select:focus,
.fld-textarea:focus{
  border-color: rgba(186, 85, 211, .55) !important;
  background: rgba(138, 43, 226, .04) !important;
}

.upload-zone{
  border: 1px dashed var(--ng-line) !important;
}
.upload-zone:hover{
  border-color: rgba(138, 43, 226, .45) !important;
  background: rgba(138, 43, 226, .03) !important;
}
/* The drop state was styled but never reachable — nothing added the class.
   It is wired now (see useApplyZones), so it wants to be unmistakable: this
   is the one moment the applicant needs to know the page has hold of their
   photos. */
.upload-zone.dragover{
  border-color: var(--purple-light, #ba55d3) !important;
  border-style: solid !important;
  background: rgba(138, 43, 226, .08) !important;
}

/* Labels, at the platform's micro-label tracking. `#ffffff65` on a
   near-black panel is at the edge of legible for 9px type, which matters
   more here than anywhere else on the site: these are the questions. */
.fld-label{
  font-size: 9px !important;
  letter-spacing: 2.2px !important;
  color: rgba(255, 255, 255, .72) !important;
}

.uz-text{ color: rgba(255, 255, 255, .55) !important; }
.uz-hint{ color: rgba(255, 255, 255, .3) !important; }
.uz-icon{ color: rgba(186, 85, 211, .4) !important; }

/* The progress rail.
   `.p-name` was 7px at 9% white — present in the markup, invisible on
   screen, so the five steps had numbers and no names. And a completed step
   turned green (#22c55e), which is the platform's colour for nothing at
   all. Purple is what every other "this is done" on the site uses. */
.p-name{
  font-size: 8px !important;
  letter-spacing: 1.8px !important;
  color: rgba(255, 255, 255, .34) !important;
}
.p-name.active{ color: rgba(255, 255, 255, .92) !important; }
.p-name.done{ color: rgba(186, 85, 211, .7) !important; }

.p-dot{
  border-color: var(--ng-line) !important;
  color: rgba(255, 255, 255, .35) !important;
}
.p-dot.done{
  border-color: rgba(186, 85, 211, .6) !important;
  background: rgba(138, 43, 226, .14) !important;
  color: var(--purple-light, #ba55d3) !important;
}
.p-line{ background: var(--ng-line) !important; }

/* "Step 1 of 5" rendered as "Step1of 5".
   `.nav-info` is a flex container, and the step number is a <span> between
   two text nodes. Flex makes each of the three a flex item, and the spaces
   at the item boundaries are dropped — the markup is correct, the layout
   ate it. A gap puts the spacing back without touching the markup. */
.nav-info{
  gap: 4px !important;
  color: rgba(255, 255, 255, .42) !important;
  letter-spacing: 1px !important;
}

/* The status panels, now that they live inside the container. */
.apply-submit-error,
.apply-submit-note{
  display: flex !important;
  flex-direction: column !important;
  gap: 3px !important;
  margin-top: 10px !important;
  padding: 12px 14px !important;
  border: 1px solid var(--ng-line) !important;
  border-radius: 2px !important;
  font-size: 10.5px !important;
  font-weight: 300 !important;
  line-height: 1.6 !important;
  letter-spacing: .2px !important;
}
.apply-submit-error{
  border-color: rgba(220, 80, 80, .45) !important;
  background: rgba(220, 80, 80, .07) !important;
  color: rgba(255, 255, 255, .78) !important;
}
.apply-submit-error strong{
  font-size: 9px !important;
  font-weight: 600 !important;
  letter-spacing: 1.8px !important;
  text-transform: uppercase !important;
  color: #f6b0b0 !important;
}
.apply-submit-note{
  border-color: rgba(138, 43, 226, .35) !important;
  background: rgba(138, 43, 226, .06) !important;
  color: rgba(255, 255, 255, .72) !important;
}

/* The current step has to outrank both the base dot and the completed ones,
   and the base rules above carry !important, so it says so explicitly.
   Hierarchy on the rail, top to bottom: current (filled, brightest), done
   (purple outline), still to come (grey outline). */
.p-dot.active{
  border-color: var(--purple-light, #ba55d3) !important;
  background: linear-gradient(135deg, rgba(138, 43, 226, .35), rgba(186, 85, 211, .25)) !important;
  color: #fff !important;
  box-shadow: 0 0 14px rgba(138, 43, 226, .3) !important;
}
.p-name.active{
  color: #fff !important;
  font-weight: 600 !important;
}


/* ═══════════════════════════════════════════════════════════════════════
   38 · MOBILE
   ═══════════════════════════════════════════════════════════════════════
   The phone layout was built to the same numbers as the desktop one. That
   is the whole problem: a 7px badge and a 9px paragraph are fine on a 27"
   monitor at arm's length and are not readable in a hand. A measured sweep
   of ten storefront pages at 390px found text under 10px in 99 distinct
   classes, and interactive controls as small as 20×11.

   What this section does NOT do is redesign anything. The phone keeps its
   own shape — the bottom tab bar, the off-canvas category drawer, the
   horizontal rails — because that shape is right. What changes is the
   metric it is drawn at: type, touch size, gutters and rhythm.

   Everything is inside `@media (max-width: 768px)`, and almost everything
   carries !important, because the per-route stylesheets are injected when
   their route loads and therefore land AFTER this file. A media query adds
   no specificity of its own.
   ═══════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px){

  /* ── A · Foundations ─────────────────────────────────────────────── */

  :root{
    /* One gutter everywhere. Pages variously used 12, 16, 20 and 32px. */
    --ng-m-gutter: 16px;
    --ng-m-tabbar: 68px;
  }

  /* Nothing is hidden behind the tab bar.
     The bar is 68px and fixed; body padding was 56px, so the last 12px of
     every page — which on the shop grid is the bottom of a product card —
     sat underneath it. Plus the home-indicator inset on phones that have
     one, which is why this is not simply 68px. */
  body{
    padding-bottom: calc(var(--ng-m-tabbar) + env(safe-area-inset-bottom, 0px)) !important;
  }

  /* ── The type floor ───────────────────────────────────────────────
     Grouped by the job the text does rather than by page, so the same kind
     of text is the same size wherever it appears — which is the consistency
     the desktop already has and the phone did not.

     Tracking comes down as size goes up: letter-spacing that reads as
     luxurious at 8px on a wide screen just eats horizontal room at 11px on
     a narrow one, and these labels are the things that were wrapping. */

  /* Buttons and anything else you press to go somewhere — 11px. These are
     also the controls the tap-target rules below enlarge, so they are the
     ones that most need to be readable. */
  :is(.rec-action-btn, .arrival-add-btn, .compact-add-to-cart-btn, .card-add-btn,
      .mag-card-btn, .sort-btn, .help-tab, .faq-chip, .mcp-pill, .sfg-btn-text,
      .compact-view-more-btn, .review-action-btn, .slide-cta, .lb-release-cta,
      .search-tag-item, .sticky-cat-link, .view-more-btn, .btn, .action-btn){
    font-size: 11px !important;
    letter-spacing: .8px !important;
  }

  /* Badges and tags — smaller than body by design, but not 7px. */
  :is(.tier-tag, .product-badge, .arrival-badge, .card-badge, .compact-badge,
      .mag-card-tag, .lb-g-tag, .lb-release-tag, .blog-category-compact,
      .perks-chip, .order-status, .nav-badge){
    font-size: 9.5px !important;
    letter-spacing: .6px !important;
  }

  /* Supporting metadata: dates, authors, counts under a figure. */
  :is(.mag-card-sublabel, .mag-card-date, .blog-author, .blog-date,
      .lb-release-date, .response-time, .lb-model-stat, .lb-model-role,
      .trending-meta, .hero-meta-item, .os-ship-opt-time, .mag-stat-label,
      .stat-label, .recently-viewed-name, .uz-hint){
    font-size: 10.5px !important;
    letter-spacing: .4px !important;
  }

  /* Body copy. Anything a visitor is expected to READ rather than scan. */
  :is(.blog-excerpt, .mag-card-summary, .response-text, .report-text,
      .perk-desc, .category-header-subtitle, .sidebar-subtitle,
      .compact-subcategory-subtitle, .uz-text, .welcome-text p,
      .lb-philosophy-body, .lb-hero-desc, .lb-nl-sub){
    font-size: 11.5px !important;
    line-height: 1.65 !important;
    letter-spacing: .2px !important;
  }

  /* Form labels — the questions. These carry the most tracking on desktop
     and are the first to wrap on a phone. */
  :is(.fld-label, .form-label, .field-label, .lbl, .section-label, .sfg-label,
      .os-label, .cart-label){
    font-size: 10.5px !important;
    letter-spacing: 1.4px !important;
  }

  /* Counts and small numerals. */
  :is(.category-count, .sidebar-count, .blog-stat-item, .trending-number,
      .sp-stat-item, .sp-stat-value, .p-dot, .mag-stat-value){
    font-size: 10.5px !important;
  }

  /* Names and titles of things in a list. */
  :is(.category-name, .compact-product-title, .response-author, .perk-title,
      .p-name, .recommendation-title, .arrival-title){
    font-size: 11px !important;
    letter-spacing: .5px !important;
  }

  /* The product title is fitted per-card by script on desktop (data-ng-fit).
     That calculation is tuned for a 141px card; at phone width the cards are
     wider and the result is needlessly small. */
  .product-card .product-title[data-ng-fit]{
    font-size: 11.5px !important;
    letter-spacing: .3px !important;
  }

  /* ── Touch targets ───────────────────────────────────────────────
     44px is the figure both platform guidelines settle on. Rather than set
     a height — which would stretch things that are deliberately compact —
     the rules below add padding where there is room and set a minimum box
     where there is not. */

  /* `display` is deliberately NOT set here. The phone header hides its nav
     links (`.header-left, .nav-group, .btn-label { display: none }`) and
     hands navigation to the bottom tab bar; forcing `display: flex` brought
     them back as 4px-wide slivers — a row of invisible tap targets along the
     top of every page. Only elements that are already laid out are resized,
     which is what `:not([hidden])` and the display check below express. */
  :is(.mobile-tab, .timeline-link, .help-tab, .sort-btn,
      .faq-chip, .mcp-pill, .sticky-cat-link){
    min-height: 44px !important;
    align-items: center !important;
  }
  /* The header's own controls, only where the phone layout still shows them. */
  .header-right :is(.nav-link, .header-icon-btn){
    min-height: 44px !important;
    align-items: center !important;
  }

  /* Icon-only controls: square, and big enough to hit without aiming. */
  :is(.timeline-search-btn, .arrival-favorite, .recommendation-wishlist-btn,
      .recently-viewed-wishlist-btn, .favorite-btn, .apply-file-x,
      .carousel-btn, .lb-releases-nav, .qty-btn){
    min-width: 40px !important;
    min-height: 40px !important;
  }

  /* Form controls. 16px on the input itself is not a style choice: iOS
     zooms the whole page when a focused input is below 16px, and the page
     does not zoom back out. */
  /* The exclusions are load-bearing, not tidiness:
       · [type=hidden] and .ng-hp are NightGuard's honeypot — a field that
         must stay invisible to a human and fillable by a bot. Giving it a
         46px box put a real, hittable input on the page and would have
         started failing legitimate sign-ups.
       · A file input is the invisible layer over an upload zone; sizing it
         would drag the zone's geometry with it. */
  :is(input, select, textarea):not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not(.ng-hp){
    font-size: 16px !important;
    min-height: 46px !important;
  }
  /* Belt and braces: whatever else happens, the honeypot has no size. */
  .ng-hp{
    min-height: 0 !important;
    min-width: 0 !important;
    font-size: inherit !important;
  }
  textarea{ min-height: 96px !important; }

  /* Checkboxes stay small but get a bigger hit area through their label. */
  :is(input[type=checkbox], input[type=radio]){
    min-width: 20px !important;
    min-height: 20px !important;
  }
}

@media (max-width: 768px){

  /* ── B · Chrome ──────────────────────────────────────────────────── */

  /* The category drawer's toggle sat at y 56–96 while the breadcrumb began
     at y 78 — an 18px overlap, with the button's circle landing on top of
     the first crumb. It moves below the breadcrumb and becomes a labelled
     control rather than a bare circle floating over other content. */
  .sidebar-toggle{
    position: sticky !important;
    top: calc(var(--header-h-shrunk, 56px) + 8px) !important;
    left: auto !important;
    margin: 0 var(--ng-m-gutter) 12px !important;
    width: auto !important;
    height: 42px !important;
    padding: 0 16px !important;
    gap: 9px;
    border-radius: 2px !important;
    background: rgba(138, 43, 226, .12) !important;
    border: 1px solid var(--ng-line) !important;
    font-size: 10.5px !important;
    letter-spacing: 1.6px !important;
    text-transform: uppercase;
    color: rgba(255, 255, 255, .8) !important;
  }
  .sidebar-toggle:after{
    content: "Categories";
    font-weight: 500;
  }

  /* The breadcrumb.
     Stacked (path above, search below) since the last pass. What it still
     had was desktop's 40px connector lines, which on a 390px screen pushed
     the crumbs apart and left the active dot sitting on top of a label.
     Short connectors, tighter crumbs, and the whole strip loses a third of
     its height. */
  .timeline-breadcrumb-wrapper{
    padding: 10px var(--ng-m-gutter) !important;
    margin-bottom: 12px !important;
  }
  .timeline-line, .timeline-line-extended{
    flex: 0 0 14px !important;
    min-width: 14px !important;
    margin: 0 6px !important;
  }
  .timeline-item{ gap: 6px !important; }
  .timeline-link{
    font-size: 10.5px !important;
    letter-spacing: .9px !important;
    /* the min-height above makes these 44px tall; on a scrolling strip the
       row only needs to be tappable, not tall */
    min-height: 32px !important;
  }
  .timeline-dot{ width: 7px !important; height: 7px !important; }
  .timeline-item.active .timeline-dot{ width: 10px !important; height: 10px !important; }

  /* ── Horizontal rails read as scrollable ─────────────────────────
     Ten of the eleven "overflowing" elements the audit found were inside a
     deliberate horizontal scroller — the rails, the sort row, the help
     tabs. They are not broken, but at 390px they clip mid-word, and a row
     that ends in half a letter looks like a bug rather than an invitation
     to swipe. A fade on the trailing edge says "there is more" without
     adding a control. */
  /* The same measured list ng-mobile.js uses. The first version of both was
     written from class names the markup appeared to use; half did not
     exist, and the home page's two product rails — which carry the whole
     offering — were not in it. */
  :is(.compact-products-grid, .new-arrivals-grid, .recommendations-grid,
      .perks-chips, .sfg-options, .timeline-path, .lb-releases-slide,
      .mobile-cat-pills, .hero-meta, .help-tabs, .search-filter-left){
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
  }
  :is(.compact-products-grid, .new-arrivals-grid, .recommendations-grid,
      .perks-chips, .sfg-options, .timeline-path, .lb-releases-slide,
      .mobile-cat-pills, .hero-meta, .help-tabs, .search-filter-left)::-webkit-scrollbar{ display: none; }

  /* The fade is masked rather than painted, so it works over whatever is
     behind the row instead of assuming a flat background. */
  .ng-m-fade{
    position: relative;
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 34px), transparent 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 34px), transparent 100%);
  }
  /* …and is removed once the row has been scrolled to its end. */
  .ng-m-fade.ng-m-end{
    -webkit-mask-image: none;
    mask-image: none;
  }

  /* Every card in a rail gets a snap point, so a swipe settles on a card
     rather than halfway across one. */
  :is(.recommendation-card, .arrival-card, .compact-product-card,
      .new-arrivals-grid > *, .compact-products-grid > *){
    scroll-snap-align: start;
  }

  /* ── The bottom tab bar ──────────────────────────────────────────
     It is the primary navigation on a phone, and it was drawing its labels
     at 8px with a 1.5px track. */
  .mobile-tab span{
    font-size: 9.5px !important;
    letter-spacing: 1px !important;
  }
  .mobile-tab i{ font-size: 21px !important; }
  .mobile-tab-bar{
    padding-bottom: env(safe-area-inset-bottom, 0px) !important;
    border-top: 1px solid var(--ng-line) !important;
  }
}

@media (max-width: 768px){

  /* ── C · The stragglers ──────────────────────────────────────────
     A second measured pass found text still under 10px in places the
     grouped rules above could not reach, because the phone rules already in
     storefront.css and the route stylesheets are written at two-class
     specificity WITH !important — e.g.
     `@media(max-width:768px){.shop-page .product-badge{font-size:7px!important}}`.
     A `:is(...)` list is one class specific, so it lost. These match the
     same shape so they can win, rather than being escalated arbitrarily. */

  .shop-page .product-badge,
  .shop-page .tier-tag,
  .search-product-card .card-badge,
  .arrival-card .arrival-badge,
  .compact-product-card .compact-badge{
    font-size: 9.5px !important;
    letter-spacing: .5px !important;
    padding: 3px 7px !important;
  }

  /* Counts, labels and captions that sit inside a named parent. */
  .category-list .category-count,
  .category-list .category-name,
  .sidebar-group-title,
  .search-filter-label,
  .mag-badge, .mag-card-label, .mag-nl-sub, .mag-nl-note,
  .option-label, .sp-stat-item, .perks-chip,
  .lb-philosophy-value-label, .lb-cd-label, .lb-g-tag,
  .view-all-section-btn, .response-avatar, .response-author,
  .reveal-btn, .rating-link, .ng-invite-alt, .clear-cart-btn,
  .sfg-row .sfg-label, .category-header-subtitle, .sidebar-subtitle,
  footer .designedby, .footer-bottom p, table th{
    font-size: 10.5px !important;
    letter-spacing: .5px !important;
  }

  /* ── Touch, second pass ──────────────────────────────────────────
     A card's buttons cannot all be 44px without doubling the height of
     every card on the page, so the rule is by importance rather than a flat
     number: the card's PRIMARY action — the one that puts something in a
     bag — gets the full 44, secondary icon buttons get 36, and a dense
     scrolling row of filter chips gets 38. Everything stays above the
     thumb-sized floor; nothing grows for the sake of a round number. */

  /* Primary card actions. */
  :is(.arrival-add-btn, .compact-add-to-cart-btn, .card-add-btn,
      .mag-card-btn, .lb-release-cta, .sp-size-guide-btn, .clear-cart-btn,
      .compact-view-more-btn, .review-action-btn, .rec-refresh-btn,
      .view-all-section-btn, .slide-cta, .reveal-btn){
    min-height: 44px !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
  }

  /* Secondary icon buttons — isolated, so 36 is comfortable. */
  :is(.compact-favorite-btn, .card-fav, .arrival-favorite,
      .recommendation-wishlist-btn, .recently-viewed-wishlist-btn,
      .favorite-btn, .color-option){
    min-width: 36px !important;
    min-height: 36px !important;
  }

  /* Filter chips: a long scrolling row, so height over bulk. */
  .sfg-btn{
    min-height: 38px !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    display: inline-flex !important;
    align-items: center !important;
  }

  /* The release carousel's page dots were a 3px-high strip — technically
     present, impossible to hit. The dot stays the same size; the hit area
     around it grows. */
  .lb-releases-tab{
    position: relative;
    min-height: 4px !important;
  }
  .lb-releases-tab:before{
    content: "";
    position: absolute;
    inset: -18px -7px;
  }
  .lb-releases-tabs{ gap: 12px !important; padding: 18px 0 6px !important; }

  /* Text links that act as buttons. */
  :is(.rating-link, .ng-invite-alt, .designedby){
    display: inline-block;
    padding: 9px 0 !important;
  }
}

@media (max-width: 768px){

  /* Third and last type pass. These lost to three-class phone rules —
     `.shop-page .shop-filter-grid .sfg-label{font-size:8px!important}` and
     its relatives — so they are written at the same depth. Anything still
     below 10px after this is a badge sitting at the deliberate 9.5px floor;
     a badge is a marker, not something to read at length. */
  .shop-page .shop-filter-grid .sfg-label,
  .product-page .shop-filter-grid .sfg-label,
  .shop-page .category-header-subtitle,
  .product-page .category-header-subtitle,
  .shop-page .sidebar-subtitle,
  .product-page .sidebar-subtitle,
  .sp-stats .sp-stat-item,
  .review-item .review-action-btn,
  .cart-page table th,
  .os-body table th{
    font-size: 10.5px !important;
    letter-spacing: 1.2px !important;
  }

  /* Bare spans and paragraphs carry no class of their own, so they are
     reached through the parent that does. */
  .category-item > span,
  .sfg-row > span,
  .product-image-container > span,
  .footer-bottom > p,
  .welcome-section p{
    font-size: 10.5px !important;
  }

  /* Primary calls to action that the second pass missed. */
  :is(.ng-invite-btn, .ng-invite-alt, .continue-btn, .lb-hero-btn,
      .mag-nl-btn, .checkout-btn, .place-order-btn){
    min-height: 44px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
}

@media (max-width: 768px){
  /* The last few, each reached through the parent that carries the class. */
  .slide-content > p,
  .mobile-auth-tabs .mat-tab span,
  .ng-challenge-label span,
  footer .footer-bottom p{
    font-size: 10.5px !important;
    letter-spacing: .4px !important;
  }
  .slide-content > p{ font-size: 11.5px !important; line-height: 1.6 !important; }
}

@media (max-width: 768px){

  /* ── D · Corrections from the first mobile render ─────────────────
     Three of these are damage from the type floor above, which is exactly
     the risk of raising type globally: a control sized for 8px text does
     not necessarily hold 11px text. */

  /* 1 · The size chips are a FIXED 26px wide with `overflow: visible`, so
     at 11px the label simply painted over its neighbours — "XS" ran into
     "S" and the colour names ran across the swatches. The chip grows to fit
     its label instead of the label spilling out of the chip. */
  .shop-page .shop-filter-grid .sfg-btn-sm,
  .product-page .shop-filter-grid .sfg-btn-sm{
    width: auto !important;
    min-width: 40px !important;
    padding: 0 11px !important;
    overflow: hidden !important;
  }

  /* 2 · A colour chip's label is the swatch. Showing "BLACK" beside a black
     circle is the same information twice, and at phone width it is the
     thing that broke the row. The swatch grows to carry the meaning on its
     own, and the name stays available to screen readers. */
  .shop-filter-grid .sfg-btn:has(.sfg-color-swatch){
    width: 40px !important;
    min-width: 40px !important;
    padding: 0 !important;
    justify-content: center !important;
  }
  .shop-filter-grid .sfg-btn:has(.sfg-color-swatch) .sfg-btn-text{
    position: absolute !important;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .shop-page .shop-filter-grid .sfg-color-swatch,
  .product-page .shop-filter-grid .sfg-color-swatch{
    width: 22px !important;
    height: 22px !important;
  }

  /* 3 · Every filter row scrolls rather than overflowing. The price row was
     426px wide inside a 358px column with no scroller, so its first and last
     chips were simply cut off with no way to reach them. */
  .shop-filter-grid .sfg-options{
    max-width: 100% !important;
    overflow-x: auto !important;
    flex-wrap: nowrap !important;
    scroll-padding-inline: var(--ng-m-gutter);
  }
  .shop-filter-grid .sfg-row{
    min-width: 0 !important;
    grid-template-columns: minmax(0, 1fr) !important;
  }

  /* 4 · The breadcrumb link is `display: block` (section 27, so a long
     product name can ellipsis). A block box does not centre its own text,
     so giving it a 32px min-height for touch left the label sitting at the
     top of its box and the dot apparently below it. */
  .timeline-item .timeline-link{
    display: flex !important;
    align-items: center !important;
  }
  .timeline-item{ align-items: center !important; }

  /* 5 · The category drawer's toggle.
     It was `position: fixed` at top-left — an unlabelled 40px circle
     floating over the breadcrumb, overlapping it by 18px. Two problems in
     one: a collision, and a control whose only affordance was an icon.
     It joins the page flow as a labelled button above the header, where it
     can collide with nothing and says what it does. */
  .shop-page .sidebar-toggle,
  .product-page .sidebar-toggle{
    position: static !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 9px;
    width: auto !important;
    height: 42px !important;
    margin: 0 var(--ng-m-gutter) 12px !important;
    padding: 0 18px !important;
    border-radius: 2px !important;
    background: rgba(138, 43, 226, .1) !important;
    border: 1px solid var(--ng-line) !important;
    color: rgba(255, 255, 255, .85) !important;
    font-size: 10.5px !important;
    font-weight: 500;
    letter-spacing: 1.6px !important;
    text-transform: uppercase;
  }
  .shop-page .sidebar-toggle:after,
  .product-page .sidebar-toggle:after{ content: "Categories"; }
}

@media (max-width: 768px){

  /* The colour row's "All" chip carries an icon rather than a swatch, so
     the `:has(.sfg-color-swatch)` rule above did not reach it and its label
     went on painting across the first swatch. Matching the ROW instead of
     the chip catches every chip in it, whichever kind it is. */
  .shop-filter-grid .sfg-options:has(.sfg-color-swatch) .sfg-btn{
    width: 40px !important;
    min-width: 40px !important;
    padding: 0 !important;
    justify-content: center !important;
  }
  .shop-filter-grid .sfg-options:has(.sfg-color-swatch) .sfg-btn-text{
    position: absolute !important;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .shop-filter-grid .sfg-options:has(.sfg-color-swatch) .sfg-btn-icon{
    font-size: 13px !important;
    margin: 0 !important;
  }

  /* The release carousel bled its cards to the screen edge with
     `margin-left: -20px` against a 20px padding — a full-bleed trick that
     only works if nothing clips it. Its own viewport is `overflow: hidden`,
     so instead of bleeding, the first card simply lost 20px off its left
     and the row sat [-19, 369] inside a [1, 389] box. The row aligns to the
     page gutter like everything else rather than reaching past it. */
  .lb-releases-slide{
    margin-left: 0 !important;
    padding-left: var(--ng-m-gutter) !important;
    padding-right: var(--ng-m-gutter) !important;
  }
}

@media (max-width: 768px){

  /* ── E · Final sweep ─────────────────────────────────────────────
     Named from a third measured pass across all ten pages at 430, 390 and
     360px. What is left after this is the deliberate 9.5px badge floor and
     nothing else. */

  /* NightGuard. The brand mark was 6.5px and the prompt, retry and error
     lines 9px — on the one control standing between an applicant and an
     account, where a misread instruction means a failed sign-up. */
  .ng-brand-text{ font-size: 9.5px !important; letter-spacing: 1.4px !important; }
  .ng-key-prompt,
  .ng-retry-btn,
  .ng-challenge-label{ font-size: 11px !important; letter-spacing: .5px !important; }
  .ng-retry-btn{
    min-height: 40px !important;
    display: inline-flex !important;
    align-items: center !important;
  }

  /* Form validation. An error nobody can read is an error nobody can fix. */
  .fld-error{ font-size: 11px !important; letter-spacing: .3px !important; }

  /* The category header's stat chips, the size-guide table, and the
     order-summary security line. */
  .category-header-stat,
  .category-header .stat-chip,
  .sp-stats .sp-stat-item,
  .sp-size-guide th,
  .sp-size-guide td,
  .option-label,
  .os-security,
  .panel-subtitle,
  .category-header-subtitle,
  .content-section h4,
  .mag-section h4{
    font-size: 10.5px !important;
    letter-spacing: .9px !important;
  }

  /* ── The last of the touch targets ──────────────────────────────
     Section headings that are also links ("New Arrivals", "The Edit",
     "Browse", "See More", "Shop Look") were 14–25px tall. They are how you
     get from the home page into a section. */
  :is(.section-link, .hero-see-more-btn, .view-all-link, .section-more,
      .lb-g-tag, .lb-g-item .lb-g-tag, .rating-link, .designedby){
    min-height: 40px !important;
    display: inline-flex !important;
    align-items: center !important;
  }

  /* Social icon links in the lookbook's model cards were 24px. */
  .lb-model-social a{
    width: 38px !important;
    height: 38px !important;
  }
}

@media (max-width: 768px){
  /* These three lost to page-scoped rules, so they are written at the same
     depth. After this the only text under 10px on any storefront page is a
     badge at the deliberate 9.5px floor. */
  .product-page .sp-stat-item,
  .shop-page .sp-stat-item,
  .sp-stat-item{ font-size: 10.5px !important; }

  .product-page table th,
  .shop-page table th,
  .mag-wrap table th,
  .review-item p,
  .product-description p,
  .mag-card p{ font-size: 10.5px !important; }
}

@media (max-width: 768px){
  /* Located precisely rather than guessed: the product page's option and
     sidebar headings, its size-guide table head, the category header's
     subtitle wherever it appears, and the footer credit line. */
  .product-options .option-group .option-label,
  .product-sidebar .size-guide h4,
  .product-sidebar .frequently-bought h4,
  .size-guide thead th,
  .frequently-bought thead th,
  .category-header .category-header-content .category-header-subtitle,
  footer .footer-bottom p{
    font-size: 10.5px !important;
    letter-spacing: .9px !important;
  }
}

@media (max-width: 768px){

  /* ── F · The pages the first sweep missed ────────────────────────
     Checkout, the customer dashboard, wishlist, confirmation and 404 were
     not in the first ten. Each brought its own labels, and the dashboard —
     which a member sees more often than any other signed-in page — had its
     whole sidebar at 8–9px with 29px nav rows. */

  /* Customer dashboard sidebar: tier, loyalty progress, group headings. */
  .user-tier,
  .loyalty-text,
  .nav-group-title,
  .nav-badge,
  .promo-apply,
  .search-result-count,
  .ch-sub,
  .cat-name{
    font-size: 10.5px !important;
    letter-spacing: 1px !important;
  }

  /* The dashboard's stat deltas sit beside a figure, so a step down from
     body but not a smudge. */
  .stat-change{ font-size: 10px !important; letter-spacing: .4px !important; }

  /* Blog hero. */
  .hero-badge{ font-size: 9.5px !important; letter-spacing: .6px !important; }
  .hero-see-more-btn{ font-size: 11px !important; letter-spacing: .9px !important; }

  /* Dashboard navigation rows were 29px — the control a member uses to move
     between orders, wishlist and settings. */
  .dashboard-nav-list .nav-item a,
  .dashboard-sidebar .nav-item a,
  .nav-item a{
    min-height: 44px !important;
    display: flex !important;
    align-items: center !important;
  }

  /* 404 and confirmation: the recovery controls. A 14px link is the only
     way off a dead end, and a 23px tag is the suggestion meant to rescue
     the visit. */
  :is(.action-btn, .search-btn, .search-tag, .nl-btn, .confirmation-link,
      .conf-action, .btn){
    min-height: 40px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
  .confirmation-page a:not(.btn):not([class*=btn]),
  .ch-wrap a:not(.btn):not([class*=btn]){
    display: inline-block;
    padding: 10px 0 !important;
  }
}

@media (max-width: 768px){
  /* The confirmation page has its own `.breadcrumb`, not the `.timeline-*`
     one every other page uses — so none of section 27 or the mobile rules
     above reached it, and its only link was 38×14. */
  .breadcrumb a,
  .breadcrumb span{
    font-size: 10.5px !important;
    letter-spacing: .9px !important;
  }
  .breadcrumb a{
    min-height: 40px !important;
    display: inline-flex !important;
    align-items: center !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   39 · MOBILE, SECOND PASS — review notes
   ═══════════════════════════════════════════════════════════════════════
   Five notes from a review of 2.12.1 on a real phone. Section 38 fixed the
   metric — type size, tap targets, gutters. These are the things that only
   show up once the metric is right: the chrome in the wrong place, the
   rhythm between sections, and cards that fit the screen rather than fight
   it.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 39A · The screen must not zoom ────────────────────────────────────
   Reported as "the search bar zooms the phone in when you tap it". The
   obvious cause was ruled out by measurement: every visible input on every
   page already computes to 16px, which is the threshold below which iOS
   zooms a focused field. Two other things were doing it.

   1 · `touch-action` is set NOWHERE in this codebase — not in
       storefront.css, not in any route sheet, not here. The default is
       `auto`, which keeps Safari's double-tap-to-zoom alive on every
       element on the site. A search field is exactly where a person taps
       twice: once to focus, once to place the caret, or once because the
       first tap did not appear to take. Safari reads that as "zoom in".
       `manipulation` turns off double-tap zoom and the 300ms click delay
       that comes with it. It does NOT touch pinch-zoom or panning, so
       nobody loses the ability to zoom deliberately.

   2 · The field's box was wrong. `.timeline-search` is 32px tall (34 above
       480px) and section 38 gives every input `min-height: 46px`, so the
       input stood 7px proud of its own wrapper top and bottom, with
       `overflow: visible` letting it paint there. A focused control that is
       not fully inside its container is one Safari will scroll and scale to
       reveal. The wrapper now holds the field it contains. */

:is(a, button, input, select, textarea, label, summary,
    [role=button], [role=tab], [role=link], [onclick]){
  touch-action: manipulation;
}

/* Safari's text inflation is a second way the page can change scale on its
   own. The layout is already written for the device width. */
html{
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

@media (max-width: 768px){
  /* `touch-action` is not inherited, but the browser intersects it up the
     ancestor chain, so declaring it once on the body settles the gesture for
     every descendant — including the bare wrappers and padding between
     controls, which is exactly where a stray second tap lands. */
  body{ touch-action: manipulation; }

  /* The wrapper matches the 46px input inside it rather than clipping it. */
  .timeline-search{
    height: 46px !important;
    min-height: 46px !important;
    align-items: center !important;
  }
  /* Written explicitly rather than relying on section 38's `:is(input…)`
     rule out-specifying section 27's phone rule by accident. On this field
     16px is not a size choice — it is the zoom threshold. */
  .timeline-search-input{
    font-size: 16px !important;
    height: 46px !important;
    min-height: 46px !important;
    padding: 0 12px !important;
    /* The placeholder is what sets the field's apparent size, so it carries
       the smaller voice the design wants while the input itself stays at
       the threshold. */
    letter-spacing: .3px !important;
  }
  .timeline-search-input::placeholder{
    font-size: 12.5px !important;
    letter-spacing: 1px !important;
  }
  .timeline-search-btn{ height: 46px !important; }
  /* The autocomplete panel hangs off the field, so it follows it down. */
  .timeline-search .ng-search-ac,
  .timeline-search .ng-search-ac-home,
  .timeline-search .search-dropdown{ top: 46px !important; }
}

@media (max-width: 480px){
  /* Section 27 took the field to 32px and the text to 10px here. Both are
     below what a phone can focus without rescaling. */
  .timeline-search{ height: 46px !important; }
  .timeline-search-input{ font-size: 16px !important; padding: 0 12px !important; }
}

/* ─── 39B · The Categories button joins the header ──────────────────────
   It was a full-width labelled bar under the header — honest, but it read
   as page content rather than as chrome, and it cost 54px of vertical room
   at the top of every shop and product page.

   It becomes the left-hand counterpart of the cart: same 33×44 box, same
   vertical centre in the 48px header, and the same distance from its edge
   of the screen that the cart sits from its own. The header is
   `position: fixed`, so the button is too — that way the pair stay together
   through the scroll shrink without either needing to know about the other.

   Measured to match: the cart occupies x 337–370 in a 390px viewport, which
   is 20px of gutter (the header's own padding) plus a 33px box, centred at
   y 2–46 inside a 48px bar. */
@media (max-width: 768px){
  .sidebar-toggle,
  .shop-page .sidebar-toggle,
  .product-page .sidebar-toggle{
    position: fixed !important;
    top: 2px !important;
    left: 20px !important;
    right: auto !important;
    bottom: auto !important;
    z-index: 1001 !important;       /* the header is 1000 */
    width: 33px !important;
    min-width: 33px !important;
    height: 44px !important;
    margin: 0 !important;
    padding: 0 !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 0 !important;
    background: none !important;
    border: none !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    letter-spacing: 0 !important;
  }
  /* The label was the point of the old bar and is wrong in the header: the
     cart has no label there either. */
  .sidebar-toggle:after,
  .shop-page .sidebar-toggle:after,
  .product-page .sidebar-toggle:after{ content: none !important; }

  /* The cart's artwork is 33×29 of sprite. There is no drawn "categories"
     icon yet, so the glyph stands in — sized and weighted to sit level with
     the sprite beside it rather than to its own default. */
  .sidebar-toggle i{
    font-size: 17px !important;
    line-height: 1 !important;
    color: rgba(255, 255, 255, .9) !important;
    transition: color .5s ease, transform .5s var(--ease-luxe, ease) !important;
  }
  .sidebar-toggle[aria-expanded=true] i,
  .sidebar-toggle.active i,
  .sidebar-toggle:hover i{
    color: var(--purple-light, #ba55d3) !important;
  }
}

/* ─── 39C · The dark theme, lifted off the floor ────────────────────────
   "The page is too dark on mobile and needs a better dark theme easier on
   the eyes that keeps the aesthetic idea."

   Measured before changing anything: the page ground is rgb(5,5,5) and the
   text is white at alphas of .12 to .44 and purple at .5 to .85. Across
   eight pages that produced between five and fifteen distinct classes per
   page below the readable line — the footer's copyright at 1.43:1, the
   shop's category counts at 1.48:1, every section subtitle at 2.0–2.5:1,
   the blog's excerpts at 2.88:1. On a 27" monitor those are a whisper. In a
   hand, at arm's length, on a screen that is usually at half brightness,
   they are not there at all.

   (One reading was a false positive and is left alone: `.tier-tag` computes
   to `color(srgb …)`, which the first version of the audit parser read as
   0–255 rather than 0–1. The tag is a pink at roughly 9:1 and was never a
   problem. Every other entry below is a real rgba() measurement.)

   TWO MOVES, AND WHY THIS ONE IS NOT "JUST MAKE IT BRIGHTER"

   1 · The ground comes UP, not down. A true-black page on an OLED phone is
       the worst case for exactly this kind of type: the pixels are off, so
       low-alpha grey smears and haloes against them, and the eye has no
       surface to rest on. A near-black with a faint purple cast reads as
       the same design and stops the smear. It costs almost nothing in
       contrast — at these luminances the +0.05 in the WCAG ratio dominates,
       so lifting the ground from #050505 to #0e0c12 moves white-on-ground
       from 20.4:1 to 19.7:1 — and it gives cards something to sit ON, which
       is what makes a dark layout read as layered rather than as a void.

   2 · The text alphas come up BY TIER, not uniformly. The design's
       hierarchy is the alphas, so flattening them to one value would
       destroy it. Each measured tier moves up as a group and keeps its
       distance from the one above: .12–.20 → .50, .25 → .58, .30–.33 → .64,
       .37–.40 → .72, .44 → .78. The purple accents move to a lighter mix at
       full opacity rather than a dark purple at half.

   Everything here is inside the phone breakpoint. The desktop is not dark
   in the hand and was not the complaint. */

@media (max-width: 768px){

  :root{
    --ng-m-ground:  #0e0c12;   /* the page */
    --ng-m-surface: #15121c;   /* a card ON the page */
    --ng-m-t1: rgba(255, 255, 255, .95);   /* headings */
    --ng-m-t2: rgba(255, 255, 255, .78);   /* names, body a person reads */
    --ng-m-t3: rgba(255, 255, 255, .64);   /* secondary — dates, counts */
    --ng-m-t4: rgba(255, 255, 255, .50);   /* legal, disclaimers */
    --ng-m-accent: #cf9cf0;                /* 9.1:1 on the ground */
  }

  /* background-COLOR only: `body` carries the stardust texture as a
     background-image and the shorthand would drop it. */
  body{ background-color: var(--ng-m-ground) !important; }
  .container{ background-color: var(--ng-m-ground) !important; }

  /* ── Tier 4 · legal and disclaimers — measured at 1.30–1.71:1 ─────── */
  footer .footer-bottom p,
  .footer-bottom p,
  .mag-nl-note,
  .lb-nl-note,
  .designedby,
  footer .designedby,
  .ng-brand-text{
    color: var(--ng-m-t4) !important;
  }
  /* `.designedby` is a bare <a> with no colour rule of its own, so on four
     of the eight pages it was rendering in the user agent's default link
     blue — rgb(0,0,238) on near-black, 2.17:1, and the wrong colour
     entirely. This is not a contrast tweak; it is a missing declaration. */
  footer a.designedby,
  a.designedby:link,
  a.designedby:visited{ color: var(--ng-m-t4) !important; }

  /* ── Tier 3 · secondary — measured at 1.48–2.77:1 ─────────────────── */
  .category-count,
  .sidebar-count,
  .category-header-subtitle,
  .sidebar-subtitle,
  .sp-cat-subtitle,
  .compact-subcategory-subtitle,
  .panel-subtitle,
  .ch-sub,
  .nl-sub,
  .mag-nl-sub,
  .lb-nl-sub,
  .error-subtitle,
  .link-desc,
  .sec-sub,
  .cat-name,
  .search-tag,
  .blog-date,
  .response-time,
  .trending-meta,
  .os-ship-opt-time,
  .lb-philosophy-sig,
  .lb-philosophy-value-text,
  .help-tab{
    color: var(--ng-m-t3) !important;
  }

  /* ── Tier 2 · what a person actually reads — 2.88–4.32:1 ──────────── */
  .blog-excerpt,
  .response-text,
  .lb-philosophy-body,
  .mag-card-summary,
  .field-label,
  .form-label,
  .ng-label,
  .os-label,
  .timeline-link,
  .view-all-section-btn,
  .btn-ghost,
  .perk-desc,
  .report-text{
    color: var(--ng-m-t2) !important;
  }
  /* The current crumb stays brightest — it says where you are. */
  .timeline-item.active .timeline-link{ color: var(--ng-m-t1) !important; }

  /* ── The purple accents ──────────────────────────────────────────────
     `#ba55d3` at alpha .5–.85 measured 2.03–3.96:1. The same hue mixed
     lighter, at full opacity, measures 9.1:1 and reads as MORE purple
     rather than less — a translucent purple on black is really a grey. */
  .blog-author,
  .response-author,
  .trending-number,
  .mag-card-date,
  .mag-card-label,
  .lb-cd-label,
  .timeline-search-dot,
  .sfg-btn.active,
  .price-current{
    color: var(--ng-m-accent) !important;
  }

  /* The bottom tab bar's resting label was rgba(140,100,170,.25) — 1.26:1,
     which on the primary navigation means four of the five destinations
     were effectively unlabelled. The current tab keeps the accent; the rest
     become legible without competing with it. */
  .mobile-tab span,
  .mobile-tab i{ color: rgba(255, 255, 255, .58) !important; }
  .mobile-tab.active span,
  .mobile-tab.active i,
  .mobile-tab[aria-current=page] span,
  .mobile-tab[aria-current=page] i{ color: var(--ng-m-accent) !important; }

  /* Placeholders sit one step under tier 4 by convention, but .22 was
     invisible. */
  ::placeholder{ color: rgba(255, 255, 255, .42) !important; }
}

/* ─── 39D · One gutter, one rhythm, one left edge ───────────────────────
   "The sections need better gap spacing, alignment for section titles and
   item cards… every page feels very cluttered… Elements shouldn't just be
   cutting off the screen always or always pushing the boundary of the edge
   of the screen it feels bad."

   Measured at 390px, the home page's content sat at SEVEN different left
   edges: 0, 1, 12, 16, 17, 21 and 29. The cause is one line —
   `.container{ width:100%; padding:0 }` on the phone — which removed the
   page's gutter entirely and left every section to invent its own inset.
   `.new-arrivals-section` used `padding-left:20px` with no right padding.
   `.compact-products-grid` used 16 both sides. The magazine's cards used
   none and ran to x=1. The section headers put their title at 29 and their
   subtitle at 21.

   And the sections had NO gap: five of them began at exactly the pixel the
   one above ended.

   The gutter goes back on the container, the per-section insets come off,
   and the rhythm is declared once as a relationship between siblings rather
   than as a margin on each. */

@media (max-width: 768px){

  .container{
    padding-left: var(--ng-m-gutter) !important;
    padding-right: var(--ng-m-gutter) !important;
  }

  /* The insets the sections were carrying to make up for the missing one. */
  .new-arrivals-section,
  .compact-products-section,
  .compact-products-grid,
  .magazine-section,
  .mag-row,
  .mag-row-bottom,
  .mag-col-stack,
  .category-header,
  .category-header-wrapper{
    padding-left: 0 !important;
    padding-right: 0 !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
  }

  /* ── The rhythm ──────────────────────────────────────────────────────
     Stated as sibling relationships so it cannot drift: every section is
     22px from the one above it; a section header takes 34 above and gives
     10 below, so a title reads as belonging to what FOLLOWS it rather than
     floating between two things. */
  .container > *{ margin-top: 0 !important; margin-bottom: 0 !important; }
  .container > * + *{ margin-top: 22px !important; }
  .container > * + .category-header-wrapper{ margin-top: 34px !important; }
  .container > .category-header-wrapper + *{ margin-top: 10px !important; }

  /* ── The section headers ─────────────────────────────────────────────
     Title and subtitle aligned to each other and to the cards below them.
     The 14px star before the title is what broke the alignment: it sits in
     the title row, so the title began after it while the subtitle began at
     the content edge — and the gap between them is the icon's width, which
     is a font metric nobody controls. On a phone the star is decoration
     standing between the reader and an aligned column, so it goes. */
  .category-header{
    justify-content: flex-start !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
  }
  .category-header-content{
    align-items: flex-start !important;
    width: 100% !important;
  }
  .category-header-title-row{
    justify-content: flex-start !important;
    gap: 0 !important;
    width: 100% !important;
  }
  .category-header-icon,
  .category-header-title-row > i{ display: none !important; }
  .category-header-text,
  .category-header-subtitle{
    text-align: left !important;
    margin-left: 0 !important;
    padding-left: 0 !important;
  }
  .category-header-subtitle{ margin-top: 5px !important; }
}

/* ─── 39E · Item cards — one metric for the whole site ──────────────────
   "Item cards everywhere look awkward and oddly sized, they should fit
   perfectly and sleek on mobile screens not being overly large or too
   small."

   Measured across eight pages at 390px, the card widths were 120, 144, 160,
   174, 179, 194, 356, 366, 388 and 390. Ten widths for one idea. The two
   extremes are the ones that read as broken: the home page's
   `.compact-product-card` at 120px wide — a product photograph the size of
   a postage stamp, twenty-four of them — and the shop page's
   `.recommendation-card` at 390px, a full-bleed card that fills the screen
   and lets you compare nothing with anything.

   There are only two kinds of card here, so there are two widths.

     · A RAIL card scrolls sideways. Its width is set so that two fit with a
       third clearly peeking, because a rail that ends flush looks like a
       grid that got cut off — which is precisely what it looked like.
     · A GRID card is one of two in a row. Its width is whatever half the
       content column is, so the pair sit inside the gutter rather than on
       top of it.

   Both are derived from the gutter rather than typed in, so a change to the
   gutter moves the cards with it. */

@media (max-width: 768px){

  :root{
    /* 2 cards + a peek at 390px → 164px. The clamp keeps it sane on a 320px
       phone and stops it ballooning on a 768px tablet. */
    --ng-m-card-rail: clamp(142px, 42vw, 180px);
    --ng-m-card-gap: 12px;
  }

  /* ── Rails ───────────────────────────────────────────────────────────
     The row bleeds to the right edge (content scrolling off-screen is the
     whole affordance) but its FIRST card starts at the gutter, so the rail
     lines up with the section title above it. */
  .new-arrivals-grid,
  .compact-products-grid{
    gap: var(--ng-m-card-gap) !important;
    padding-left: 0 !important;
    margin-right: calc(-1 * var(--ng-m-gutter)) !important;
    padding-right: var(--ng-m-gutter) !important;
  }

  :is(.arrival-card, .compact-product-card,
      .new-arrivals-grid > *, .compact-products-grid > *){
    flex: 0 0 var(--ng-m-card-rail) !important;
    width: var(--ng-m-card-rail) !important;
    min-width: 0 !important;
    max-width: none !important;
  }

  /* ── The recommendation rail becomes what it is on desktop: a grid ───
     `.recommendations-grid` is `repeat(6, 1fr)` on a wide screen. The phone
     rule turned it into a flex carousel and forced each card to
     `flex:0 0 100%; width:100%; height:380px` — one card per screenful.
     Two columns is both the closer relative of the desktop layout and the
     only version in which the cards can be compared. The height comes down
     with the width so the proportion stays the desktop card's. */
  .recommendations-grid{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: var(--ng-m-card-gap) !important;
    overflow: visible !important;
    margin-right: 0 !important;
    padding-right: 0 !important;
  }
  .recommendation-card{
    flex: initial !important;
    width: 100% !important;
    height: 262px !important;
    border-radius: 2px !important;
    border: 1px solid var(--ng-line) !important;
    border-bottom: 1px solid var(--ng-line) !important;
  }
  /* Two actions side by side inside a 173px card. */
  .recommendation-card .rec-action-btn{
    min-height: 36px !important;
    font-size: 10px !important;
    letter-spacing: .4px !important;
    padding: 0 6px !important;
  }

  /* ── Two-up grids ────────────────────────────────────────────────────
     Every one of these was a `repeat(2, 1fr)` already; what differed was
     the gap (0, 1, 8 and 12px on four different pages) and whether the grid
     sat inside the gutter. */
  .products-grid,
  .cat-grid,
  .perks-grid,
  .lb-gallery,
  .lb-models-grid{
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: var(--ng-m-card-gap) !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
  /* The lookbook gallery ran edge to edge with 1px seams — a contact sheet
     rather than a gallery. It joins the page. */
  .lb-g-item,
  .lb-model-card{
    width: auto !important;
    border-radius: 2px !important;
  }

  /* ── Full-width cards ────────────────────────────────────────────────
     These were already one per row; they were just sitting at 12px, 16px or
     17px from the edge depending on the page. */
  .links-grid,
  .blog-grid-compact,
  .trending-sidebar,
  .cart-layout{
    padding-left: 0 !important;
    padding-right: 0 !important;
    gap: var(--ng-m-card-gap) !important;
  }

  /* ── The Claim button ────────────────────────────────────────────────
     "Claim buttons shouldn't be so large." They were 44px tall and filling
     the card's full width — on a 225px card that is a fifth of its height
     and the loudest thing in it, louder than the photograph it is selling.

     36px, because the card itself is also a link: the button is not the
     only way in, so it does not need the 44 that section 38 gives a
     control standing alone. It stays a button, not a bar. */
  :is(.arrival-add-btn, .compact-add-to-cart-btn, .card-add-btn){
    min-height: 36px !important;
    height: 36px !important;
    font-size: 10px !important;
    letter-spacing: .6px !important;
  }
}

/* ─── 39F · The Edit ────────────────────────────────────────────────────
   "The Edit on mobile is overly cluttered and needs a better mobile layout
   unique and perfectly fitting for mobile while trying to keep it as true
   to the desktop as possible."

   The measurement says exactly what "cluttered" means here. Every
   `.mag-card` is a photograph with `.mag-card-body` absolutely positioned
   over its bottom edge — the desktop design, and a good one, because
   desktop gives the card 260–380px of height and the text lands in the
   bottom third.

   On the phone the card is 160px tall and the body is 147px. The text does
   not sit in the bottom third of the picture; it covers ninety-two per cent
   of it. Four of those in a column is the whole section: a stack of dark
   rectangles with a photograph faintly visible behind the words.

   Three numbers were also simply wrong at this width:
     · `.mag-feature` ran to x=1 — the full screen width, edge to edge,
       while everything beside it sat at 17.
     · `.mag-nl-glow` is `top:-40px` on a `position:absolute` layer. On
       desktop it blooms above the newsletter panel over empty margin; on
       the phone, with the sections butted together, it rendered 39px ABOVE
       its own parent, inside the card stack overhead.
     · The `.mag-card-sm` pair were 140px wide against a 164px rail card
       everywhere else on the same page.

   The layout below is the desktop layout — image, tag, date, title,
   summary, action, in that order, with the text over the picture — at a
   proportion where that composition still works. Nothing is removed and
   nothing is moved to a different place in the card. The card is given the
   height its own content needs, the summary is held to two lines so that
   height is predictable, and the action comes down from 44px to 36 to match
   the Claim buttons in 39E. */

@media (max-width: 768px){

  .magazine-section{ padding-bottom: 0 !important; }
  .mag-row{ gap: var(--ng-m-card-gap) !important; }

  /* ── The feature card ────────────────────────────────────────────────
     The hero of the section. It keeps its full-width shape, but inside the
     gutter now, and gets the height that lets the picture read. */
  .mag-feature{
    min-height: 240px !important;
    border-radius: 2px !important;
    border: 1px solid var(--ng-line) !important;
    border-bottom: 1px solid var(--ng-line) !important;
  }
  .mag-feature .mag-card-body{ padding: 14px 16px !important; }

  /* ── The two small cards ─────────────────────────────────────────────
     A scrolling pair. Same rail metric as every other rail on the site, so
     they line up with the arrivals row above them instead of being 24px
     narrower for no reason. */
  .mag-col-stack{
    gap: var(--ng-m-card-gap) !important;
    margin-right: calc(-1 * var(--ng-m-gutter)) !important;
    padding-right: var(--ng-m-gutter) !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
  }
  .mag-card-sm{
    flex: 0 0 var(--ng-m-card-rail) !important;
    width: var(--ng-m-card-rail) !important;
    min-height: 190px !important;
  }
  .mag-card-sm .mag-card-body{ padding: 10px 12px !important; }

  /* ── The four article cards ──────────────────────────────────────────
     147px of text on a 160px card. The card grows to 210 and the text is
     held to a known height, so the photograph gets the top two-thirds back
     and all four cards are identical — which is the thing that made the
     column read as a list rather than as a stack of accidents. */
  .mag-blog{
    min-height: 210px !important;
    margin-bottom: 0 !important;
    border-radius: 2px !important;
  }
  .mag-blog .mag-card-body{ padding: 12px 14px !important; }
  .mag-card-summary{
    display: -webkit-box !important;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden !important;
    margin-bottom: 8px !important;
  }
  /* The in-card action. Kept — the card's own click handler and the
     button's are separate handlers and removing one of them would be a
     behaviour change, not a polish. It is sized like every other in-card
     action instead. */
  .mag-card-btn{
    min-height: 36px !important;
    height: 36px !important;
    font-size: 10px !important;
    letter-spacing: .6px !important;
  }
  .mag-feature-row{ gap: 8px !important; }

  /* ── The newsletter panel ────────────────────────────────────────────
     The glow is decoration that was escaping its own box upward into the
     cards above. It stays inside the panel it belongs to. */
  .mag-newsletter{
    margin: 0 !important;
    overflow: hidden !important;
    border-radius: 2px !important;
  }
  .mag-nl-glow{ top: 0 !important; }
  .mag-nl-inner{ padding: 18px 16px !important; }
}

/* ─── 39G · What 39D broke, and what it uncovered ───────────────────────
   Putting the gutter back on the container was the right fix and it had a
   predictable consequence: several elements were carrying NEGATIVE margins
   to escape a padding that, on the phone, had not existed since the
   container was set to `padding:0`. With the padding back, those margins
   are no longer compensating for anything — they are just pushing content
   off the left edge, and the page grew to 402px on the blog and 393 on
   help. Each is re-stated as a bleed of exactly one gutter so it stays
   correct if the gutter ever changes.

   The rest are things the measured sweep surfaced once the page stopped
   overflowing: four classes still under the 10px floor, and two controls
   under the touch floor. */

@media (max-width: 768px){

  /* The blog's "See More" was `margin-left:-12px` against the trending
     sidebar's old 12px padding — 414px wide inside a 356px column. */
  .trending-sidebar .view-all-section-btn,
  .view-all-section-btn{
    margin-left: 0 !important;
    margin-right: 0 !important;
    width: auto !important;
    max-width: 100% !important;
  }

  /* The help tabs bled by 20px each side to reach the edges of a container
     that used to have 20px of padding. One gutter, stated once. */
  .help-tabs{
    margin-left: calc(-1 * var(--ng-m-gutter)) !important;
    margin-right: calc(-1 * var(--ng-m-gutter)) !important;
    padding-left: var(--ng-m-gutter) !important;
    padding-right: var(--ng-m-gutter) !important;
  }

  /* ── The section title is a link ─────────────────────────────────────
     Hiding the decorative star in 39D left `.category-header-text` as a
     121×14 tap target — the control that takes a visitor from the home
     page into New Arrivals, Browse or The Edit. */
  .category-header-text,
  a.category-header-text{
    display: inline-flex !important;
    align-items: center !important;
    min-height: 40px !important;
  }
  /* …which makes the wrapper tall enough again, so the subtitle keeps its
     own line rather than being pushed onto the title's. */
  .category-header-content{ padding-bottom: 2px !important; }

  /* The password reveal is 20px wide. It is the only way to check a
     password before submitting it on a phone keyboard. */
  .reveal-btn{
    min-width: 40px !important;
    justify-content: center !important;
  }

  /* ── The last four under the floor ───────────────────────────────────
     Located, not guessed: the footer credit under the shop page's own
     scope, the apply form's step counter, and two lookbook classes that
     carry no shared class with anything section 38 grouped. */
  .shop-page footer .footer-bottom p,
  .product-page footer .footer-bottom p,
  .mag-wrap footer .footer-bottom p,
  .lb-page footer .footer-bottom p{
    font-size: 10.5px !important;
  }
  .nav-info span,
  .apply-page .nav-info span{
    font-size: 10.5px !important;
    letter-spacing: .6px !important;
  }
  .lb-philosophy-overline{
    font-size: 10.5px !important;
    letter-spacing: 1.4px !important;
  }
  .lb-nl-note{
    font-size: 10.5px !important;
    letter-spacing: .4px !important;
  }
}

/* ─── 39H · The contrast stragglers ─────────────────────────────────────
   Located by re-running the audit AFTER 39C rather than assuming it had
   landed. Five classes and three elements were still under the line, all
   for the same reason section 38 part C documents: the phone rules in
   storefront.css are written as `.shop-page .category-count{…!important}`,
   two classes deep, and a one-class list loses to them however many
   !importants it carries. These are written at the same depth.

     .shop-page .category-count        #ffffff26  (.15) → 1.48:1
     .shop-page .sidebar-subtitle      #ffffff42  (.26) → 3.73:1
     .shop-page .sp-cat-subtitle       #ffffff59  (.35) → 3.07:1
     .product-page .category-header-subtitle #fff6 (.40)
     .shop-page footer .footer-bottom p           (.12) → 1.30:1

   Plus three unclassed elements the grouped rules could not name: the empty
   cart's explanatory line, the sign-in tab label, and the same footer line
   under the shop scope. */

@media (max-width: 768px){

  .shop-page .category-count,
  .product-page .category-count,
  .category-list .category-count{
    color: var(--ng-m-t3) !important;
  }

  .shop-page .sidebar-subtitle,
  .product-page .sidebar-subtitle,
  .shop-page .sp-cat-subtitle,
  .product-page .sp-cat-subtitle,
  .shop-page .category-header-subtitle,
  .product-page .category-header-subtitle,
  .cart-page .category-header-subtitle,
  .ch-wrap .category-header-subtitle,
  .product-page .reviews-section .category-header-subtitle{
    color: var(--ng-m-t3) !important;
  }

  /* The footer credit, under every page scope that re-declares it. */
  .shop-page footer .footer-bottom p,
  .product-page footer .footer-bottom p,
  .cart-page footer .footer-bottom p,
  div[class$="-page"] footer .footer-bottom p,
  footer .footer-bottom p{
    color: var(--ng-m-t4) !important;
  }

  /* The empty cart's one line of guidance, and the sign-in / create-account
     tabs — the control a guest uses before they can do anything at all. */
  .empty-cart p,
  .empty-state p{ color: var(--ng-m-t3) !important; }

  .mobile-auth-tabs .mat-tab span{ color: var(--ng-m-t2) !important; }
  .mobile-auth-tabs .mat-tab.active span,
  .mobile-auth-tabs .mat-tab[aria-selected=true] span{
    color: var(--ng-m-accent) !important;
  }
}

/* ─── 39I · The last of the contrast, by ancestor ───────────────────────
   Three pages left, each losing to a rule written under a container 39C and
   39H did not name. Found by walking the ancestor chain of every remaining
   element rather than adding scopes speculatively — the cart's subtitle is
   not under `.cart-page` at all but under `.recommendations-full-width`,
   which is why the guess in 39H missed it.

   Two readings in the final sweep are NOT defects and are deliberately left
   alone:
     · `.tier-tag` computes to `color(srgb 0.98 0.58 0.61)`. That is a pink
       at roughly 9:1; the 1.04:1 is the audit parser reading 0–1 channel
       values as 0–255.
     · `.error-number` is the 48px "404" watermark at alpha .03. It is
       decoration behind the message, not text anyone is asked to read, and
       raising it would put a giant numeral on top of the recovery links. */

@media (max-width: 768px){

  .recommendations-full-width .category-header-subtitle,
  .recommendations-full-width .category-header-text{
    color: var(--ng-m-t3) !important;
  }
  .recommendations-full-width .category-header-text{ color: var(--ng-m-t1) !important; }

  /* The apply page — the form a model fills in to join. Every line here is
     an instruction, and it was the least legible page on the site. */
  .app-container .hero p,
  .app-container .f-head p,
  .app-container .p-name,
  .app-container .p-dot,
  .app-container .p-step span{
    color: var(--ng-m-t3) !important;
  }
  .app-container .p-step.active .p-name,
  .app-container .p-step.active .p-dot{ color: var(--ng-m-accent) !important; }
  /* "Step 1 of 4" — the only thing telling an applicant how far they are. */
  .nav-info,
  .nav-info span,
  .app-container .nav-info span{ color: var(--ng-m-t3) !important; }

  /* Search results. */
  .search-page .search-query-label,
  .search-header .search-query-label,
  .search-filter-bar .search-result-count,
  .search-product-card .card-original{
    color: var(--ng-m-t3) !important;
  }
}

/* ─── 39J · The gutter reaches the pages that do not use `.container` ───
   39D put the gutter on `.container`, which is the home page's column. Four
   pages do not lay out that way — shop, blog and search hang their content
   off `.main-layout > .main-content`, and the shop's recommendation rail
   hangs off `.recommendations-full-width` beside it. Those columns had no
   gutter, so the same problem persisted there: the shop grid at x=12 with
   its own 12px padding, the blog's cards at x=0 running to the screen edge,
   the search grid at x=45 because three ancestors each added a little.

   Traced by walking the ancestor chain of each grid rather than adding
   scopes until something worked. The gutter goes on the column; every inner
   box stops adding one of its own. One inset per page, and the same one. */

@media (max-width: 768px){

  .main-content,
  .shop-page .recommendations-full-width,
  .cart-page .recommendations-full-width,
  .ch-wrap .recommendations-full-width,
  .recommendations-full-width{
    padding-left: var(--ng-m-gutter) !important;
    padding-right: var(--ng-m-gutter) !important;
  }
  /* Search puts a margin on the layout itself, which is why its grid began
     at 45 rather than 16. */
  .search-page .main-layout,
  .shop-page .main-layout,
  .blog-page .main-layout{
    margin-left: 0 !important;
    margin-right: 0 !important;
    width: auto !important;
  }
  .search-page .main-content > div{
    padding-left: 0 !important;
    padding-right: 0 !important;
  }

  /* The inner boxes, now that the column carries the inset. */
  .products-grid,
  .search-products-grid,
  .blog-grid-compact,
  .trending-sidebar,
  .recommendations-grid,
  .perks-section,
  .compact-product-category{
    padding-left: 0 !important;
    padding-right: 0 !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
  /* The compact category's "View More" carried 20px of side margin to line
     up with the 20px padding that has just been removed. */
  .compact-view-more-btn{
    margin-left: 0 !important;
    margin-right: 0 !important;
    width: 100% !important;
  }

  /* Two grids were still at their desktop gaps. */
  .products-grid,
  .search-products-grid{
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: var(--ng-m-card-gap) !important;
  }
  .search-product-card{ width: auto !important; }
}

/* ─── 39K · Two corrections to 39J ──────────────────────────────────────
   Measured, not assumed:

   1 · The shop grid did not move. `.shop-page .products-grid` is declared
       four times in storefront.css, the last two with `!important` at two
       classes deep — `padding:12px!important; gap:8px!important`. A
       one-class rule cannot beat that however many !importants it carries,
       which is the same lesson as section 38 part C. Written at the same
       depth, the shop grid now uses the same gutter and gap as every other
       two-up grid on the site.

   2 · The cart's recommendation rail got the gutter TWICE — once from
       39J's rule on `.recommendations-full-width` and once from its
       `.container` ancestor, which the shop page does not have. 356px of
       column became 324 and the cards came out at 156 rather than 173.
       The rail takes the gutter only where nothing above it already has. */

@media (max-width: 768px){

  .shop-page .products-grid,
  .product-page .products-grid,
  .search-page .search-products-grid{
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: var(--ng-m-card-gap) !important;
    padding: 0 !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
  }

  .container .recommendations-full-width,
  .container .main-content{
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
}

/* One more of the same: `.shop-page .main-content` and its product-page
   twin are `padding:0!important` at two classes deep, so 39J's one-class
   rule on `.main-content` never reached the two busiest pages on the site —
   the shop grid came out at x=0, flush to both screen edges. The vertical
   padding those rules set is kept; only the sides change. */
@media (max-width: 768px){
  .shop-page .main-content,
  .product-page .main-content,
  .blog-page .main-content,
  .search-page .main-content{
    padding-left: var(--ng-m-gutter) !important;
    padding-right: var(--ng-m-gutter) !important;
  }
}

/* The lookbook gallery's last row has no height rule below 768px — at that
   width its final two tiles collapsed to 18px while their neighbours stood
   at 160, because those two rely on the image's intrinsic height and the
   rest are given one. A floor, so every tile in the grid is the same tile. */
@media (max-width: 768px){
  .lb-g-item{ min-height: 140px !important; }
  .lb-g-item img{ width: 100% !important; height: 100% !important; object-fit: cover !important; }
}

/* ─── 39L · The pages served outside the SPA ────────────────────────────
   Only `index.html` was loading this stylesheet. Every storefront route —
   /shop, /blog, /cart and the rest — renders through it, so those were
   covered; and the eleven legacy `.html` files are 301-redirected to those
   routes by .htaccess, so they never reach a visitor at all.

   Three files are NOT redirected and ARE served directly, which is how they
   went unmeasured until now:
     · 404.html   — the ErrorDocument for every bad or stale URL on the
                    domain. Measured at 390px it had seven classes under the
                    type floor, sixteen controls under the touch floor, and
                    its breadcrumb search input at 10px — which is the zoom
                    bug that started this pass, on the page a visitor is
                    most likely to arrive at by accident.
     · training.html — served at /training on each panel host. Staff-facing.
     · reset-password.html — reached from a reset email, on a phone,
                    usually by someone who is already locked out.
   All three now load this file (and ng-mobile.js). The rule below is the
   one thing the reset page still needed: its "request a new one" link sits
   inside a paragraph, so no button rule reached it. */

@media (max-width: 768px){
  .lede a,
  p.lede a{
    display: inline-block;
    padding: 9px 0 !important;
    min-height: 40px !important;
  }
}

/* ─── 39M · The rest of the icon-led headings ───────────────────────────
   39D hid the star in `.category-header-title-row` because it sat between
   the title and the left edge, so the title began after it while the
   subtitle beneath began at the edge — a misalignment whose size is the
   icon's font metric, which is not a number anything in CSS can match.

   That is a pattern, not a one-off. Walking every page for a leading
   `<i class="fa-…">` inside a heading that has a subtitle as its sibling
   turned up five more, all of them left-aligned:

     .compact-subcategory-title   (home — every category block)
     .ch-title-row                (cart and checkout)
     .sec-title                   (404 and search)
     .section-label               (dashboard)
     .panel-title                 (sign-in / create account)

   Worth being specific about why this was invisible in measurement: Font
   Awesome is blocked in the test environment, so every one of these icons
   renders at zero width there and the columns line up perfectly. The defect
   only appears once the font loads — which is to say, only for real
   visitors. It was found structurally rather than by measuring pixels.

   The centred icon badges — the newsletter panels' circles — are NOT in
   this list. Their subtitle is centred too, so they align already. */

@media (max-width: 768px){
  .compact-subcategory-title > i:first-child,
  .ch-title-row > i:first-child,
  .sec-title > i:first-child,
  .section-label > i:first-child,
  .panel-title > i:first-child{
    display: none !important;
  }
  /* …and the gap they left behind. */
  .compact-subcategory-title,
  .ch-title-row,
  .sec-title,
  .section-label,
  .panel-title{ gap: 0 !important; }
}

/* ═══════════════════════════════════════════════════════════════════════
   40 · MOBILE, THIRD PASS — review notes
   ═══════════════════════════════════════════════════════════════════════
   Seven notes from a review of 2.12.3. Three of them turned out to be the
   same kind of finding: a mobile rule that HIDES or FLATTENS something the
   desktop has, written at some earlier point and never revisited. The body
   texture, the neon category strip and the blog's drawer were all built,
   all working, and all switched off by a single `display:none`.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 40A · The breadcrumb, back to one row ─────────────────────────────
   "The breadcrumb has a lot of negative space and should perform the same
   way it does on desktop, on mobile."

   Measured at 390 against 1440:

     desktop   wrapper 60px tall — path and search on ONE row, 20px gap
     mobile    wrapper 114px tall — path on one line, search on the next

   114px of an 844px screen is thirteen per cent of the viewport spent on a
   breadcrumb, for the same three crumbs and the same field. The stacking
   was a reasonable call when the field needed 240px; it does not need 240px.

   One row, like desktop. The path takes the slack and scrolls; the field
   sits at its right at 132px — and because 132px is a cramped place to type,
   it expands to the full row on focus and the path slides away under it.
   That is the one thing the phone does that the desktop does not, and it is
   there because a phone is the only place where the field and the path
   cannot both be comfortable at once. */

@media (max-width: 768px){
  .timeline-breadcrumb{
    flex-direction: row !important;
    align-items: center !important;
    gap: 10px !important;
  }
  .timeline-path{
    order: 1 !important;
    flex: 1 1 auto !important;
    min-width: 0 !important;
    padding: 0 !important;
  }
  .timeline-search{
    order: 2 !important;
    flex: 0 0 132px !important;
    width: 132px !important;
    min-width: 0 !important;
    transition: flex-basis .32s var(--ease-luxe, ease), width .32s var(--ease-luxe, ease);
  }
  /* Typing needs the room; the path is still one swipe away. */
  .timeline-search:focus-within{
    flex: 1 1 100% !important;
    width: 100% !important;
  }
  .timeline-search:focus-within ~ .timeline-path,
  .timeline-breadcrumb:has(.timeline-search:focus-within) .timeline-path{
    flex: 0 1 0 !important;
    opacity: 0;
    transition: opacity .2s ease, flex-basis .32s var(--ease-luxe, ease);
  }
  /* 46 for the field + 10 top and bottom. Was 114. */
  .timeline-breadcrumb-wrapper{
    padding: 10px var(--ng-m-gutter) !important;
    margin-bottom: 12px !important;
  }
}

/* ─── 40B · The body texture returns ────────────────────────────────────
   "The body background image used on desktop should appear on mobile as
   well to maintain aesthetic consistency."

   `storefront.css` carries, in its 768px block:

       body{ background-image: none; background-color: #050505 }

   — so the stardust texture was switched off on phones outright. Putting it
   back is one line, but it would have shown nothing on its own: every page
   column paints an OPAQUE background over it. Measured:

       home / account / lookbook / cart   .container    rgb(14,12,18)
       shop                              .main-content  rgb(5,5,5)
       blog                              .main-content  rgb(5,5,8)

   The column IS the page on a phone — full bleed, edge to edge — so it has
   nothing to distinguish itself FROM. It can be transparent and let the
   body's ground and texture through, which is what the desktop does either
   side of its bordered panel. */

@media (max-width: 768px){
  body{
    background-image: url(https://www.transparenttextures.com/patterns/stardust.png) !important;
    background-repeat: repeat !important;
    background-color: var(--ng-m-ground) !important;
  }
  /* The column stops being a lid over the texture. */
  .container,
  .main-content,
  .shop-page .main-content,
  .product-page .main-content,
  .blog-page .main-content,
  .search-page .main-content{
    background-color: transparent !important;
    background-image: none !important;
  }
}

/* ─── 40C · The card language matches desktop again ─────────────────────
   "Categories on the home page should be more consistent with their design
   on desktop, needs better color contrast for the background / black and
   grays, currently the page is too dark."

   Measuring the same elements at 1440 and 390 shows the phone was quietly
   given a different design language — softer corners and no frames:

     element                       desktop        mobile
     .compact-product-category     1px border     NO border, radius 0
                                   radius 2px
     .compact-product-card         radius 2px     radius 8px
     .arrival-card                 radius 2px     radius 8px
     .product-card                 radius 2px     radius 10px
     .new-arrivals-section         1px border     NO border, radius 0
     .trending-card                radius 2px     radius 1px

   Five different radii on the phone against one on the desktop. 2px is the
   brand's corner — it is on the breadcrumb, the buttons, the panels and the
   chips — and 8 to 10px reads as a different, softer product.

   The contrast half is the frames. A category block on desktop is a bordered
   card sitting on the page; on the phone it was a transparent region with
   nothing marking where it began or ended, which is most of why the page
   reads as an undifferentiated dark field. The border comes back and the
   block gets a surface one step above the ground, so the frame has something
   to be the edge OF. */

@media (max-width: 768px){

  /* One corner, the desktop's. */
  :is(.compact-product-card, .arrival-card, .product-card, .recommendation-card,
      .search-product-card, .trending-card, .blog-card-compact, .mag-card,
      .lb-g-item, .lb-model-card, .cat-card, .link-card, .perk-item){
    border-radius: 2px !important;
  }

  /* The category block is a card again. */
  .compact-product-category,
  .new-arrivals-section{
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background-color: var(--ng-m-surface) !important;
    padding: 14px var(--ng-m-gutter) !important;
  }
  /* Its rail still bleeds to the card's own edge rather than stopping short
     of it — the bleed is one card padding now, not one page gutter. */
  .compact-product-category .compact-products-grid{
    margin-right: calc(-1 * var(--ng-m-gutter)) !important;
    padding-right: var(--ng-m-gutter) !important;
  }
  .new-arrivals-section .new-arrivals-grid{
    margin-right: calc(-1 * var(--ng-m-gutter)) !important;
    padding-right: var(--ng-m-gutter) !important;
  }
}

/* ─── 40D · The neon category strip comes to the phone ──────────────────
   "The neon categories beneath the slider on index should appear on mobile,
   in a sleek, clean, fitting way for consistency without being cluttered."

   `storefront.css` hides three things in one declaration:

       .categories-search-section,.sticky-sentinel,.sticky-search-bar{display:none}

   The strip is drawn from `category_icons_sprite.png` — a 1294×128 sheet,
   each tile addressed by its own `--cx`/`--cw` and scaled by `--sp-k`, which
   is .28 on desktop. So there is nothing to rebuild: the artwork scales, and
   the phone just needs a smaller k and a row that scrolls.

   Two of the three STAY hidden, and that is the "without being cluttered"
   half. `.cat-search-top` is a second search field — the phone already has
   one in the breadcrumb and a search destination in the tab bar, and three
   ways to search on one screen is the clutter he is describing.
   `.sticky-search-bar` is the desktop's scroll-follow bar, which on a phone
   would fight the fixed header and the fixed tab bar for the same thumb. */

@media (max-width: 768px){

  .categories-search-section{
    display: block !important;
    position: relative !important;
    margin: 0 0 2px !important;
    padding: 0 !important;
    overflow: visible !important;
  }
  /* The duplicate search and the scroll-follow bar stay off. */
  .cat-search-top,
  .sticky-sentinel,
  .sticky-search-bar{ display: none !important; }

  .categories-grid{
    --sp-k: .30;                       /* .28 on desktop — a touch larger, since
                                          there are four tiles on screen, not ten */
    display: flex !important;
    flex-wrap: nowrap !important;
    justify-content: flex-start !important;
    align-items: flex-start !important;
    gap: 10px !important;
    margin-top: 0 !important;
    padding: 0 !important;
    overflow-x: auto !important;
    overflow-y: hidden !important;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    /* bleeds right like every other rail, starts at the gutter */
    margin-right: calc(-1 * var(--ng-m-gutter)) !important;
    padding-right: var(--ng-m-gutter) !important;
  }
  .categories-grid::-webkit-scrollbar{ display: none; }

  /* The sprite cells are not the same width — they run 28px to 48px at this
     scale, because each category's artwork is its own size on the sheet. On
     desktop the tiles are flex children of a centred row and even out at
     108px; set to `flex: 0 0 auto` for a rail they collapse to their own art
     and the row reads as ten different-sized stickers. A fixed tile with the
     art centred in it is what makes the row a row. */
  .categories-grid .category-item{
    flex: 0 0 84px !important;
    width: 84px !important;
    scroll-snap-align: start;
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: 7px !important;
    padding: 0 !important;
    background: none !important;
    border: none !important;
  }
  .categories-grid .category-item:before{
    margin: 0 auto !important;
  }
  .categories-grid .category-name{
    font-size: 9.5px !important;
    letter-spacing: .6px !important;
    color: var(--ng-m-t3) !important;
    white-space: nowrap;
  }
}

/* ─── 40E · The account page shows one form at a time ───────────────────
   "On the me tab / account tab the page is cluttered on mobile, only one
   form should show at once, naturally the sign in screen should show by
   default with the create account hidden in a tab on mobile."

   The tab strip he is describing already exists. `.mobile-auth-tabs` is in
   the markup with `#matSignIn` marked active and `#matRegister` beside it,
   and `storefront-AccountRoute.css` styles both including an `.active`
   state. What was missing is everything behind it:

     · no rule anywhere hides the inactive panel, so `#loginForm` (352px)
       and `#regForm` (572px) both render, stacked — 924px of form on a
       844px screen, eleven fields, two submit buttons
     · both tab buttons' `onclick` is `function Qt(){}` — an empty function

   So the control was built, styled, and wired to nothing. Reading the
   handler before assuming the tabs were missing is what turned this from
   "build a tab system" into "hide one panel and give the buttons a job".

   The state lives as a class on `<html>`, not on the panels or the tabs.
   Those are React-owned nodes: a class written onto them is gone at the
   next render, and this page re-renders on every validation message. The
   `<html>` element is the one node React never touches, so the visible
   state is a pure function of it — which also means the tab highlight and
   the panel visibility can never disagree. */

@media (max-width: 768px){

  .auth-grid{ grid-template-columns: minmax(0, 1fr) !important; }

  /* Sign in is what a returning visitor wants, so it is the default. */
  .auth-grid > #regForm{ display: none !important; }
  html.ng-auth-register .auth-grid > #regForm{ display: flex !important; }
  html.ng-auth-register .auth-grid > #loginForm{ display: none !important; }

  /* The highlight is driven from the same class rather than from `.active`,
     for the reason above. */
  html:not(.ng-auth-register) #matSignIn,
  html.ng-auth-register #matRegister{
    background: rgba(138, 43, 226, .14) !important;
    color: rgba(255, 255, 255, .92) !important;
  }
  html:not(.ng-auth-register) #matSignIn i,
  html.ng-auth-register #matRegister i{
    color: var(--ng-m-accent) !important;
  }
  html.ng-auth-register #matSignIn,
  html:not(.ng-auth-register) #matRegister{
    background: transparent !important;
    color: var(--ng-m-t3) !important;
  }

  .mobile-auth-tabs{
    margin: 0 0 14px !important;
    width: 100% !important;
  }
  .mat-tab{ font-size: 10.5px !important; letter-spacing: 1px !important; }
  .auth-shell{ padding: 14px var(--ng-m-gutter) !important; }
}

/* ─── 40F · One drawer button, on every page that has a drawer ──────────
   "Categories button should be in the header on every page not only on the
   shop page." · "The blog page should have a separate sidebar menu in the
   header (they should use the same icon though in the header not make two,
   the button should just change / alternate sidebars depending on what
   page/tab is active)."

   The mechanism for this already exists and is identical on every route
   that has it: the button adds `.open` to the page's sidebar, and `.open`
   moves it from `left: -220px` (or -280) to `left: 0`. Verified on shop:
   closed x=-280 → open x=0 → closed x=-280.

   What the measurement found was two different gaps wearing the same
   symptom.

   BLOG — the drawer, the toggle and the open state are ALL present and
   working. `.blog-sidebar` gets `.open`, its `left` goes -220 → 0, and the
   button's `aria-expanded` flips to true. It is invisible because
   `storefront-BlogRoute.css` ends with, after building the whole thing:

       .blog-sidebar{ position: fixed; left: -220px; … }
       .blog-sidebar.open{ left: 0 }
       .blog-sidebar{ display: none }          ← this

   One declaration, and it is the entire bug. The sidebar holds Blog Home
   and four categories with post counts — fifteen items.

   PRODUCT — `.category-sidebar` is in the DOM with its real content
   ("Categories / Shop by type / All Products / Shirts…") and its off-canvas
   geometry, but the route renders NO toggle at all, so there has never been
   a way to open it. That one needs a button, which is in ng-mobile.js —
   created once, appended to `<body>` rather than into a React subtree, and
   only on routes that have a drawer and no button of their own. Never two. */

@media (max-width: 768px){
  .blog-sidebar,
  .product-page .category-sidebar,
  .search-page .category-sidebar{
    display: flex !important;
  }
  /* Both drawers sit under the header and above the page, like the shop's. */
  .blog-sidebar,
  .category-sidebar{
    top: var(--header-h-shrunk, 48px) !important;
    height: calc(100vh - var(--header-h-shrunk, 48px)) !important;
    z-index: 700 !important;
  }
  /* The button ng-mobile.js supplies is the same control, so it inherits
     everything section 39B set for `.sidebar-toggle`. */
  .ng-m-drawer-btn{ display: inline-flex !important; }
}
/* …and never on a wide screen, where the sidebar is part of the layout. */
@media (min-width: 769px){
  .ng-m-drawer-btn{ display: none !important; }
}

/* The product page's size chart. Earlier audits used a slug that does not
   exist, so they measured the 404 page and never saw a real product page's
   size guide — `table.size-chart` inside `.size-guide` keeps its own 9px
   head. A size chart read wrong is a garment returned. */
@media (max-width: 768px){
  .size-guide .size-chart th,
  .size-guide .size-chart td,
  table.size-chart th,
  table.size-chart td{
    font-size: 10.5px !important;
    letter-spacing: .6px !important;
  }
}

/* ─── 40G · The product page, audited for the first time ────────────────
   Every earlier sweep used `/product/1` as its sample. There is no product
   with that slug, so the router served the 404 page and twelve passes of
   "the product page is clean" were measuring a page with no product on it.
   Using a real slug — `delicate-lace-bralette` — surfaced a size chart at
   9px and six classes under the contrast line, on the page where a visitor
   chooses a size, a colour and a quantity before spending money.

   A sample that silently falls back to an error page is worse than no
   sample: it reports success. */

@media (max-width: 768px){
  .review-date,
  .rating-text,
  .option-label,
  .quantity-label,
  .product-page .option-label,
  .product-page .quantity-label{
    color: var(--ng-m-t3) !important;
  }
  /* The quantity stepper's − and + are the control, not a label. */
  .qty-btn{ color: var(--ng-m-t1) !important; }
  /* A chosen size has to be unmistakable — it is what gets shipped. */
  .size-option.selected,
  .size-option[aria-selected=true]{
    color: var(--ng-m-accent) !important;
    border-color: var(--ng-m-accent) !important;
  }
}

/* Three of those lost to `.product-page X{…!important}` — two classes deep,
   the same shape section 38 part C documents. Written at that depth. */
@media (max-width: 768px){
  .product-page .review-date{ color: var(--ng-m-t3) !important; }
  .product-page .rating-text{ color: var(--ng-m-t2) !important; }
  .product-page .size-option.selected{
    color: var(--ng-m-accent) !important;
    border-color: var(--ng-m-accent) !important;
    background: rgba(138, 43, 226, .18) !important;
  }
}

/* The drawer scrim (ng-mobile.js creates it; this draws it). Below the
   drawer, above everything else, and never in the way of the header — the
   header stays lit so the button that opened the drawer is still visible
   and still closes it. */
.ng-m-scrim{
  position: fixed;
  left: 0; right: 0;
  top: var(--header-h-shrunk, 48px);
  bottom: 0;
  z-index: 650;                 /* drawers sit at 700, header at 1000 */
  background: rgba(0, 0, 0, .58);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s ease;
}
.ng-m-scrim.is-on{
  opacity: 1;
  pointer-events: auto;
}
@media (min-width: 769px){
  .ng-m-scrim{ display: none !important; }
}
@media (prefers-reduced-motion: reduce){
  .ng-m-scrim{ transition: none; }
}


/* --- 41 . The Edit's sidebar IS the shop's sidebar ---------------------
   `/blog` and `/thread` render the same aside - `#blogSidebar`, filled by
   `jnSidebar()` in journal-shared.jsx - and its rules lived in each route's
   own stylesheet. That is the situation section 27 describes for the
   breadcrumb, and it had drifted from /shop in a way a visitor sees one click
   apart: a 10px Font Awesome glyph in a left-aligned row that slid 4px right
   on hover, against /shop's 38px neon chip, 11px label and pill count.

   The fix is NOT a second set of rules that happen to look the same - that is
   how the drift started. The markup now carries /shop's own class names
   (`.category-list`, `.category-item`, `.category-name`, `.category-count`)
   and every sidebar rule in sections 35 and 39 has `.blog-sidebar
   .category-list ...` added to its selector list. The two sidebars are laid
   out by the SAME declarations - the `38px minmax(0,1fr) auto` grid, the chip,
   the hairline, the hover shift, the active inset bar, the count pill are one
   definition each. Change the shop sidebar later and The Edit follows on its
   own.

   What is left here is only what genuinely differs: which sheet the tiles come
   from, and what to do about a category that has no tile yet.

   Note for whoever is next: `.blog-page` (sections 39H/40A/40C) is never set
   on any element - those three rules are dead. Left alone rather than wired
   up, because wiring it would change mobile layout that was signed off
   without it. */

/* -- 41A . Shell: one scroll container, and it tracks the header -------
   /shop keeps ONE scroller: `overflow:hidden` on the shell, `overflow-y:auto`
   on the list. /blog had `overflow-y:auto` on BOTH - two nested scroll
   containers over one 220px column, so the wheel acted on whichever was under
   the cursor and the sticky `.sidebar-header` could be scrolled out of its own
   sidebar.

   And /shop follows the shrinking header via `body:has(header.header-shrunk)`.
   /blog carried the `transition` for that move but no rule that ever made it:
   the top edge stayed at 80px while the header went to 56px, leaving a 24px
   slot of page showing above the sidebar for the whole of every scroll.

   `.main-layout > ...` rather than `.blog-sidebar` alone: the route sheets set
   `overflow-y:auto` on the same single class and load after this file, so a
   one-class rule here would lose the tie. */
.main-layout > .blog-sidebar{
  overflow: hidden;
  contain: layout paint;
}
.blog-sidebar .sidebar-list{
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0;
  scrollbar-width: thin;
  scrollbar-color: var(--border-purple) transparent;
}

body:has(header.header-shrunk) .blog-sidebar{
  top: var(--header-h-shrunk);
  height: calc(100vh - var(--header-h-shrunk));
  max-height: calc(100vh - var(--header-h-shrunk));
}

/* -- 41B . The one real difference: a different sheet ------------------
   `blog_category_sprite.png` is 2339x128, seventeen tiles, addressed exactly
   the way `category_icons_sprite.png` is on /shop - each tile names its own
   `--cx`/`--cw`, everything scales by `--sp-k` (`.22`, inherited from the
   shop rule). One request for the whole sidebar instead of seventeen. */
.blog-sidebar .category-list{
  --sp-w: 2339px;
  --sp-h: 128px;
}
.blog-sidebar .category-list .category-item::before{
  background-image: url(/blog_category_sprite.png);
}

/* the sheet, left to right */
.blog-sidebar .category-item.spr-beauty        { --cx:    4px; --cw: 129px }
.blog-sidebar .category-item.spr-trends        { --cx:  142px; --cw: 109px }
.blog-sidebar .category-item.spr-sustainability{ --cx:  260px; --cw: 138px }
.blog-sidebar .category-item.spr-care-tips     { --cx:  407px; --cw: 127px }
.blog-sidebar .category-item.spr-style-guide   { --cx:  543px; --cw: 138px }
.blog-sidebar .category-item.spr-color-theory  { --cx:  690px; --cw: 138px }
.blog-sidebar .category-item.spr-self-care     { --cx:  837px; --cw: 138px }
.blog-sidebar .category-item.spr-wellness      { --cx:  984px; --cw: 138px }
.blog-sidebar .category-item.spr-designer      { --cx: 1131px; --cw: 138px }
.blog-sidebar .category-item.spr-fit-guide     { --cx: 1278px; --cw: 138px }
.blog-sidebar .category-item.spr-quick-links   { --cx: 1425px; --cw: 131px }
.blog-sidebar .category-item.spr-trending      { --cx: 1565px; --cw: 104px }
.blog-sidebar .category-item.spr-latest        { --cx: 1678px; --cw: 132px }
.blog-sidebar .category-item.spr-popular       { --cx: 1819px; --cw: 131px }
.blog-sidebar .category-item.spr-archives      { --cx: 1959px; --cw: 122px }
.blog-sidebar .category-item.spr-2025-08       { --cx: 2090px; --cw: 113px }
.blog-sidebar .category-item.spr-2025-07       { --cx: 2212px; --cw: 123px }

/* -- 41C . A category with no tile keeps its glyph, in the same chip ---
   A `spr-<key>` CLASS is set in journal-shared.jsx and is the only thing that
   draws a tile. It was a `data-spr` attribute in 2.14.0 and the icons never
   drew: `oy()` in 20-app.jsx filters `attrs` through an allow-list and drops
   anything not in it, so every row arrived with its chip, no tile and a
   hidden glyph - an empty purple square. `cls` is never filtered.

   Splitting the rows into `.has-spr` / `.no-spr` is deliberate the other way
   too: if the tile were drawn for every row, a category with no artwork would
   compute `width: calc(var(--cw) * var(--sp-k))` with `--cw` unset, the
   declaration would be invalid at computed-value time, `background-position`
   would fall back to `0 0`, and every uncovered category would silently
   render THE FIRST TILE IN THE SHEET - the wrong icon, confidently.

   So those rows generate no `::before` at all and their Font Awesome glyph
   becomes grid column one instead: same 38px chip, same centring, same glow,
   same row height. A different icon, never a broken one.

   Fashion & Style, Lifestyle and Community are the three without artwork. */
.blog-sidebar .category-list .category-item.no-spr::before{
  content: none;
}
/* 28px, not 38px: the chip is a 38x38 BACKGROUND image, and the tile that
   sits in it renders 28px tall. Matching the tile - not the chip - is what
   keeps a fallback row exactly as tall as a sprite row. */
.blog-sidebar .category-list .category-item > .sidebar-fallback-icon{
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 28px;
  margin: 0;
  font-size: 17px;
  color: rgba(186,85,211,.82);
  text-shadow: 0 0 9px rgba(138,43,226,.55), 0 0 20px rgba(138,43,226,.22);
  transition: color .35s var(--ease-smooth, ease), text-shadow .35s var(--ease-smooth, ease);
}
.blog-sidebar .category-list .category-item:hover > .sidebar-fallback-icon,
.blog-sidebar .category-list .category-item.active > .sidebar-fallback-icon{
  color: #dcaef2;
  text-shadow: 0 0 12px rgba(186,85,211,.75), 0 0 26px rgba(138,43,226,.35);
}

/* -- 41D . Group headings ----------------------------------------------
   /shop has no groups, so these are The Edit's own. They are dividers, not
   rows: left-aligned to the chip column so the sidebar keeps one left edge
   the whole way down. */
.blog-sidebar .sidebar-group-title{
  padding: 15px 10px 7px;
  font-size: 8.5px;
  letter-spacing: 1.8px;
  color: rgba(186,85,211,.72);
  opacity: 1;
  border-top: 1px solid var(--ng-line);
}
.blog-sidebar .sidebar-group:first-child .sidebar-group-title{
  border-top: 0;
  padding-top: 6px;
}

/* -- 41E . Long names wrap instead of truncating -----------------------
   The one place The Edit cannot simply inherit /shop, and it is the content's
   fault, not the component's. /shop's categories are single words - "Robes",
   "Panties" - so section 39's `white-space: nowrap` and ellipsis never fire.
   The Edit's are phrases. Measured in its own 95px label column at /shop's
   10.5px / 1.7px:

       Beauty & Wellness  141px      Sustainability  108px
       Trends & Insights  135px      Trending Now    104px
       Fashion & Style    118px      Most Popular    104px

   Seven of the twelve rows overflowed, so the sidebar read "BEAUTY & W...",
   "TRENDS & I...", "SUSTAINABILI...". An ellipsis on a nav rail is worse than
   no label: the reader cannot tell two truncated categories apart.

   Two lines fixes the phrases, but only if the break lands on the space. At
   10.5px "Sustainability" is 108px with nowhere to break, so it split as
   SUSTAINABILI / TY. One type step down - 10px, 0.6px tracking - puts every
   single-word label inside the column and leaves every multi-word one
   breaking at its space. `overflow-wrap` stays as the net for a future name
   longer than any of these, rather than the mechanism.

   The rows do not change height for it: two lines of 10px at `line-height:
   1.25` is 25px, under the 28px tile beside it, so every row is still the
   42px /shop sets. */
.blog-sidebar .category-list .category-name{
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  white-space: normal;
  overflow: hidden;
  text-overflow: clip;
  overflow-wrap: break-word;
  word-break: normal;
  font-size: 10px !important;
  letter-spacing: .6px !important;
}

/* -- 41F . The sidebar header is /shop's header band -------------------
   /shop's `.sidebar-header` is the `.category-header` treatment in miniature:
   a violet-black gradient, a gradient hairline under it, a travelling light
   on that hairline, and a shimmering gradient-clipped title with a glyph
   beside it. The Edit's was a plain 22px block of `var(--surface)` with two
   lines of flat text - the one part of the sidebar that still looked like a
   different product.

   The appearance comes from the five rules in section 35, which now carry
   `.blog-sidebar .sidebar-*` in their selector lists. What is here is the
   geometry those rules assume, which lives in storefront.css scoped to
   `.shop-page`, plus the one thing that SHOULD differ: the glyph. /shop
   stacks layers; The Edit opens a book. Same size, same mask, same glow. */
.blog-sidebar .sidebar-header{
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 64px;               /* --shop-side-hdr */
  box-sizing: border-box;
  position: relative;
  text-align: center;
}
:root{
  --ng-ic-journal: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M249.6 471.5c10.8 3.8 22.4-4.1 22.4-15.5l0-377.4c0-4.2-1.6-8.4-5-11C247.4 52 202.4 32 144 32C93.5 32 46.3 45.3 18.1 56.1C6.8 60.5 0 71.7 0 83.8L0 454.1c0 11.9 12.8 20.2 24.1 16.5C55.6 460.1 105.5 448 144 448c33.9 0 79 14 105.6 23.5zm76.8 0C353 462 398.1 448 432 448c38.5 0 88.4 12.1 119.9 22.6c11.3 3.8 24.1-4.6 24.1-16.5l0-370.3c0-12.1-6.8-23.3-18.1-27.6C529.7 45.3 482.5 32 432 32c-58.4 0-103.4 20-123 35.6c-3.3 2.6-5 6.8-5 11L304 456c0 11.4 11.7 19.3 22.4 15.5z'/%3E%3C/svg%3E");
}
.blog-sidebar .sidebar-title::before{
  -webkit-mask-image: var(--ng-ic-journal);
          mask-image: var(--ng-ic-journal);
}

/* --- 42 . The article card - one definition, /blog and /thread ---------
   The card on the blog home and the card in Related Articles are the same
   card: same shell, same image fade, same bands, same type. They were two
   components (`.blog-card-compact` in the blog route sheet, `.related-card`
   in the thread route sheet) and they looked it - one a tall centred card
   with a dissolving photo, the other a flat 170px tile in a scroll track.

   Defined once, here, for both. The selectors are two classes deep so they
   beat the single-class rules the route sheets still carry, whichever order
   those load in - the same reason section 41 is scoped the way it is.

   The markup carries both sets of class names (thread-live.jsx), so nothing
   in either route sheet had to be deleted to make this work. */

.main-content .blog-card-compact,
.ng-related-grid .related-card{
  --blog-img-h: 120px;
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid rgba(138,43,226,.2);
  border-radius: 2px;
  cursor: pointer;
  background: linear-gradient(160deg, rgba(22,10,36,.96) 0%, rgba(10,6,18,.98) 60%, rgba(14,8,24,.97) 100%);
  box-shadow: inset 0 1px 0 rgba(186,85,211,.08), 0 2px 14px rgba(0,0,0,.55);
  transition: transform .45s var(--ease-luxe), border-color .45s var(--ease-luxe), box-shadow .45s var(--ease-luxe);
}
.main-content .blog-card-compact:hover,
.ng-related-grid .related-card:hover{
  border-color: rgba(186,85,211,.4);
  box-shadow: inset 0 1px 0 rgba(186,85,211,.18), 0 10px 36px rgba(138,43,226,.22), 0 2px 0 rgba(186,85,211,.1);
  transform: translateY(-3px);
}
/* the hairline across the top */
.main-content .blog-card-compact::before,
.ng-related-grid .related-card::before{
  content: "";
  position: absolute; top: 0; left: 0; right: 0; height: 1px;
  z-index: 5; pointer-events: none;
  background: linear-gradient(90deg, transparent 0%, rgba(186,85,211,.2) 25%, rgba(220,180,255,.28) 50%, rgba(186,85,211,.2) 75%, transparent 100%);
  transition: opacity .45s ease;
}
.main-content .blog-card-compact:hover::before,
.ng-related-grid .related-card:hover::before{
  background: linear-gradient(90deg, transparent 0%, rgba(186,85,211,.38) 25%, rgba(220,180,255,.5) 50%, rgba(186,85,211,.38) 75%, transparent 100%);
}

/* -- 42A . The image dissolves into the card ---------------------------
   Two things at once, because either alone falls short. The mask fades the
   PHOTO out over its last third, so the picture ends by dissolving rather
   than stopping at a hard 120px line. The veil is the purple: a gradient
   laid over the image, transparent at the top and reaching the card's own
   `rgba(18,9,30,.97)` at the bottom, so image and content meet in the same
   colour and there is no seam to find.

   It lives on the content block's `::before` because the image is an `<img>`
   and cannot carry a pseudo-element; `z-index:-1` inside the content's
   stacking context puts it over the photo and under the text. */
.main-content .blog-image-compact,
.ng-related-grid .related-card .blog-image-compact{
  width: 100%;
  height: var(--blog-img-h);
  object-fit: cover;
  flex-shrink: 0;
  position: relative;
  z-index: 1;
  transition: transform .5s var(--ease-luxe);
  -webkit-mask-image: linear-gradient(to bottom,
      #000 0%, #000 54%, rgba(0,0,0,.74) 76%, rgba(0,0,0,.3) 92%, transparent 100%);
          mask-image: linear-gradient(to bottom,
      #000 0%, #000 54%, rgba(0,0,0,.74) 76%, rgba(0,0,0,.3) 92%, transparent 100%);
}
.main-content .blog-card-compact:hover .blog-image-compact,
.ng-related-grid .related-card:hover .blog-image-compact{ transform: scale(1.05) }

.main-content .blog-content-compact,
.ng-related-grid .related-card .blog-content-compact{
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: 16px;
}
.main-content .blog-content-compact::before,
.ng-related-grid .related-card .blog-content-compact::before{
  content: "";
  position: absolute; left: 0; right: 0;
  top: calc(-1 * var(--blog-img-h));
  height: var(--blog-img-h);
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(to bottom,
      rgba(138,43,226,.07)   0%,
      rgba(138,43,226,.15)  36%,
      rgba(96,34,158,.36)   64%,
      rgba(26,13,42,.82)    87%,
      rgba(18,9,30,.97)    100%);
  transition: opacity .45s var(--ease-luxe);
}
.main-content .blog-card-compact:hover .blog-content-compact::before,
.ng-related-grid .related-card:hover .blog-content-compact::before{ opacity: .86 }

/* -- 42B . The bands, and why they are fixed ---------------------------
   `.blog-excerpt` used to be `flex: 1`, so it absorbed every pixel of slack
   in the card. A short excerpt did not sit under its title - its BOX grew,
   and the stat row and the response were pushed to the card's floor with a
   hole between them and the text. Three cards in a row, three different
   holes in three different places, because the hole's size was whatever that
   article's excerpt did not use.

   Every band is now a fixed height instead, so the same band sits on the
   same line across a row: two lines of title, one byline, three of excerpt.
   The slack goes to the response block, which is the one element that can
   absorb it without looking wrong - it is a quote box, and a quote box with
   a little more air in it still reads as a quote box. */
.main-content .blog-category-compact,
.ng-related-grid .blog-category-compact{
  display: flex; align-items: center; justify-content: center; gap: 4px;
  flex: 0 0 auto;
  margin-bottom: 8px;
  font-size: 8px; font-weight: 400; letter-spacing: 1px; text-transform: uppercase;
  color: rgba(186,85,211,.7);
}
.main-content .blog-title-compact,
.ng-related-grid .blog-title-compact{
  flex: 0 0 auto;
  min-height: calc(1.35em * 2);
  margin-bottom: 8px;
  font-size: 12px; font-weight: 500; line-height: 1.35; letter-spacing: .5px;
  text-align: center; text-transform: uppercase; color: #fff;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.main-content .blog-author-date,
.ng-related-grid .blog-author-date{
  display: flex; align-items: center; justify-content: space-between;
  flex: 0 0 auto;
  margin-bottom: 8px;
  font-size: 10px;
}
.main-content .blog-author, .ng-related-grid .blog-author,
.main-content .blog-date,   .ng-related-grid .blog-date{
  display: inline-flex; align-items: center; gap: 5px; min-width: 0;
}
.main-content .blog-author, .ng-related-grid .blog-author{
  color: rgba(186,85,211,.7); font-weight: 500; letter-spacing: .3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.main-content .blog-date, .ng-related-grid .blog-date{
  color: rgba(255,255,255,.25); font-weight: 300; flex: 0 0 auto;
}
.main-content .blog-author i, .ng-related-grid .blog-author i{ font-size: 8px; color: rgba(186,85,211,.55) }
.main-content .blog-date i,   .ng-related-grid .blog-date i{ font-size: 8px; color: rgba(255,255,255,.26) }
.main-content .blog-card-compact:hover .blog-author i,
.ng-related-grid .related-card:hover .blog-author i{ color: rgba(186,85,211,.8) }

/* three lines, always three lines */
.main-content .blog-excerpt,
.ng-related-grid .blog-excerpt{
  flex: 0 0 auto;
  height: calc(1.45em * 3);
  margin-bottom: 10px;
  font-size: 11px; font-weight: 300; line-height: 1.45;
  text-align: center; color: rgba(255,255,255,.38);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.main-content .blog-stats-compact,
.ng-related-grid .blog-stats-compact{
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  gap: 6px; margin-bottom: 10px;
  flex-wrap: wrap;
}

/* -- 42C . The response block is the card's floor ----------------------
   It takes the slack (`flex: 1 1 auto`) so no band above it has to, and it
   is always drawn - an article with no responses yet gets the same box,
   muted, saying so. Hiding it left a card-shaped hole and made one card in
   a row visibly shorter than its neighbours. */
.main-content .recent-response{
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 52px;
  margin-top: 0;
}
.main-content .recent-response-empty{
  background: rgba(255,255,255,.012);
  border-color: rgba(138,43,226,.08);
  box-shadow: none;
}
.main-content .recent-response-empty .response-avatar{
  background: rgba(138,43,226,.14);
  border: 1px solid rgba(138,43,226,.2);
  color: rgba(186,85,211,.7);
  font-size: 8px;
}
.main-content .recent-response-empty .response-author{ color: rgba(255,255,255,.38); font-weight: 400 }
.main-content .recent-response-empty .response-text{ color: rgba(255,255,255,.26); font-style: italic }
.main-content .blog-card-compact:hover .recent-response-empty{
  border-color: rgba(138,43,226,.18);
  background: rgba(138,43,226,.04);
}
/* Related articles have no response payload (`shop_blog_cards($r, false)`),
   so their floor is the stat row and it gets the slack instead. */
.ng-related-grid .blog-stats-compact{ margin-top: auto; margin-bottom: 0 }

/* -- 42D . The refresh transition reaches the related rail -------------
   `ng-shop.js` drives it off `.rec-refresh-btn` and the section's
   `.recommendations-grid`, both of which the related rail now has, so the
   JavaScript already worked. What did not was the CSS: every rule below was
   written for `.recommendation-card`, and an article card is a
   `.related-card`. Same skeleton, same shimmer, same staggered reveal. */
.recommendations-grid .related-card > *{
  transition: opacity .3s ease, filter .3s ease, transform .3s ease;
}
.recommendations-grid.ng-rec-out .related-card > *{
  opacity: 0; filter: blur(6px); transform: translateY(6px) scale(.985);
}
.recommendations-grid.ng-rec-out .related-card::after,
.recommendations-grid.ng-rec-in  .related-card::after{
  content: "";
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background:
    linear-gradient(rgba(255,255,255,.055), rgba(255,255,255,.055)) 50% calc(100% - 78px) / 62% 8px no-repeat,
    linear-gradient(rgba(186,85,211,.16),  rgba(186,85,211,.16))  50% calc(100% - 60px) / 32% 8px no-repeat,
    linear-gradient(rgba(255,255,255,.04), rgba(255,255,255,.04)) 50% 22% / 100% 120px no-repeat;
}
.recommendations-grid.ng-rec-in .related-card::after{ opacity: 0; transition: opacity .4s ease }
.recommendations-grid.ng-rec-out .related-card > .blog-content-compact::before{ opacity: 0 }
.recommendations-grid.ng-rec-in .related-card > *{
  animation: ngRecIn .6s cubic-bezier(.19, 1, .22, 1) both;
}
.recommendations-grid.ng-rec-in .related-card:nth-child(2) > *{ animation-delay: .06s }
.recommendations-grid.ng-rec-in .related-card:nth-child(3) > *{ animation-delay: .12s }
.recommendations-grid.ng-rec-in .related-card:nth-child(4) > *{ animation-delay: .18s }


/* ═══════════════════════════════════════════════════════════════════════
   42 · THE MOBILE PASS — the phone gets the desktop's layout, not a
        sideways version of it
   ═══════════════════════════════════════════════════════════════════════
   The desktop storefront is a column of bordered panels, each with a
   centred heading and a grid of cards inside it. The phone had been given
   something structurally different: the panels lost their borders, the
   grids became horizontal scrollers, and the headings went flush left. So
   the two did not read as the same site — the phone showed two and a half
   cards of everything and hid the rest behind a swipe nobody signalled.

   This section puts the desktop's STRUCTURE on the phone at the phone's
   proportions. Nothing here changes a pixel above 768px.

   The one idea behind all of it: a horizontal scroller inside a vertically
   scrolling page is a place things go to be missed. Every one of them
   becomes a grid.


   ─── 42A · No sideways scrolling ──────────────────────────────────────
   Three strips scrolled: the category icons, New Arrivals, and each
   compact row. All three become grids that show everything they hold. */

@media (max-width: 768px){

  /* The ten categories, five across and two down, instead of four visible
     and six behind a swipe. `!important` throughout because the rules being
     replaced (section 40D) are themselves `!important`. */
  .categories-grid{
    display: grid !important;
    grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
    gap: 16px 2px !important;
    overflow: visible !important;
    padding: 4px 0 2px !important;
    margin-right: 0 !important;
    margin-top: 2px !important;
    scroll-snap-type: none !important;
  }
  .categories-grid .category-item{
    flex: initial !important;
    width: auto !important;
    min-width: 0 !important;
    gap: 6px !important;
  }
  /* A fifth of 346px is 69px, and "ACCESSORIES" at the desktop's 9px/2px
     tracking needs 82. The label comes down rather than the column going
     up: five across is what makes the whole set visible at once, which is
     the point of the change. */
  .categories-grid .category-name{
    font-size: 7.5px !important;
    letter-spacing: .5px !important;
    line-height: 1.25 !important;
    text-align: center !important;
    color: var(--ng-m-t3) !important;
  }

  /* New Arrivals and the compact rows: two up, wrapped, all of it visible. */
  .new-arrivals-grid,
  .compact-products-grid{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
    overflow: visible !important;
    scroll-snap-type: none !important;
    padding: 0 !important;
    margin-bottom: 0 !important;
  }
  .new-arrivals-grid > *,
  .compact-products-grid > *{
    flex: initial !important;
    width: auto !important;
    min-width: 0 !important;
    scroll-snap-align: none !important;
  }


  /* ─── 42B · The panels come back ────────────────────────────────────
     On desktop each section is a bordered panel sitting on the page. The
     phone stripped the border, the background and the padding, which is
     why it read as one undifferentiated column: nothing told you where a
     section started. The panels return at phone padding — the container
     was never the problem, its execution was. */
  .new-arrivals-section,
  .compact-products-section > .compact-product-category{
    background: linear-gradient(135deg, #070709, #0b0a0f) !important;
    border: 1px solid var(--ng-line) !important;
    border-image: none !important;
    border-radius: 2px !important;
    padding: 14px 12px !important;
    margin: 0 !important;
  }
  .compact-products-section{
    display: flex !important;
    flex-direction: column !important;
    gap: 14px !important;
    margin: 0 !important;
    padding: 0 !important;
  }

  /* The heading band, restored and centred like the desktop's. Flush left
     with no rule above it was the "cluttered" half of the complaint: the
     titles did not separate the sections, they just sat in the flow. */
  .category-header{
    justify-content: center !important;
    padding: 18px 0 12px !important;
    margin: 0 !important;
    border-top: 1px solid var(--ng-line) !important;
    border-bottom: 1px solid var(--ng-line) !important;
    background: linear-gradient(135deg, #0a0610f2, #0d0716f0) !important;
  }
  .category-header-content{ align-items: center !important; }
  .category-header-title-row{ justify-content: center !important; }

  /* THE CONTRAST FIX. The title is painted with a clipped gradient and
     `-webkit-text-fill-color: transparent`, and no phone rule ever reset
     the fill — so the `color:#ffffffe6` written for mobile has been doing
     nothing, and the heading has been rendering as the desktop's animated
     shimmer at 12px, which is where the washed-out look came from. Turning
     the fill back on is what makes the colour below apply at all. */
  .category-header-text,
  a.category-header-text{
    background: none !important;
    -webkit-text-fill-color: currentColor !important;
    animation: none !important;
    color: var(--ng-m-t1) !important;
    font-size: 12.5px !important;
    letter-spacing: 2.6px !important;
    font-weight: 500 !important;
    text-align: center !important;
    justify-content: center !important;
  }
  .category-header-subtitle{
    text-align: center !important;
    color: var(--ng-m-t3) !important;
    letter-spacing: 1.2px !important;
  }
  /* Same treatment for the per-row headings inside each panel. */
  .compact-subcategory-header{ text-align: center !important; }
  .compact-subcategory-title{
    justify-content: center !important;
    color: var(--ng-m-t1) !important;
    letter-spacing: 1.8px !important;
  }
  .compact-subcategory-subtitle{
    text-align: center !important;
    color: var(--ng-m-t3) !important;
  }


  /* ─── 42C · The container, with the page showing past it ────────────
     `.container` was 100% wide with a 1px rule at x=0 and x=389, so the
     border was against the bezel and the body never showed — the panel and
     the page were the same surface and the layout had no outer edge. It
     now sits inboard, which is what the desktop does at 85% width, and the
     ground shows down both sides.

     9px of margin, not 16: the gutter INSIDE the container is what holds
     content off the edge, and paying for the inset twice would leave a
     346px column on a 390px screen. */
  .container{
    width: auto !important;
    margin-left: 9px !important;
    margin-right: 9px !important;
    padding-left: 12px !important;
    padding-right: 12px !important;
    border-radius: 2px !important;
  }
  /* The heading band is edge-to-edge INSIDE the container, the way the
     desktop's is — it is a divider, and a divider with a gap each side is
     just a box. */
  .container > .category-header-wrapper{
    margin-left: -12px !important;
    margin-right: -12px !important;
  }


  /* ─── 42D · The breadcrumb goes ─────────────────────────────────────
     Three links and two connectors in 130px, next to a search field they
     were already colliding with. The trail tells a phone nothing it cannot
     get from the back gesture.

     The PATH goes; the search does not. They share a wrapper but they are
     not the same control, and search is the one thing on that bar a phone
     actually wants — so it takes the whole row instead of 132px of it. */
  .timeline-path{ display: none !important; }
  .timeline-breadcrumb .timeline-search,
  .timeline-search{
    order: 0 !important;
    flex: 1 1 100% !important;
    width: 100% !important;
  }
  .timeline-breadcrumb-wrapper{
    padding: 8px 10px !important;
    margin-bottom: 14px !important;
  }


  /* ─── 42E · Sort and filters, laid out rather than queued ───────────
     Every row was a nowrap scroller, so each one showed two and a half
     options with the rest off-screen on both sides — you could not see
     what the choices were, let alone which was selected. Nothing here has
     more options than fits in two rows of a grid. */
  .shop-page .shop-filter-grid{
    margin: 4px 0 18px !important;
  }
  .shop-page .shop-filter-grid > .sfg-row{
    padding: 10px !important;
  }
  /* Sort: four options, two by two, each filling its half. */
  .shop-page .shop-filter-grid > .sfg-row:nth-child(1){
    flex-wrap: wrap !important;
    row-gap: 8px !important;
  }
  /* Three across, not two: seven options in two columns is four rows and
     190px of sort control before a single product — taller than the cards
     it is sorting. */
  .shop-page .shop-filter-grid > .sfg-row:nth-child(1) > .sfg-options{
    display: grid !important;
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
    gap: 3px !important;
    overflow: visible !important;
    flex: 1 1 100% !important;
  }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(1) .sfg-btn{
    flex: initial !important;
    width: 100% !important;
    min-width: 0 !important;
    padding: 0 4px !important;
    font-size: 9px !important;
    letter-spacing: .6px !important;
  }
  /* Sizes, colours and price bands: wrap instead of scroll. */
  .shop-filter-grid .sfg-options{
    flex-wrap: wrap !important;
    overflow: visible !important;
    justify-content: flex-start !important;
    row-gap: 6px !important;
  }
  .shop-page .shop-filter-grid .sfg-label{
    justify-self: start !important;
    text-align: left !important;
  }
  .shop-page .shop-filter-grid .sfg-row{ justify-items: stretch !important; }
  /* The price bands are the widest chips; three per row reads as a set. */
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(8){
    display: grid !important;
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
    gap: 5px !important;
  }
  .shop-page .shop-filter-grid > .sfg-row:nth-child(2) > :nth-child(8) .sfg-btn{
    width: 100% !important;
    min-width: 0 !important;
    padding: 0 4px !important;
  }


  /* ─── 42F · The product grid, off the bezel ─────────────────────────
     Cards started 16px from the edge and the gap between them was 12, so
     the outside margin read as tighter than the inside one and the whole
     grid looked pushed against the glass. 18 outside against 12 inside
     puts more air at the edge than between, which is what reads as a
     margin rather than an overflow. */
  .shop-page .main-content,
  .product-page .main-content,
  .search-page .main-content{
    padding-left: 18px !important;
    padding-right: 18px !important;
  }
  .shop-page .products-grid{
    gap: 12px !important;
    margin-bottom: 22px !important;
  }
  /* One label, not a stack. Each card carries two: `.product-badge` is the
     merchandising one the catalogue sets (NEW, BESTSELLER, LIMITED) and
     `.tier-tag` is the tier. On a 173px card they stack into the corner and
     on several products they print the same word twice — "LIMITED" over
     "LIMITED" — because the tier and the badge agree. The tier goes: the
     card's border is already coloured by it, so nothing is lost, and what
     is left is the one label that says something the card does not. */
  .shop-page .product-card .tier-tag,
  .compact-product-card .tier-tag,
  .new-arrival-card .tier-tag{
    display: none !important;
  }


  /* ─── 42G · "You may also like", compact ────────────────────────────
     Four cards at 262px each plus two stacked buttons is most of a screen
     for something nobody came for. The card loses its fixed height and the
     footer goes back to the two columns the desktop has. */
  .recommendations-grid{
    gap: 10px !important;
    margin-bottom: 0 !important;
  }
  /* A DEFINITE HEIGHT, not `auto`. The card's only in-flow child is an
     `object-fit:cover` image at `height:100%`, and `.recommendation-info`
     is absolutely positioned — so with the card on `height:auto` the image
     resolves to its own intrinsic aspect ratio and the card becomes
     `column width ÷ photo aspect`. That is different on every page,
     because the column is 169px inside the cart's container and 174px on
     the full-bleed product rail, and it differs between two cards in the
     same grid whenever the photos do. Fixing the height is what makes the
     card one size everywhere. */
  .recommendation-card{
    height: 196px !important;
    min-height: 0 !important;
    max-height: none !important;
  }
  .recommendation-card .rec-action-btn{
    min-height: 34px !important;
  }
  /* View More and Refresh side by side. `storefront.css` drops the footer
     to one column below 900px; there is room for two at 390 once they stop
     carrying desktop padding. */
  .rec-footer{
    display: grid !important;
    grid-template-columns: 1fr 1fr !important;
    gap: 8px !important;
    margin: 16px 0 0 !important;
  }
  .rec-footer .more-like-it-btn,
  .rec-footer .rec-refresh-btn{
    width: 100% !important;
    min-width: 0 !important;
    margin: 0 !important;
    padding: 0 8px !important;
    font-size: 9.5px !important;
    letter-spacing: 1.2px !important;
    white-space: nowrap !important;
  }


  /* ─── 42H · The drawer the home page never had ──────────────────────
     `ng-mobile.js` now builds a `.category-sidebar` out of the category
     links the home page already renders, so the button is on every page
     and opens whatever that page's sidebar is. The synthesized one is
     appended to <body> rather than into a layout, so it needs the phone
     drawer geometry that `.shop-page .category-sidebar` gets from its own
     scoped rule. */
  .ng-m-synth-sidebar{
    position: fixed !important;
    left: -280px !important;
    top: 0 !important;
    height: 100dvh !important;
    width: 270px !important;
    z-index: 700 !important;
    display: flex !important;
    flex-direction: column !important;
    padding-top: 48px !important;
    overflow-y: auto !important;
    background: #050505f7 !important;
    -webkit-backdrop-filter: blur(24px) !important;
    backdrop-filter: blur(24px) !important;
    border-right: 1px solid rgba(138, 43, 226, .12) !important;
    transition: left .4s cubic-bezier(.19, 1, .22, 1) !important;
  }
  .ng-m-synth-sidebar.open{ left: 0 !important; }

  /* The product route's drawer opens at 220px where every other one is
     270. Same drawer, same contents, same gesture — the width should not
     tell you which page you happened to open it from.

     The closed offset has to move with the width. The product route parks
     its drawer at `left:-200px`, which hides a 200px panel and leaves 70px
     of a 270px one sitting on the page — a strip of category rows down the
     left of every product, pushing the content across. Width and offset
     are one measurement, not two. */
  .product-page .main-layout > .category-sidebar,
  .product-page .category-sidebar{
    width: 270px !important;
    left: -290px !important;
  }
  .product-page .main-layout > .category-sidebar.open,
  .product-page .category-sidebar.open{ left: 0 !important; }
  /* The clones arrive as the strip's icon-over-label tiles. In a drawer
     they are rows, which is what the sidebar's own list styling expects.

     The sprite tokens have to be restated here. `--sp-k` lives on
     `.categories-grid`, and the tile's width is
     `calc(var(--cw) * var(--sp-k))` — outside that element the custom
     property is unset, the calc is invalid at computed-value time, and the
     row renders with no icon at all. The same three tokens the shop
     sidebar uses put the artwork back. */
  .ng-m-synth-sidebar .category-list{
    --sp-k: .22;
    --sp-w: 1294px;
    --sp-h: 128px;
    --ng-chip: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38'%3E%3Crect x='.5' y='.5' width='37' height='37' rx='5' fill='rgba%28138%2C43%2C226%2C.07%29' stroke='rgba%28138%2C43%2C226%2C.30%29'/%3E%3C/svg%3E");
    --ng-chip-hover: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38'%3E%3Crect x='.5' y='.5' width='37' height='37' rx='5' fill='rgba%28138%2C43%2C226%2C.16%29' stroke='rgba%28186%2C85%2C211%2C.65%29'/%3E%3C/svg%3E");
    padding: 12px 10px 16px !important;
    list-style: none !important;
    margin: 0 !important;
  }
  .ng-m-synth-sidebar .category-item:before{
    flex: 0 0 auto;
    margin-inline: calc((38px - var(--cw) * var(--sp-k)) / 2) !important;
  }
  .ng-m-synth-sidebar .category-item{
    background: var(--ng-chip) 7px 50% / 38px 38px no-repeat,
                linear-gradient(90deg, rgba(255, 255, 255, .018), rgba(255, 255, 255, 0)) !important;
    border: 1px solid transparent !important;
    border-radius: 3px !important;
    text-decoration: none !important;
  }
  .ng-m-synth-sidebar .category-item:hover{
    border-color: rgba(138, 43, 226, .3) !important;
    background: var(--ng-chip-hover) 7px 50% / 38px 38px no-repeat,
                linear-gradient(90deg, rgba(138, 43, 226, .15), rgba(138, 43, 226, .02)) !important;
  }
  /* The header, the same component the shop and The Edit sidebars use. */
  .ng-m-synth-sidebar .sidebar-header{
    position: relative;
    overflow: hidden;
    padding: 16px 16px 15px !important;
    background: linear-gradient(135deg, rgba(8, 4, 14, .98), rgba(12, 6, 20, .97), rgba(8, 4, 14, .98)) !important;
    border-bottom: 1px solid;
    border-image: var(--border-blend) 1;
    text-align: center !important;
  }
  .ng-m-synth-sidebar .sidebar-title{
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 10px !important;
    font-size: 13px !important;
    font-weight: 500 !important;
    letter-spacing: 2.6px !important;
    text-transform: uppercase !important;
    color: rgba(255, 255, 255, .95) !important;
  }
  .ng-m-synth-sidebar .sidebar-subtitle{
    font-size: 9.5px !important;
    font-weight: 300 !important;
    letter-spacing: .9px !important;
    text-transform: uppercase !important;
    color: rgba(255, 255, 255, .4) !important;
    margin-top: 4px !important;
  }
  .ng-m-synth-sidebar .category-item{
    flex-direction: row !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: 13px !important;
    width: auto !important;
    min-height: 48px !important;
    padding: 5px 12px 5px 7px !important;
  }
  .ng-m-synth-sidebar .category-name{
    font-size: 10px !important;
    letter-spacing: 2px !important;
    text-align: left !important;
    color: rgba(255, 255, 255, .82) !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   43 · THE MOBILE PASS, SECOND ROUND
   ═══════════════════════════════════════════════════════════════════════
   Section 42 gave the phone the desktop's structure. This gives it the
   desktop's surface and its feel: the container is a surface again, the
   section headings are the real component rather than a plain line of
   text, and the navigation stops announcing itself.

   Everything is inside `max-width: 768px`. Nothing above it moves. */

@media (max-width: 768px){

  /* ─── 43A · The container is a surface, not a hole ──────────────────
     Section 42 gave it margins and kept its borders, so the page finally
     showed down both sides — but the panel between those borders was
     transparent, so what showed THROUGH it was the same ground. Two rules
     and no surface: an outline drawn around nothing.

     `var(--surface)` is what the desktop paints there, and it is the same
     token, not a phone approximation of it. */
  .container{
    background: var(--surface) !important;
  }


  /* ─── 43B · The section heading is the desktop component ────────────
     The phone had been given a flattened copy: no band, no border, no
     pulse, the title left-aligned. Section 42 centred it and made it
     legible, which fixed the contrast but left it a line of text where
     the desktop has a piece of chrome.

     This is the desktop rule, at phone padding. The gradient text comes
     back with it — at 14px on a 1440 screen it reads as a shimmer, and
     the reason it looked washed out here was never the gradient, it was
     that `-webkit-text-fill-color: transparent` had no reset so the 12px
     mobile colour could not apply and nothing was controlling it. Now the
     desktop's own treatment runs, deliberately, at a size chosen for the
     phone. */
  .category-header{
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    position: relative !important;
    overflow: visible !important;
    margin: 0 !important;
    padding: 15px 18px !important;
    background: linear-gradient(135deg, #08040efa, #0c0614f7, #08040efa) !important;
    border-top: 1px solid !important;
    border-bottom: 1px solid !important;
    border-left: none !important;
    border-right: none !important;
    border-image: var(--border-blend) 1 !important;
  }
  /* The travelling pulse along the band — the "glowing border animation".
     It was `display:none` on the phone. Same 2px bar, same 19.6s loop; the
     width goes from 15% to 22% only because 15% of 372px is 56px and reads
     as a speck rather than a sweep. */
  /* 15%, NOT 22%. `circularPulse` is shared with the desktop and drives
     `left` to a maximum of 85% — so the bar's width and that 85% have to
     add to exactly 100 or it hangs off the end of the band. At 15% the
     right edge lands flush on the band's right edge, which is how the
     desktop keeps it in without clipping anything.
     Widening it to 22% made 85 + 22 = 107%: a 7% overhang, about 28px at
     this width, for the ten seconds of the loop the bar spends parked at
     85%. Nothing above it clips — `.category-header` and its wrapper are
     both `overflow:visible` — so those 28px went straight into the
     document's scroll width and the whole page could be dragged sideways,
     which is the growing and shrinking. `overflow:hidden` would also stop
     it, but it would clip the bar's own `top:-2px` / `bottom:-2px` travel
     and the pulse would never appear on the borders at all. */
  .category-header:before{
    content: "" !important;
    display: block !important;
    position: absolute !important;
    width: 15% !important;
    height: 2px !important;
    background: linear-gradient(90deg, transparent, var(--purple), transparent) !important;
    border-radius: 1px !important;
    z-index: 3 !important;
    pointer-events: none !important;
    animation: circularPulse 19.6s linear infinite !important;
  }
  .category-header-text,
  a.category-header-text{
    background: linear-gradient(105deg, #ffffffc0, #fff 40%, #e8bfff, #fff 70%, #ffffffb0) !important;
    background-size: 200% 100% !important;
    -webkit-background-clip: text !important;
            background-clip: text !important;
    -webkit-text-fill-color: transparent !important;
    animation: sectionTitleShimmer 6s ease-in-out infinite !important;
    font-size: 13px !important;
    font-weight: 500 !important;
    letter-spacing: 2.8px !important;
    text-transform: uppercase !important;
  }
  .category-header-icon{
    display: inline-block !important;
    color: var(--purple-light) !important;
    font-size: 13px !important;
    filter: drop-shadow(0 0 6px rgba(186, 85, 211, .4)) !important;
  }
  /* The band is edge-to-edge inside whatever holds it — a divider with a
     gap each side is a box. */
  .container > .category-header-wrapper,
  .main-content > .category-header-wrapper,
  .recommendations-full-width > .category-header-wrapper{
    margin-left: -12px !important;
    margin-right: -12px !important;
  }


  /* ─── 43C · Navigation stops announcing itself ──────────────────────
     Every route change raised `ngVeil` — a full-screen overlay with the
     logo and a loading bar — in the same frame as the tap. That overlay
     is what a page load looks like, so client-side navigation was being
     dressed up as the thing it exists to avoid. On desktop the chunk is
     usually there before the veil can register; on a phone it sits.

     The veil is not removed, it is deferred. Navigation that resolves
     inside 280ms never paints it and the new page simply appears, which
     is the app feel. Navigation that takes longer still gets its loading
     screen, because at that point the person needs to know something is
     happening.

     `!important` because `ng-live.css` loads after this file and sets
     `animation: none` on exactly this element. */
  .ng-route-loader:not(.is-idle):not(.is-leaving){
    animation: ngVeilDefer .01s linear .28s both !important;
  }
  @keyframes ngVeilDefer{
    from{ opacity: 0; visibility: hidden }
    to  { opacity: 1; visibility: visible }
  }


  /* ─── 43D · One card size, everywhere ───────────────────────────────
     `.compact-product-card` is used on the index rails, in The Edit and
     in the cart's suggestions, and it was picking up a different height
     in each because each parent sized it. The card is one component; it
     gets one size, set on the card. */
  .compact-product-card{
    height: 200px !important;
    min-height: 0 !important;
    max-height: none !important;
  }
  /* NO HEIGHT ON THE IMAGE. This rule used to clamp it to 112px, and that
     is what broke the fade.
       · `.compact-image-container` does not exist — the real name is
         `.compact-product-image-container`, so that arm never matched.
       · `.compact-product-image` IS the <img>, so the clamp landed on the
         photo. The card is 200px and the image became 112px, top-aligned,
         leaving 88px of bare card underneath.
     The two gradients are drawn against the CARD box, not the image: the
     card's own `::after` is 78% of 200px = 156px tall. So the fade started
     part-way up the photo, the photo stopped in a hard line at 112px, and
     the gradient carried on over empty background — which reads as a solid
     black block rather than a fade.
     The image keeps `height:100%` from the base sheet and fills the card,
     and the fade lands on the photo the whole way, as it does on desktop
     and in New Arrivals. */


  /* ─── 43E · Counts that fill their rows ─────────────────────────────
     Seven New Arrivals in two columns is three full rows and a widow.
     Three columns of two is six, which fills, and six is also what fits
     before the section outgrows a thumb's scroll. The seventh is not
     hidden from the site — Browse and the category pages still carry it. */
  .new-arrivals-grid{
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
    gap: 8px !important;
  }
  .new-arrivals-grid > :nth-child(n+7){ display: none !important; }
  /* A third of 346px is 115px, so the card comes down with the column. */
  .new-arrivals-grid .arrival-card{
    height: 176px !important;
    min-height: 0 !important;
  }
  /* The matching pair for the arrival card is deliberately absent too.
     Neither `.arrival-image` nor `.arrival-image-container` exists — the
     real markup is `.arrival-image-wrap` wrapping a bare <img> — so the
     rule that used to sit here never matched anything, which is the only
     reason New Arrivals still looked right while the compact card did not.
     Making it match would break New Arrivals in exactly the same way. */
  .new-arrivals-grid .arrival-name{
    font-size: 9px !important;
    letter-spacing: .2px !important;
    line-height: 1.25 !important;
  }
  .new-arrivals-grid .arrival-price{ font-size: 10px !important; }
  .new-arrivals-grid .arrival-badge{
    font-size: 6.5px !important;
    padding: 2px 5px !important;
  }
  /* Four recommendations, not six: two clean rows under a product. */
  .recommendations-grid > :nth-child(n+5){ display: none !important; }
  /* Five trending articles on The Edit, not ten. */
  .trending-grid > .trending-card:nth-child(n+6){ display: none !important; }


  /* ─── 43F · Sort loses SALE ─────────────────────────────────────────
     Seven options across three columns leaves one alone on the last row
     with two empty cells beside it. Six is two full rows. SALE goes
     rather than something else because it is the one on its way out, and
     the sale products are still reachable — the ON SALE count in the
     header above is a filter. */
  .shop-page .shop-filter-grid > .sfg-row:nth-child(1) .sfg-btn:nth-child(7){
    display: none !important;
  }


  /* ─── 43G · The hero arrows get out of the text ─────────────────────
     Both sat at the vertical middle of the slide, which is exactly where
     the subtitle runs, so a 30px circle was parked on top of the copy at
     each end. On a phone there is no room beside the text, so they move
     out of its band entirely and sit low, clear of the last line and
     above the call to action. */
  .nav-arrows{
    top: auto !important;
    bottom: 14px !important;
    transform: none !important;
    width: 30px !important;
    height: 30px !important;
    z-index: 6 !important;
  }
  /* Paired in the right corner rather than one at each edge. Split, the
     left one lands on the call-to-action, which sits in the bottom-left —
     and a control overlapping a button is worse than one overlapping text,
     because it takes the tap. Together they also read as a pair, which is
     what they are. */
  .nav-arrows.prev-arrow{ right: 52px !important; left: auto !important; }
  .nav-arrows.next-arrow{ right: 14px !important; left: auto !important; }
  /* No padding is added to the slide's copy to make room. The hero is a
     fixed height, so padding does not push the arrows away — it pushes the
     TITLE out of the top of the box, which is what it did on the first
     attempt. Moving the arrows out of the text band was the whole fix; the
     copy is already clear of them down there. */


  /* ─── 43H · The Edit, compacted ─────────────────────────────────────
     The magazine block was the longest section on the phone: full-width
     feature cards at desktop height, one after another, each looking like
     the last.

     The first attempt made the rows two-up and let the cards size to their
     content. Both were wrong. `.mag-card` is a BACKGROUND-IMAGE tile with
     its text in an absolutely-positioned overlay, so it has no content
     height — `height:auto` collapsed it to 2px. And a half-width column is
     narrower than the newsletter panel's own content, so that clipped.

     So: the rows stay one column and the cards keep a definite height,
     just a much shorter one. The saving comes from the height, which is
     where the space actually was, not from the column count. */
  .magazine-section{
    display: flex !important;
    flex-direction: column !important;
    gap: 10px !important;
    margin: 0 !important;
    padding: 0 !important;
  }
  .mag-row{
    display: flex !important;
    flex-direction: column !important;
    gap: 10px !important;
    margin: 0 !important;
    padding: 0 !important;
    width: 100% !important;
  }
  .mag-col-stack{
    display: flex !important;
    flex-direction: column !important;
    gap: 10px !important;
    width: 100% !important;
    min-width: 0 !important;
  }
  .mag-row > *,
  .mag-col-stack > *{
    width: 100% !important;
    min-width: 0 !important;
    margin: 0 !important;
  }
  .mag-card{
    /* `flex` has to be released along with the heights. These cards are
       flex items in a column, where `flex-basis` IS the height — a basis
       of `163.8px` inherited from the desktop rail quietly outranked every
       `height` set here, which is why the small cards kept coming back the
       same size as the feature one. */
    flex: 0 0 auto !important;
    height: 164px !important;
    min-height: 0 !important;
    max-height: none !important;
  }
  /* The secondary cards come down further, so the section has a rhythm
     instead of three identical blocks — "repetitive" was half the note.
     `min-height` has to come with it: `storefront.css` floors every
     `.mag-card-sm` at `160px !important`, which silently held the height
     at the feature card's and made this rule look applied when it was not. */
  .mag-card.mag-card-sm{
    height: 128px !important;
    min-height: 0 !important;
  }
  /* The newsletter is a real content panel, not a picture, so it sizes to
     what it holds and keeps the full width its form needs. */
  .mag-newsletter{
    height: auto !important;
    min-height: 0 !important;
    width: 100% !important;
    padding: 16px 14px !important;
  }
  .mag-card-title,
  .mag-feature-row{
    font-size: 12px !important;
    line-height: 1.3 !important;
    letter-spacing: .2px !important;
  }
  .mag-card-label{ font-size: 10px !important; }
  .mag-card-date,
  .mag-card-sublabel{ font-size: 9px !important; }

  /* ─── 43I · The footer meets the tab bar ────────────────────────────
     `body` reserves 68px at the bottom for the fixed tab bar, and that
     reservation sat BELOW the footer — so the page ended, then 68px of
     ground showed, then the bar. The footer should be the last thing you
     see before the bar, touching it.

     The padding moves off the body and onto the footer, so the footer
     itself is what fills the reserved strip. Pages without a footer keep
     the body padding, or their last section would sit under the bar. */
  body:has(footer){
    padding-bottom: 0 !important;
  }
  /* `footer`, not `body > footer`. The index, the cart and the lookbook
     put the footer directly in `body`, but `.shop-page` and
     `.product-page` wrap the header, the content AND the footer in one
     div — so the child selector missed them, their wrapper kept its own
     68px bottom padding, and that padding sat BELOW the footer as dead
     scroll. The footer is the last thing on every page; it is the thing
     that should absorb the tab bar's height, wherever it happens to sit
     in the tree. */
  footer{
    margin-bottom: 0 !important;
    padding-bottom: calc(var(--ng-m-tabbar) + env(safe-area-inset-bottom, 0px) + 18px) !important;
  }
  .shop-page,
  .product-page{
    padding-bottom: 0 !important;
  }


  /* ─── 43J · The search field does not zoom the page ─────────────────
     iOS zooms to any input whose computed font-size is under 16px, and
     zooming is what breaks the full-screen feel — the page comes back at
     a different scale and has to be pinched out again.
     `ng-polish.css:4346` already set 16px on the breadcrumb search, but
     only there: the home hero's `.search-input`, the drawer's and the
     autocomplete's were all still below the threshold.
     Every text input on the phone is 16px now. It is the one number iOS
     accepts, so it is not a preference. */
  input[type="text"],
  input[type="search"],
  input[type="email"],
  input[type="tel"],
  input[type="password"],
  input[type="number"],
  textarea,
  select,
  .search-input,
  .timeline-search-input,
  .sidebar-search-input{
    font-size: 16px !important;
  }
  /* The placeholder can stay small — it is not what iOS measures. */
  .search-input::placeholder,
  .timeline-search-input::placeholder{
    font-size: 12.5px !important;
    letter-spacing: 1px !important;
  }


  /* ─── 43K · The product page reads down the middle ──────────────────
     Name, rating, price and description were flush left in a 314px
     column, and the size and colour rows were 130px wide inside it — so
     the page had three different left edges and a lot of dead space to
     the right of every control. The prose centres, and the two controls
     that are really pickers take the whole row, which is also what makes
     them thumb-sized. */
  .product-page .product-title,
  .product-page .product-price,
  .product-page .product-description,
  .product-page .price-description-row{
    text-align: center !important;
    width: 100% !important;
  }
  .product-page .product-rating,
  .product-page .color-options{
    display: flex !important;
    justify-content: center !important;
    flex-wrap: wrap !important;
    width: 100% !important;
  }
  .product-page .price-description-row{
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    gap: 6px !important;
  }
  /* THE COLUMN HAS TO STRETCH FIRST. `.product-options` is a flex column
     whose children size to their content, so every `.option-group` inside
     it was 128px wide in a 314px column — colour, size and quantity were
     not failing to fill their row, they were filling a row that had
     collapsed around them. Stretching the group is what gives the rules
     below something to fill. */
  .product-page .product-options{
    align-items: stretch !important;
    width: 100% !important;
  }
  .product-page .option-group{
    width: 100% !important;
    align-self: stretch !important;
  }
  .product-page .option-label{ text-align: center !important; }

  /* Size and quantity fill the row they sit in. */
  /* Flex, not `repeat(auto-fit, minmax(0, 1fr))`. With a zero minimum
     auto-fit is free to lay as many tracks as it likes and then divide
     nothing between them — it resolved to four 2.5px columns and a 28px
     row. Flex children with `flex: 1 1 0` share the row by definition. */
  .product-page .size-options{
    display: flex !important;
    flex-wrap: nowrap !important;
    gap: 6px !important;
    width: 100% !important;
  }
  .product-page .size-options .size-option{
    flex: 1 1 0 !important;
    width: auto !important;
    min-width: 0 !important;
    margin: 0 !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
    text-align: center !important;
  }
  .product-page .quantity-row{
    display: flex !important;
    align-items: center !important;
    gap: 12px !important;
    width: 100% !important;
  }
  .product-page .quantity-row .quantity-label{ flex: 0 0 auto !important; }
  .product-page .quantity-controls{
    display: flex !important;
    flex: 1 1 auto !important;
    justify-content: space-between !important;
    align-items: center !important;
    width: auto !important;
    min-width: 0 !important;
    max-width: none !important;
  }
  .product-page .quantity-controls .qty-input{
    flex: 1 1 auto !important;
    min-width: 0 !important;
    width: auto !important;
    text-align: center !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   44 · THE MOBILE PASS, THIRD ROUND
   ═══════════════════════════════════════════════════════════════════════
   Mostly the pages that are not the index: the cart, the checkout, and the
   four routes that wrap their own chrome.

   ONE THING TO KNOW BEFORE EDITING ANY OF THIS. The per-route stylesheets
   (`storefront-CartRoute.css`, `storefront-LookbookRoute.css`, …) are
   injected into <head> by the bundler AFTER this file, so a plain rule in
   a route chunk beats a plain rule here whatever the specificity. Several
   rules below carry `!important` for that reason alone — not because they
   are fighting another polish rule, but because they are reaching past a
   sheet that loads later. */

@media (max-width: 768px){

  /* ─── 44A · The remove control is a control ─────────────────────────
     `.ci-remove` was `color:#ffffff25` — 15% white — on an 8px uppercase
     label with 4px of padding. Barely visible, well under any contrast
     floor, and the one button on the cart row that the 44px touch-target
     pass missed, because its `:is()` list names `.clear-cart-btn` and not
     this. It now looks like the rest of the platform: a bordered chip on
     the same near-black, and it turns red on press rather than being red
     by default — destructive styling belongs on the confirmation, not on
     something sitting next to a quantity stepper. */
  .ci-remove{
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 6px !important;
    min-height: 34px !important;
    padding: 0 12px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: #0e0e10 !important;
    color: rgba(255, 255, 255, .62) !important;
    transition: color .3s ease, border-color .3s ease, background .3s ease !important;
  }
  .ci-remove span{
    font-size: 9px !important;
    font-weight: 500 !important;
    letter-spacing: 1.4px !important;
    text-transform: uppercase !important;
  }
  .ci-remove i{ font-size: 10px !important; }
  .ci-remove:hover,
  .ci-remove:active,
  .ci-remove:focus-visible{
    color: #ff6b6b !important;
    border-color: rgba(255, 107, 107, .45) !important;
    background: rgba(255, 59, 48, .08) !important;
  }

  /* The checkout line price. `.os-value` sits at 75% white and never had a
     mobile rule, while `.os-label` beside it was lifted to 78% — so the
     product name was brighter than its own price and the hierarchy read
     backwards. The price is the number somebody is checking. */
  .os-value{
    color: rgba(255, 255, 255, .95) !important;
    font-weight: 500 !important;
  }
  .os-label{ color: var(--ng-m-t3) !important; }
  .os-row.os-total .os-value,
  .os-value.os-total{ color: #fff !important; }


  /* ─── 44B · The rail header reaches the container's borders ─────────
     On the index the band is flush to the container's edge: its wrapper
     carries `margin: 0 -12px`, which exactly cancels the container's 12px
     of padding. Inside the recommendations rail it has the same −12px but
     ALSO keeps `padding: 0 20px` from `storefront.css`, whose two-class
     selector outranked the polish layer's one-class reset — so the band
     stopped 20px short of the border on the cart and checkout while the
     identical component reached it on the index.
     Same specificity, later sheet: the padding goes and the two match. */
  .recommendations-full-width .category-header-wrapper{
    padding-left: 0 !important;
    padding-right: 0 !important;
  }


  /* ─── 44C · Every page meets its header and its footer ──────────────
     The index is flush because of three things working together: `body`
     reserves exactly the fixed header's height, `.container` is
     `flex: 1` inside `body`'s column, and `body` is `min-height: 100vh`.
     The container therefore starts at the header's bottom edge and grows
     to meet the footer however short the page is.

     `.shop-page` and `.product-page` break the second and third. They
     wrap the header, the content AND the footer in one div, and that div
     carries `min-height: 100vh` with no `flex: 1`. `body` has already
     reserved 48px at the top and 68px at the bottom, so a child forced to
     a full viewport makes the document `100vh + 116px` — about 116px of
     dead scroll under the footer, every page, guaranteed.

     `flex: 1` is what the index uses, and it keeps working if the header
     height ever changes; `min-height: calc(100vh - …)` would need editing
     the day it does. */
  .shop-page,
  .product-page{
    flex: 1 1 auto !important;
    min-height: 0 !important;
    display: flex !important;
    flex-direction: column !important;
  }
  /* And the inner column has to be the part that grows. */
  .shop-page .main-layout,
  .product-page .main-layout{
    flex: 1 1 auto !important;
    min-height: 0 !important;
  }
  .shop-page .main-content,
  .product-page .main-content{
    flex: 1 1 auto !important;
    min-height: 0 !important;
  }
  /* The sidebar is a fixed drawer on a phone, but it still carries the
     desktop's `min-height: calc(100vh - var(--header-h)) !important` — and
     once `.main-layout` becomes a column that min-height is added to the
     column's height rather than sitting beside it. A whole viewport of
     nothing, below the content. */
  .shop-page .main-layout > .category-sidebar,
  .product-page .main-layout > .category-sidebar{
    min-height: 0 !important;
    align-self: auto !important;
  }

  /* The Edit turns its layout row into `display:block` at this width,
     which takes `.main-content` out of a flex column — so it collapses to
     its content height and the footer floats up under the last card. A
     column is what the other pages use and what `.main-content{flex:1}`
     is already written for. */
  .blog-route .main-layout,
  body > .main-layout,
  .main-layout:not(.shop-page .main-layout):not(.product-page .main-layout){
    display: flex !important;
    flex-direction: column !important;
    flex: 1 1 auto !important;
    min-height: 0 !important;
  }
  body > .main-layout > .main-content{
    flex: 1 1 auto !important;
    min-height: 0 !important;
  }

  /* The lookbook uses `.container`, like the index — but its route sheet
     loads after this one and re-declares the container plainly, so it went
     edge-to-edge on a different background while every other page was
     inset 9px on `var(--surface)`. These already exist in section 43;
     restated with the weight needed to reach past a later sheet. */
  .container{
    width: auto !important;
    margin-left: 9px !important;
    margin-right: 9px !important;
    padding-left: 12px !important;
    padding-right: 12px !important;
    background: var(--surface) !important;
    border-left: 1px solid var(--ng-line) !important;
    border-right: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    flex: 1 1 auto !important;
  }
  .lb-hero{
    border-radius: 2px 2px 0 0 !important;
  }

  /* THE SAME CONTAINER ON THE PAGES THAT DO NOT HAVE ONE. The index, the
     cart and the lookbook render a `.container` and now all three sit 9px
     in on `var(--surface)` with a hairline. Shop, product and The Edit
     render `.main-content` instead — edge to edge, transparent — so the
     site changed shape when you moved between them.

     It takes the container's treatment, and its horizontal padding drops
     from 18px to 12px to match: 9 + 1 + 12 is the same 22px of inset the
     index gives its content, so a product card sits the same distance
     from the glass on every page. */
  .main-layout > .main-content{
    /* `width: auto`, and it is load-bearing. Every one of these carries a
       `width: 100% !important` from its own sheet, and a 100%-wide box
       with 9px of margin each side is 18px wider than the space it has —
       it hangs off the right edge and takes the page's scroll width with
       it. Margins only inset a box that is allowed to be narrower. */
    width: auto !important;
    max-width: none !important;
    margin-left: 9px !important;
    margin-right: 9px !important;
    padding-left: 12px !important;
    padding-right: 12px !important;
    background: var(--surface) !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    box-sizing: border-box !important;
  }


  /* ─── 44D · Frequently bought together, two by two ──────────────────
     `storefront.css` sets two columns at ≤768 and then cancels itself
     ninety bytes later with a `!important` single column at ≤480 — so on
     a 390px phone the pair rendered as two unrelated stacked banners and
     the sidebar block ran long. Two columns, four items, one block. */
  .product-page .compact-recommendations{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
    padding: 12px !important;
  }
  .product-page .compact-recommendations > .compact-rec-card:nth-child(n+5){
    display: none !important;
  }
  .product-page .compact-rec-card{
    height: 104px !important;
    border-radius: 2px !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   45 · THE EDIT'S TABS
   ═══════════════════════════════════════════════════════════════════════
   The control itself is built by `ng-mobile.js` from the cards already on
   the page — see the comment there for why it cannot be done in CSS. This
   is only its appearance, and the one rule that does the hiding.

   `[data-ng-edit-hidden]` rather than a class, so that a card hidden by
   the tab filter can never be confused with one the site itself hid: the
   fourth `.mag-card-sm` is `display:none` from `storefront.css`, and a
   shared class would have made those two states indistinguishable. */

@media (max-width: 768px){

  .ng-m-edit-tabs{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 3px;
    margin: 0 0 10px;
    padding: 3px;
    border: 1px solid var(--ng-line);
    border-radius: 2px;
    background: #0b0b0d;
  }
  .ng-m-edit-tab{
    min-height: 34px;
    padding: 0 10px;
    border: 1px solid transparent;
    border-radius: 2px;
    background: transparent;
    color: rgba(255, 255, 255, .58);
    font-size: 9px;
    font-weight: 500;
    letter-spacing: 1.6px;
    text-transform: uppercase;
    cursor: pointer;
    transition: color .3s ease, border-color .3s ease, background .3s ease;
  }
  .ng-m-edit-tab.is-on{
    color: #fff;
    border-color: rgba(186, 85, 211, .55);
    background: linear-gradient(135deg, rgba(138, 43, 226, .45), rgba(186, 85, 211, .2));
  }

  [data-ng-edit-hidden]{ display: none !important; }
}


/* ═══════════════════════════════════════════════════════════════════════
   46 · THE MOBILE PASS, FOURTH ROUND
   ═══════════════════════════════════════════════════════════════════════
   Eight notes. Two of them — the container's top edge and the account
   dashboard — turned out not to be cosmetic, and both are fixed at the
   cause rather than at the symptom.

   The cascade note from section 44 still applies: the per-route
   stylesheets are injected after this file, so a plain rule in a route
   chunk beats a plain rule here whatever the specificity. `!important`
   below is usually reaching past a later sheet, not fighting a polish
   rule. */

@media (max-width: 768px){

  /* ─── 46A · Every container meets the header ────────────────────────
     Note seven: "on most pages the top of the container is either
     detached, seems to have conflicting borders, or unnecessary
     border-radius".

     All three, and one cause behind them.

     The header renders 48px tall on every route. The space the page
     reserves for it does not: each route chunk declares its own
     `--header-h`, and `body`'s top padding follows that variable rather
     than the element. Measured at 390px:

         home, shop, lookbook, cart, search   --header-h 48px   → flush
         blog, account                        --header-h 52px   → 4px gap
         dashboard                            --header-h 60px   → 12px gap
         help                                 --header-h 80px   → 22px gap

     So the "detachment" is a strip of page background showing between the
     header's bottom border and the top of the container, and it is a
     different width on every route because every route guesses
     differently. Nothing sizes the header FROM this variable — it is a
     reservation, and on four routes it is simply wrong.

     Pinning the variable is the whole fix, and it fixes more than the gap:
     the drawers are positioned with `top: var(--header-h)` and sized with
     `calc(100vh - var(--header-h))`, so on those same four routes they
     were hanging below the header and running off the bottom by the same
     few pixels. One number, measured off the element that is actually on
     screen, and every consumer comes right at once.

     Custom properties take `!important`, and they need it: the route
     chunks redeclare these on `:root` and load later. */
  :root{
    --header-h: 48px !important;
    --header-h-shrunk: 48px !important;
    --ng-m-header: 48px;
  }
  body{
    padding-top: var(--ng-m-header) !important;
  }

  /* The other two words in the note. "Conflicting borders" is the
     container's own 1px top border sitting directly under the header's 1px
     bottom border — two hairlines, 2px of line, where the design has one.
     "Unnecessary border-radius" is the 2px corner rounding, which on a box
     whose top edge is meant to disappear into the header cuts a small
     notch out of each top corner and shows the page behind it.

     The container keeps its left, right and bottom borders and its bottom
     corners; the top edge simply stops existing. */
  .container,
  .main-layout > .main-content,
  .shop-page .main-content,
  .product-page .main-content{
    border-top: 0 !important;
    border-top-left-radius: 0 !important;
    border-top-right-radius: 0 !important;
    margin-top: 0 !important;
  }
  /* The lookbook hero is the first thing inside its container and carries
     its own top rounding, which would put the notch back. */
  .lb-hero{
    border-top-left-radius: 0 !important;
    border-top-right-radius: 0 !important;
  }


  /* ─── 46B · The footer stops at the tab bar ─────────────────────────
     Note two: too much empty space at the bottom.

     Section 43I moved the tab bar's reservation onto the footer, which was
     right, but it was not the only reservation. Measured on the index:

         footer            padding-bottom  86px   (tab bar 68 + 18 slack)
         .footer-bottom    padding-bottom  76px   (its own, from desktop)
                                          ────
         below the last line of text      162px

     Only 68px of that is behind the tab bar. The other 94px is empty
     ground, which is what the note is about — and the larger half of it
     was inside `.footer-bottom`, not in the rule section 43I wrote, which
     is why trimming that rule last round barely moved it.

     The inner padding goes entirely: the footer's own bottom padding
     already separates the last line from the bar. The slack comes down
     from 18px to 8px, which is a visible gap above the bar rather than a
     band of nothing. */
  /* The page classes are named explicitly because `storefront.css` carries
     `.shop-page .footer-bottom{padding:10px 20px 80px!important}` and the
     same for `.product-page` — two classes, where `footer .footer-bottom`
     is one class and a type. Specificity beats source order, so the plain
     selector fixed the index and left 80px of nothing on the two pages
     people actually scroll to the bottom of. */
  .shop-page .footer-bottom,
  .product-page .footer-bottom,
  .cart-page .footer-bottom,
  .lb-page .footer-bottom,
  div[class$="-page"] .footer-bottom,
  footer .footer-bottom{
    padding-bottom: 0 !important;
    margin-bottom: 0 !important;
  }
  footer{
    margin-bottom: 0 !important;
    padding-bottom: calc(var(--ng-m-tabbar) + env(safe-area-inset-bottom, 0px) + 8px) !important;
  }


  /* ─── 46C · The Edit: one wide card, four squares, the newsletter ────
     Note one. Both tabs get the same shape, and it is a grid.

     The cards are spread across two `.mag-row`s and a nested
     `.mag-col-stack`, which is a desktop layout decision — row one is a
     feature plus a stack plus the newsletter — and it has nothing to do
     with which tab a card belongs to. `display: contents` dissolves those
     three wrappers so every card becomes a direct item of one grid on the
     section, and the grid can then place them by role instead of by which
     div they happen to live in.

     The roles are assigned in `ng-mobile.js` on every tab switch, because
     which card is first depends on which tab is showing and the cards a
     tab hides are still in the tree. See `tagEditRoles()`.

     `order` rather than source order for the newsletter: it is the last
     child of row ONE, so in document order it would land in the middle of
     the squares. */
  .magazine-section{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
    align-items: start !important;
    margin: 0 !important;
    padding: 0 !important;
  }
  .magazine-section .mag-row,
  .magazine-section .mag-col-stack{
    display: contents !important;
  }
  /* `storefront.css` hides every `.mag-card-sm` that lands in the bottom
     row, and it does it inside its own `@media (max-width:768px)` — so
     this is a phone-only rule, and on a wide screen those two release
     tiles have always been part of the bottom row.

     It was a reasonable rule when the block was one long stack: two more
     cards on an already too-long section. It is the wrong rule now. There
     is no bottom row on a phone any more — the wrappers are dissolved
     above and the cards are grouped by tab — and hiding these two costs
     the Releases tab two of its five tiles, leaving the 2x2 permanently
     short. They come back, and only at this width; desktop is untouched
     either way. */
  .magazine-section .mag-row-bottom .mag-card-sm{
    display: block !important;
  }
  .magazine-section .mag-row-bottom .mag-card-sm[data-ng-edit-hidden]{
    display: none !important;
  }
  /* `display:contents` would otherwise resurrect a row the tab hid: it is
     a display value, and it is set with `!important` right above. */
  .magazine-section .mag-row[data-ng-edit-hidden],
  .magazine-section .mag-col-stack[data-ng-edit-hidden],
  .magazine-section [data-ng-edit-hidden]{
    display: none !important;
  }
  .ng-m-edit-tabs{
    grid-column: 1 / -1 !important;
    order: 0 !important;
  }
  /* The wide one. A rectangle, not a square — it is the tab's headline. */
  .magazine-section .mag-card.ng-m-edit-lead{
    grid-column: 1 / -1 !important;
    order: 1 !important;
    height: 150px !important;
    min-height: 0 !important;
    max-height: none !important;
    aspect-ratio: auto !important;
    flex: 0 0 auto !important;
  }
  /* The squares. `aspect-ratio` rather than a height, so they stay square
     at any width — 167px on a 390px phone, 148px on a 350px one — and
     `height:auto` has to come with it or the 164px from section 43H wins
     and they are not square at all. */
  .magazine-section .mag-card.ng-m-edit-tile{
    grid-column: auto !important;
    order: 2 !important;
    height: auto !important;
    min-height: 0 !important;
    max-height: none !important;
    aspect-ratio: 1 / 1 !important;
    flex: 0 0 auto !important;
  }
  /* An odd tile at the end spans the row instead of sitting alone with a
     hole beside it. Releases has five tiles and never hits this; Journal
     is capped at four posts by the API and always does. */
  .magazine-section .mag-card.ng-m-edit-tile.ng-m-edit-wide{
    grid-column: 1 / -1 !important;
    aspect-ratio: auto !important;
    height: 124px !important;
  }
  .magazine-section .mag-newsletter{
    grid-column: 1 / -1 !important;
    order: 3 !important;
    height: auto !important;
    min-height: 0 !important;
    width: auto !important;
    padding: 16px 14px !important;
  }
  /* Section 43H set `width:100%` on every row child to stop the desktop
     rail widths leaking in. In a grid that is the column width, which is
     right for a square but wrong for anything spanning two columns with a
     border — 100% of the grid AREA plus the border overflows it. */
  .magazine-section .mag-row > *,
  .magazine-section .mag-col-stack > *{
    width: auto !important;
    box-sizing: border-box !important;
  }
  /* The squares are half the width the copy was set for. */
  .magazine-section .mag-card.ng-m-edit-tile .mag-card-body{
    padding: 10px 11px !important;
  }
  .magazine-section .mag-card.ng-m-edit-tile .mag-card-title{
    font-size: 11px !important;
    line-height: 1.25 !important;
  }
  .magazine-section .mag-card.ng-m-edit-tile .mag-card-summary{
    display: none !important;
  }
  .magazine-section .mag-card.ng-m-edit-lead .mag-card-summary{
    display: -webkit-box !important;
    -webkit-line-clamp: 2 !important;
    -webkit-box-orient: vertical !important;
    overflow: hidden !important;
  }


  /* ─── 46D · You may also like: one button, and the shop's card ───────
     Note three, in two parts.

     The button first. Every rail card carried View and Claim side by side,
     each 58px wide, and View only does what tapping the card already does
     — `ngOpenProduct(p)` is the card's own click handler. Two controls,
     one of them redundant, each below a comfortable target. Claim takes
     the row.

     The card second. "The same size as the regular shop item cards": the
     shop grid card computes to 167 x 240 at 390px, and the rail card was
     coming out 174 x 196 on the product page and 190 x 196 on the
     dashboard, because the rail was full-bleed and the grid was dividing a
     different width on every page. 46E puts the rail inside the container,
     which makes the two columns 167px wide by arithmetic rather than by a
     number typed in here; the height is set to match. */
  .recommendation-card .rec-quick-view-btn{
    display: none !important;
  }
  .recommendation-actions{
    display: flex !important;
    gap: 0 !important;
    margin-top: 8px !important;
  }
  .recommendation-card .rec-add-cart-btn{
    flex: 1 1 auto !important;
    width: 100% !important;
    /* The shop card's Claim button, to the pixel: ng-polish:4811 gives
       `.arrival-add-btn` 36px and 10px type. The rail's was 34px and a
       different radius, which is exactly the kind of near-miss that reads
       as sloppiness without being nameable. */
    min-height: 36px !important;
    height: 36px !important;
    font-size: 10px !important;
    letter-spacing: .6px !important;
    border-radius: 2px !important;
    padding: 0 10px !important;
  }
  .recommendation-card{
    height: 240px !important;
    min-height: 0 !important;
    max-height: none !important;
  }
  /* `storefront.css` puts 20px 24px of padding inside a card that is now
     167px wide — the title had 119px to sit in. The shop card's details
     block uses 10px, so this does too. */
  .recommendation-info{
    padding: 10px !important;
  }
  .recommendation-title{
    margin-bottom: 3px !important;
  }
  .recommendations-grid{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: var(--ng-m-card-gap) !important;
    overflow: visible !important;
    padding: 0 !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
  }


  /* ─── 46E · The rail sits inside the container ──────────────────────
     Note six: "the you may also like section breaks out of the page
     container completely, it should be within the border-left and
     border-right of the container".

     It does, and the reason is worth writing down because it decides the
     shape of the fix.

     `ng-polish.css:1876` makes `.recommendations-full-width` full-bleed:
     `width:100vw; margin-left:calc(50% - 50vw)`. `ng-polish.css:2763`
     cancels that for a rail that is a DIRECT CHILD of `.container` — which
     is the cart and the checkout, and nothing else. On the dashboard the
     rail is three levels down inside `.container`, so the child combinator
     misses it; on the shop and the product page there is no `.container`
     at all — the rail is a sibling of `.main-content` inside
     `.main-layout`. Three of the four pages that render it were breaking
     out, and the one that was not was the one the exception was written
     for.

     So: stop being full-bleed everywhere, at the source. */
  .recommendations-full-width{
    width: 100% !important;
    max-width: 100% !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
    box-sizing: border-box !important;
  }

  /* Inside a `.container` — the cart, the checkout, the dashboard — that
     is all it takes: the rail is now as wide as the container's content
     box, and its header band outdents 12px to reach the inner edge of the
     borders, which is what the index does and what "within the border-left
     and border-right perfectly" means. */

  /* The shop and the product page have no `.container` to be inside. The
     rail is the LAST CHILD OF THE PAGE — a sibling of `.main-layout`, not
     of `.main-content` inside it — and `.main-content` is the bordered box
     on those pages. Giving the rail the same horizontal box and joining
     the two — no border between them, rounded only at the outer corners —
     makes them read as one container rather than as a box with something
     loose underneath it. */
  .shop-page > .recommendations-full-width,
  .product-page > .recommendations-full-width{
    width: auto !important;
    max-width: none !important;
    margin: 0 9px !important;
    padding: 0 12px !important;
    background: var(--surface) !important;
    border-left: 1px solid var(--ng-line) !important;
    border-right: 1px solid var(--ng-line) !important;
    border-bottom: 1px solid var(--ng-line) !important;
    border-top: 0 !important;
    border-radius: 0 0 2px 2px !important;
  }
  /* Only drop the container's bottom edge when there is in fact a rail
     below it to carry on — on a page without one the border is the bottom
     of the box and has to stay. */
  .shop-page:has(> .recommendations-full-width) .main-content,
  .product-page:has(> .recommendations-full-width) .main-content{
    border-bottom: 0 !important;
    border-bottom-left-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
    margin-bottom: 0 !important;
  }

  /* The band itself, on every page that has one. −12px cancels the
     container's 12px of padding exactly, so the band's edges land on the
     inner face of the left and right borders. */
  .recommendations-full-width > .category-header-wrapper,
  .recommendations-full-width .category-header-wrapper{
    margin-left: -12px !important;
    margin-right: -12px !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
  }

  /* Customer reviews, the other half of note six. Same component, same
     container, and it was inset differently: `storefront.css` pads
     `.reviews-section` by 16px and then zeroes the wrapper's margins, so
     the band started 16px inside the container's padding while the rail's
     started 12px outside it — 28px apart at the left edge, on one page,
     between two instances of the same header.

     The section's own padding goes and the band outdents like every other
     one, so both headers now run border to border. */
  .product-page .reviews-section{
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  .product-page .reviews-section .category-header-wrapper{
    margin-left: -12px !important;
    margin-right: -12px !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  .product-page .reviews-section .category-header{
    padding-left: 14px !important;
    padding-right: 14px !important;
  }


  /* ─── 46F · The search field ────────────────────────────────────────
     Note four: "sticks out too much and could be a lot smoother".

     `.timeline-search` is the one search a phone ever sees — the hero and
     the sticky bars are both `display:none` at this width — and it was
     drawn as an outlined box: a `rgba(255,255,255,.015)` fill, which is
     within rounding of the page behind it, inside a `rgb(45,44,45)`
     border. A rectangle made of nothing but its outline, sitting on top of
     the page instead of in it.

     Inverted: the field is now DARKER than the surface it sits on, so it
     reads as a well the page is cut into, and the border can drop to a
     whisper because the fill is doing the work. The accent arrives on
     focus and only on focus.

     The placeholder moves from 22% to 38% white. 22% on this ground is
     2.1:1 — below any floor, and a placeholder is the only label this
     field has. */
  .timeline-search{
    /* No height here. Section 42's `min-height: 46px` is matched to the
       46px input inside the wrapper, and a shorter wrapper clips a focused
       field — which is what Safari scrolls and rescales the page to
       reveal. This block is about colour, not the box. */
    background: #09090b !important;
    border: 1px solid rgba(255, 255, 255, .06) !important;
    border-radius: 2px !important;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .55) !important;
    transition: border-color .35s ease, background .35s ease, box-shadow .35s ease !important;
  }
  .timeline-search:focus-within{
    background: #0b0910 !important;
    border-color: rgba(138, 43, 226, .42) !important;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .55), 0 0 0 3px rgba(138, 43, 226, .10) !important;
  }
  .timeline-search-input{
    height: 100% !important;
    padding: 0 12px !important;
    color: rgba(255, 255, 255, .86) !important;
    letter-spacing: .3px !important;
  }
  .timeline-search-input::placeholder{
    color: rgba(255, 255, 255, .38) !important;
    font-size: 12.5px !important;
    letter-spacing: .5px !important;
    text-transform: none !important;
  }
  /* The button was `min-height:40px` from the tap-target pass inside a
     30px bar — it overflowed its own container on every page that has a
     breadcrumb. The bar is 42px now, so the target fits inside it rather
     than sticking out of it. */
  .timeline-search-btn{
    height: 100% !important;
    min-height: 0 !important;
    min-width: 44px !important;
    padding: 0 !important;
    color: rgba(255, 255, 255, .42) !important;
  }
  .timeline-search:focus-within .timeline-search-btn{
    color: var(--ng-m-accent) !important;
  }
  /* The hairline between field and button was a purple gradient at 18%,
     invisible against the new fill and pointless at this size. */
  .timeline-search-btn:before{
    background: rgba(255, 255, 255, .07) !important;
    top: 26% !important;
    bottom: 26% !important;
  }
  .timeline-search-dot{
    margin-left: 12px !important;
  }
  /* And the frame around the frame. `.timeline-breadcrumb-wrapper` is a
     346x64 box with its own 1px `rgb(45,44,45)` border and 2px radius,
     holding a 324x46 field with a second 1px border 10px inside it — two
     concentric outlined rectangles at the top of every page, which is
     most of what "sticks out too much" is describing.

     That wrapper had a job when it held the breadcrumb trail. Section 42D
     hid the trail on phones, so what is left is a frame around a single
     control. The field is the control; one box is enough, and it takes
     the full width the wrapper was reserving. */
  .timeline-breadcrumb-wrapper{
    background: transparent !important;
    border: 0 !important;
    border-radius: 0 !important;
    padding: 10px 0 0 !important;
    margin-bottom: 12px !important;
  }


  /* ─── 46G · Size guide, and frequently bought together ──────────────
     Note five, two components in the product sidebar.

     The size guide first. It was the last 10px border-radius on the site —
     `ng-polish.css:5461` rounds every card to 2px and its list does not
     name `.size-guide` — so it was a soft-cornered box in a square-cornered
     page. Beyond that it read as a bare table: the heading was the same
     weight as the data, and the columns were padded 16px each side inside
     a 314px panel, which left three columns fighting over 250px.

     It is a panel with a header band now, like everything else here, and
     the padding is spent on the rows rather than on the gutters. */
  .product-page .size-guide{
    border-radius: 2px !important;
    border: 1px solid var(--ng-line) !important;
    background: #060607 !important;
    overflow: hidden !important;
    margin-bottom: 14px !important;
  }
  .product-page .size-guide h4{
    margin: 0 !important;
    padding: 11px 14px !important;
    background: rgba(255, 255, 255, .022) !important;
    border-bottom: 1px solid var(--ng-line) !important;
    color: rgba(255, 255, 255, .88) !important;
    font-size: 10.5px !important;
    font-weight: 500 !important;
    letter-spacing: 2px !important;
    text-transform: uppercase !important;
  }
  .product-page .size-guide h4 i{
    color: var(--ng-m-accent) !important;
    font-size: 11px !important;
  }
  .product-page .size-chart{
    table-layout: fixed !important;
    width: 100% !important;
  }
  /* Three classes deep, to reach past `ng-polish.css:2621`, which is
     `.product-page .size-guide .size-chart th` with `!important` padding
     and outranks a two-class rule however late it loads. */
  .product-page .size-guide .size-chart th,
  .product-page .size-chart th{
    padding: 9px 6px !important;
    font-size: 9px !important;
    letter-spacing: 1.2px !important;
    color: rgba(255, 255, 255, .55) !important;
    background: transparent !important;
    border-bottom: 1px solid var(--ng-line) !important;
  }
  .product-page .size-chart td{
    padding: 10px 6px !important;
    font-size: 11.5px !important;
    letter-spacing: .2px !important;
    color: rgba(255, 255, 255, .84) !important;
    border-bottom: 1px solid rgba(255, 255, 255, .045) !important;
  }
  /* The first column is the size — the thing being looked up — so it reads
     as a label and the measurements read as data. */
  .product-page .size-chart td:first-child{
    color: #fff !important;
    font-weight: 500 !important;
  }
  .product-page .size-chart tbody tr:nth-child(even) td{
    background: rgba(255, 255, 255, .016) !important;
  }
  .product-page .size-chart tbody tr:last-child td{
    border-bottom: 0 !important;
  }

  /* Frequently bought together, matched to the index's category tiles.
     Those are the most compact thing on the site: no card, no border, no
     fill — a centred image over a 7.5px uppercase caption, five across.
     The FBT block was four bordered cards with a boxed image, which is a
     different language in the same column.

     Two columns, not five: these are products with a price, and round
     three asked for 2x2 specifically. Everything else is the category
     tile — chrome removed, content centred, caption at the category
     tile's size and tracking. */
  .product-page .frequently-bought{
    border-radius: 2px !important;
    border: 1px solid var(--ng-line) !important;
    background: #060607 !important;
    overflow: hidden !important;
  }
  .product-page .frequently-bought h4{
    margin: 0 !important;
    padding: 11px 14px !important;
    background: rgba(255, 255, 255, .022) !important;
    border-bottom: 1px solid var(--ng-line) !important;
    color: rgba(255, 255, 255, .88) !important;
    font-size: 10.5px !important;
    font-weight: 500 !important;
    letter-spacing: 2px !important;
    text-transform: uppercase !important;
  }
  .product-page .compact-recommendations{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 14px 10px !important;
    padding: 14px 12px !important;
    background: transparent !important;
  }
  /* Three deep, to clear `.product-page .frequently-bought [class*="card"]`
     in this same file, which re-borders every card in this block with
     `!important` and outranks a two-class rule however late it comes. */
  .product-page .frequently-bought .compact-rec-card{
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    gap: 7px !important;
    height: auto !important;
    min-height: 0 !important;
    padding: 0 !important;
    background: none !important;
    border: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    overflow: visible !important;
  }
  .product-page .frequently-bought .compact-rec-card .compact-rec-image{
    width: 100% !important;
    height: 84px !important;
    object-fit: cover !important;
    border-radius: 2px !important;
    display: block !important;
  }
  .product-page .frequently-bought .compact-rec-card .compact-rec-info{
    position: static !important;
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    gap: 3px !important;
    padding: 0 !important;
    width: 100% !important;
    background: none !important;
    text-align: center !important;
  }
  .product-page .frequently-bought .compact-rec-card .compact-rec-title{
    font-size: 9px !important;
    font-weight: 500 !important;
    letter-spacing: .5px !important;
    line-height: 1.35 !important;
    text-transform: uppercase !important;
    color: rgba(255, 255, 255, .64) !important;
    text-align: center !important;
  }
  .product-page .frequently-bought .compact-rec-card .compact-rec-price{
    font-size: 11px !important;
    font-weight: 500 !important;
    letter-spacing: .4px !important;
    color: var(--ng-m-accent) !important;
  }


  /* ─── 46H · The account dashboard ───────────────────────────────────
     Note eight: "there doesn't seem to be any mobile styling for the
     dashboard".

     There are in fact 43 rules in `storefront-DashboardRoute.css` behind
     `max-width` queries. The reason the page looks unstyled is that the
     first of them is broken in a way that hides everything else:

         @media (max-width:1024px)  .dashboard-sidebar{ width:100% }
         @media (max-width:968px)   .dashboard-sidebar{ position:fixed; left:-220px }

     Both match at 390px. The second makes the drawer fixed but never
     restates the width, and a fixed element's percentage width resolves
     against the VIEWPORT — so the closed drawer is the full screen wide,
     parked at -220px, and 182px of it sits on top of the page at all
     times. Everything behind that strip is unreachable. That is the whole
     of "no mobile styling", and no amount of tidying the panels below
     would have shown up underneath it.

     The rest of this block is the pass the route never got: the panels
     that have no rule at any width. */

  /* The drawer, sized and parked off-screen by the same number so the two
     can never drift apart again. */
  .dashboard-sidebar{
    width: min(280px, 82vw) !important;
    max-width: min(280px, 82vw) !important;
    min-width: 0 !important;
    left: calc(-1 * min(280px, 82vw)) !important;
    top: var(--ng-m-header) !important;
    height: calc(100vh - var(--ng-m-header)) !important;
    max-height: calc(100vh - var(--ng-m-header)) !important;
    margin: 0 !important;
    border-left: 0 !important;
    border-right: 1px solid var(--ng-line) !important;
    background: var(--surface) !important;
    z-index: 660 !important;
    transition: left .35s var(--ease-luxe, ease) !important;
  }
  .dashboard-sidebar.open{
    left: 0 !important;
  }
  .dashboard-sidebar .sidebar-header{
    padding: 14px 16px !important;
  }
  .dashboard-sidebar .user-avatar{
    width: 40px !important;
    height: 40px !important;
  }
  .dashboard-sidebar .nav-group-title{
    padding: 12px 16px 6px !important;
  }
  .dashboard-sidebar .nav-item a{
    padding: 0 16px !important;
  }

  /* The welcome panel was 836px tall — a quarter of the page before the
     first order — on 28px of padding with the four stat cards stacked one
     per row underneath it. */
  .welcome-section{
    padding: 16px 14px !important;
    margin-bottom: 14px !important;
    border-radius: 2px !important;
  }
  .welcome-actions{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 8px !important;
    width: 100% !important;
  }
  .welcome-actions .action-btn{
    width: auto !important;
    min-width: 0 !important;
    padding: 0 10px !important;
    min-height: 38px !important;
    height: 38px !important;
    font-size: 10px !important;
    letter-spacing: .6px !important;
  }
  /* Two by two. The route chunk drops to one column at 480px, which is
     every phone — four cards, 144px each, 576px of the same card. */
  .stats-grid,
  .welcome-section .stats-grid{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 0 !important;
    padding-top: 14px !important;
  }
  .stat-card{
    padding: 12px 8px !important;
    border-right: 1px solid rgba(138, 43, 226, .07) !important;
    border-bottom: 1px solid rgba(138, 43, 226, .07) !important;
  }
  /* In a two-column grid the last card is not the one on the right, and
     the bottom row has nothing under it. */
  .stats-grid > .stat-card:nth-child(2n){
    border-right: 0 !important;
  }
  .stats-grid > .stat-card:nth-last-child(-n+2){
    border-bottom: 0 !important;
  }
  .stat-value{
    font-size: 17px !important;
    line-height: 1.2 !important;
  }
  .stat-icon{
    font-size: 13px !important;
    margin-bottom: 4px !important;
  }

  /* The dashboard's section bands. `ng-polish.css:4632` zeroes every
     `.category-header-wrapper` margin with `!important`, which beats the
     route chunk's −16px and left these bands floating inside the
     container's padding while the same band on the index runs edge to
     edge. Same −12px as everywhere else in this round. */
  .dashboard-layout .category-header-wrapper,
  .dashboard-content .category-header-wrapper{
    margin-left: -12px !important;
    margin-right: -12px !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  .dashboard-content .category-header{
    padding: 12px 14px !important;
    margin-bottom: 14px !important;
  }

  /* Order cards. 208px each, four of them, on a centred column layout that
     put the thumbnails in the middle of the card and the two buttons under
     them. Read left to right instead: the number and status on one line,
     the thumbnails and the total on the next, the actions last. */
  .orders-list{
    gap: 8px !important;
  }
  .order-card{
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) auto !important;
    grid-template-areas:
      "info  total"
      "items items"
      "acts  acts" !important;
    align-items: center !important;
    gap: 8px 10px !important;
    padding: 12px !important;
    text-align: left !important;
    border-radius: 2px !important;
  }
  .order-card .order-info{
    grid-area: info !important;
    align-items: flex-start !important;
    min-width: 0 !important;
    text-align: left !important;
  }
  .order-card .order-total{
    grid-area: total !important;
    text-align: right !important;
    white-space: nowrap !important;
  }
  .order-card .order-items{
    grid-area: items !important;
    display: flex !important;
    justify-content: flex-start !important;
    gap: 6px !important;
  }
  .order-card .order-actions{
    grid-area: acts !important;
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 8px !important;
    min-width: 0 !important;
  }
  /* 26px tall, and not in any of the tap-target lists. */
  .order-action-btn{
    min-height: 38px !important;
    height: 38px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    padding: 0 8px !important;
    font-size: 10px !important;
    letter-spacing: .5px !important;
    border-radius: 2px !important;
  }
  .order-thumb{
    width: 38px !important;
    height: 46px !important;
    border-radius: 2px !important;
  }
  .view-more-btn{
    min-height: 40px !important;
  }

  /* Two controls doing one job. The recommendations block on this page
     ends with the rail's own footer — "VIEW MORE" — and then the static
     tree's "See More" immediately under it, both going to the same place.
     The rail's footer is the one that belongs to the rail, so the outer
     one goes, and only in the section that actually has a rail in it: the
     identical button under Recent Orders is the only way to see the rest
     of the orders and has to stay. */
  .dashboard-content .content-section:has(.recommendations-full-width) .view-more-btn{
    display: none !important;
  }
  /* The rail footer is a two-column grid built for VIEW MORE beside
     REFRESH. The dashboard renders only the first, so it sat at half width
     in a row with no height — 169 x 13. Alone, it takes the row. */
  .rec-footer > :only-child{
    grid-column: 1 / -1 !important;
  }
  .rec-footer .more-like-it-btn,
  .rec-footer .rec-refresh-btn{
    min-height: 40px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
  }

  /* Recently viewed. The track keeps the desktop carousel's 50px of side
     padding — room for arrow buttons that are not on a phone — with
     `overflow-x:hidden`, so the first card started 50px in and the rest
     were clipped rather than scrollable. It is a swipe rail here, like
     every other rail on the phone. */
  .recently-viewed-track{
    padding-left: 0 !important;
    /* 12px, not the 16px gutter: this rail lives inside `.container`,
       whose padding is 12px, so that is exactly how far it has to bleed to
       reach the inner face of the border and no further. */
    padding-right: 12px !important;
    margin-right: -12px !important;
    overflow-x: auto !important;
    scrollbar-width: none !important;
    scroll-snap-type: x proximity !important;
  }
  .recently-viewed-track::-webkit-scrollbar{ display: none !important; }
  .recently-viewed-item{
    flex: 0 0 var(--ng-m-card-rail) !important;
    width: var(--ng-m-card-rail) !important;
    height: 200px !important;
    opacity: 1 !important;
    border-radius: 2px !important;
  }
  /* The arrows and the edge fades belong to a carousel that is now a
     swipe rail; both were sitting over the cards. */
  .recently-viewed-wrapper .carousel-btn,
  .recently-viewed-fade-left,
  .recently-viewed-fade-right{
    display: none !important;
  }

  /* The settings panels — profile, addresses, payments, notifications,
     support. None of these has a rule at any width in the route chunk. */
  .dashboard-content .form-grid{
    grid-template-columns: minmax(0, 1fr) !important;
    gap: 12px !important;
  }
  .dashboard-content .form-section{
    padding: 14px 12px !important;
    border-radius: 2px !important;
  }
  .dashboard-content .ng-dash-form{
    gap: 12px !important;
  }
  .dashboard-content .ng-check-row,
  .dashboard-content .ng-toggle-row{
    display: flex !important;
    align-items: center !important;
    justify-content: space-between !important;
    gap: 12px !important;
    min-height: 44px !important;
    padding: 6px 0 !important;
  }
  .dashboard-content .ng-toggle-row > span:first-child,
  .dashboard-content .ng-check-row > span:first-child{
    min-width: 0 !important;
  }
  .dashboard-content .ng-thread{
    padding: 12px !important;
    border-radius: 2px !important;
  }
  .dashboard-content .ng-msg-body{
    font-size: 11px !important;
    line-height: 1.5 !important;
  }
  .dashboard-content input,
  .dashboard-content select,
  .dashboard-content textarea{
    width: 100% !important;
    min-height: 42px !important;
    border-radius: 2px !important;
    box-sizing: border-box !important;
  }
  .dashboard-content textarea{
    min-height: 90px !important;
  }
  /* The route's own toggle button. `ng-polish.css:4405` parks every mobile
     drawer button at `top:2px; left:20px`, which on this page puts it on
     top of the header's own left-hand controls. The dashboard header has
     no hamburger of its own, so it goes where the other drawers' buttons
     go — but the z-index has to clear the drawer, or an open drawer covers
     the control that closes it. */
  .sidebar-toggle{
    z-index: 1002 !important;
  }


  /* ─── 46I · The human check is reachable ────────────────────────────
     Found while signing in to reach the dashboard at all, and worth fixing
     on the way past: the NightGuard challenge input on `/account` sits
     under the fixed tab bar. A person on a phone cannot tap it, which
     makes signing in impossible on the device this whole round is about.

     The panel scrolls into view above the bar instead. */
  .ng-key-challenge{
    position: relative !important;
    z-index: 2 !important;
    scroll-margin-bottom: calc(var(--ng-m-tabbar) + 24px) !important;
  }
  .ng-key-input{
    min-height: 44px !important;
    position: relative !important;
    z-index: 2 !important;
  }
  .auth-shell{
    padding-bottom: calc(var(--ng-m-tabbar) + 16px) !important;
  }
  /* The tick box itself lands at y=786 on a 844px screen — inside the
     68px the tab bar occupies. The page scrolls, so it is reachable, but
     only if you know to scroll; the extra room below the form means the
     last control clears the bar once the page bottoms out. */
  .ng-captcha{
    scroll-margin-bottom: calc(var(--ng-m-tabbar) + 24px) !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   47 · TWO MORE SHEETS, AND THE EDIT'S FRONT PAGE
   ═══════════════════════════════════════════════════════════════════════
   `ng-dash-sprite.png` (1667×128, twelve tiles) for the signed-in
   dashboard's left nav, and `ng-blog-filter-sprite.png` (951×128, seven)
   for The Edit's category row — which also stops being a pill bar and
   becomes the index's category strip, sitting under the hero the way the
   index's sits under the slideshow.

   Both sheets follow the convention the other three already use: one row of
   finished colour artwork, each tile naming its own `--cx`/`--cw`, the whole
   thing scaled by `--sp-k`. The membership classes (`has-spr spr-<key>`)
   are written in JS — `DASH_SPR` in dashboard-live.jsx, `jnFilterSpr` in
   journal-shared.jsx — for the same reason The Edit's sidebar does it that
   way: a row whose key has no artwork must get NO tile rather than a tile
   with `--cw` unset, because that computes an invalid width, falls back to
   `background-position: 0 0`, and renders the first tile in the sheet.

   The `?v=` on every sprite URL is not decoration. `.htaccess` caches images
   for a month while css and js revalidate, so a sheet that changes without
   its query string changing is a month of the old artwork against the new
   rules. */

/* ─── 47A · The dashboard's left nav ────────────────────────────────────
   Ten links and two of the three group headings. The sheet's own captions
   say which: "ACCOUNT SETTINGS" and "SUPPORT & REWARDS" are the verbatim
   text of `.nav-group-title` rows, not links. "Account Overview", the first
   heading, has no artwork and keeps its bare text.

   This rail is not `.category-item`, so it does not inherit the shop and
   blog sidebars' machinery — the `::before` is written out here. What it
   does borrow is the one part that matters for a list: the tile is centred
   in a fixed slot with symmetric margins, rather than being laid out at its
   own width. Tiles run 104px to 138px on the sheet, so without that the
   labels would start at eleven different x positions down one column. */
.dashboard-nav-list{
  --sp-w: 1667px;
  --sp-h: 128px;
  --sp-k: .19;                    /* → 24px tall, 20–26px wide */
}
.dashboard-nav-list .nav-item a.has-spr::before,
.dashboard-nav-list .nav-group-title.has-spr::before{
  content: "";
  display: block;
  flex: 0 0 auto;
  width: calc(var(--cw) * var(--sp-k));
  height: calc(var(--sp-h) * var(--sp-k));
  background-image: url(/assets/ng-dash-sprite.png?v=2250);
  background-repeat: no-repeat;
  background-size: calc(var(--sp-w) * var(--sp-k)) calc(var(--sp-h) * var(--sp-k));
  background-position: calc(var(--cx) * var(--sp-k) * -1) 0;
  transition: filter .35s ease;
}
/* The slot. 28px wide, then a 10px gutter before the label — the same
   total the 16px glyph plus its 10px margin used to occupy, so nothing
   below the icon moves. */
.dashboard-nav-list .nav-item a.has-spr::before{
  margin-left: calc((28px - var(--cw) * var(--sp-k)) / 2);
  margin-right: calc((28px - var(--cw) * var(--sp-k)) / 2 + 10px);
}
.dashboard-nav-list .nav-item a.has-spr:hover::before{
  filter: brightness(1.22) drop-shadow(0 0 8px rgba(138, 43, 226, .6));
}
.dashboard-nav-list .nav-item a.has-spr.active::before{
  filter: brightness(1.3) drop-shadow(0 0 10px rgba(138, 43, 226, .75));
}

/* A heading is not a link: its tile is smaller and quieter, so the eye
   still reads the group as a level above the rows under it. */
.dashboard-nav-list .nav-group-title.has-spr{
  display: flex;
  align-items: center;
}
.dashboard-nav-list .nav-group-title.has-spr::before{
  --sp-k: .135;                   /* → 17px tall */
  margin-left: calc((22px - var(--cw) * var(--sp-k)) / 2);
  margin-right: calc((22px - var(--cw) * var(--sp-k)) / 2 + 8px);
  opacity: .72;
}
/* The first heading has no tile, and its label still has to start where the
   other two do. An empty `::before` holding the same 22px slot does that;
   padding does not, for two reasons. Section 46H sets
   `.dashboard-sidebar .nav-group-title{padding:12px 16px 6px!important}`,
   and `!important` beats a more specific plain rule — so a `padding-left`
   here is simply discarded at phone width. And the row's own padding is
   16px on a phone against 18px on desktop, so a hard-coded indent would be
   right at one width and wrong at the other. The slot is measured from
   wherever the padding happens to put it. */
.dashboard-nav-list .nav-group-title.no-spr{
  display: flex;
  align-items: center;
}
.dashboard-nav-list .nav-group-title.no-spr::before{
  content: "";
  display: block;
  flex: 0 0 22px;
  margin-right: 8px;
}

/* ── While we were in here: the desktop dashboard scrolled sideways ─────
   Not caused by this release — it reproduces against the pre-2.24 sheet —
   but it is 133px of horizontal scrollbar on the signed-in dashboard at
   1440px, and the component is the one round four spent a section on.

   `ng-polish.css:1770` gives `.recommendations-full-width` a width of
   `min(1200px, 85vw)` with auto margins, which is right on /shop and
   /product where the rail is a child of the page. On the dashboard it is
   three levels inside `.container`, in a column that is 914px wide at
   1440px — so a 1200px rail in a 914px column, 286px of it hanging out,
   and the page grows to fit.

   Section 46E fixed the same thing for phones; the rule was scoped to
   `max-width: 768px` because that was the round's brief. This is its
   desktop half. */
.dashboard-content .recommendations-full-width,
.dashboard-layout .recommendations-full-width{
  width: 100% !important;
  max-width: 100% !important;
  margin-left: 0 !important;
  margin-right: 0 !important;
}

/* the sheet, left to right — ten links then the two headings, in the order
   they sit on ng-dash-sprite.png */
.dashboard-nav-list .spr-dashboard    { --cx:    4px; --cw: 138px }
.dashboard-nav-list .spr-orders       { --cx:  151px; --cw: 138px }
.dashboard-nav-list .spr-wishlist     { --cx:  298px; --cw: 138px }
.dashboard-nav-list .spr-grp-settings { --cx:  445px; --cw: 129px }
.dashboard-nav-list .spr-profile      { --cx:  583px; --cw: 126px }
.dashboard-nav-list .spr-addresses    { --cx:  718px; --cw: 123px }
.dashboard-nav-list .spr-payments     { --cx:  850px; --cw: 138px }
.dashboard-nav-list .spr-grp-support  { --cx:  997px; --cw: 104px }
.dashboard-nav-list .spr-notifications{ --cx: 1110px; --cw: 138px }
.dashboard-nav-list .spr-loyalty      { --cx: 1257px; --cw: 138px }
.dashboard-nav-list .spr-support      { --cx: 1404px; --cw: 126px }
.dashboard-nav-list .spr-signout      { --cx: 1539px; --cw: 124px }

/* A row with a tile hides its Font Awesome glyph the way every other
   sprite row on the site does — `category-icon` is `display:none`
   platform-wide, and dashboard-live.jsx tags the glyph with it. This is the
   belt to that braces: if the JS has not run yet, the glyph is all there is
   and it should still be the right size. */
.dashboard-nav-list .nav-item a.no-spr i{
  width: 28px;
  margin-right: 10px;
  text-align: center;
}


/* ─── 47B · The Edit's category row becomes the index's strip ───────────
   "Utilise the blog icon sprite the same way the categories are done on the
   homepage beneath the hero."

   So this is not a restyled pill bar, it is the same component: a sprite
   tile over a small uppercase caption, on the same grid, at the same size.
   `.mcp-pill` keeps its class name because the route stylesheet and the
   click handler both use it, but everything that made it a capsule is off.

   Mobile only, and that costs nothing: `storefront-BlogRoute.css` already
   sets `.mobile-cat-pills{display:none}` at the top level and only shows it
   under 768px. The desktop blog keeps its full sidebar. */
@media (max-width: 768px){

  .mobile-cat-pills{
    --sp-w: 951px;
    --sp-h: 128px;
    --sp-k: .30;                  /* the index strip's k, so the two match */
    display: flex !important;
    flex-wrap: wrap !important;
    /* The index is exactly ten tiles in a five-column grid, so its last row
       is always full. This row is data-driven — `filter(count > 0)
       .slice(0, 6)` plus All — so it can be four, six or seven. Centred
       wrap reads as deliberate at any count; a grid would leave the last
       row hanging left with a hole beside it. At a full five it is
       indistinguishable from the index's grid. */
    justify-content: center !important;
    align-items: flex-start !important;
    gap: 16px 2px !important;
    overflow: visible !important;
    padding: 6px 0 2px !important;
    margin: 0 0 4px !important;
    background: none !important;
    border: 0 !important;
    scroll-snap-type: none !important;
  }

  .mobile-cat-pills .mcp-pill{
    /* five across, matching the index's `repeat(5, minmax(0,1fr))` with its
       2px column gap */
    flex: 0 0 calc((100% - 8px) / 5) !important;
    max-width: calc((100% - 8px) / 5) !important;
    min-width: 0 !important;
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: 6px !important;
    height: auto !important;
    padding: 0 !important;
    background: none !important;
    border: 0 !important;
    border-radius: 0 !important;
    color: inherit !important;
    text-transform: none !important;
    white-space: normal !important;
    transition: transform .35s var(--ease-luxe, ease) !important;
  }

  .mobile-cat-pills .mcp-pill.has-spr::before{
    content: "";
    display: block;
    margin: 0 auto;
    width: calc(var(--cw) * var(--sp-k));
    height: calc(var(--sp-h) * var(--sp-k));
    background-image: url(/assets/ng-blog-filter-sprite.png?v=2250);
    background-repeat: no-repeat;
    background-size: calc(var(--sp-w) * var(--sp-k)) calc(var(--sp-h) * var(--sp-k));
    background-position: calc(var(--cx) * var(--sp-k) * -1) 0;
    transition: filter .45s var(--ease-luxe, ease);
  }
  /* A category with no artwork keeps its glyph, drawn at the tile's height
     so the row stays level. */
  .mobile-cat-pills .mcp-pill.no-spr i.sidebar-fallback-icon{
    display: flex !important;
    align-items: center;
    justify-content: center;
    /* the tile's height, so a fallback row sits exactly as tall as a
       sprite row — and `width:100%` so the glyph centres in the column
       rather than collapsing to zero and pulling the label left */
    width: 100% !important;
    height: calc(var(--sp-h) * var(--sp-k));
    font-size: 19px !important;
    color: rgba(186, 85, 211, .8) !important;
  }
  .mobile-cat-pills .mcp-pill i{
    margin: 0 !important;
  }

  .mobile-cat-pills .category-name{
    font-size: 7.5px !important;
    font-weight: 500 !important;
    letter-spacing: .5px !important;
    line-height: 1.25 !important;
    text-align: center !important;
    text-transform: uppercase !important;
    color: var(--ng-m-t3) !important;
  }
  .mobile-cat-pills .mcp-pill.active::before{
    filter: brightness(1.28) drop-shadow(0 0 9px rgba(138, 43, 226, .7));
  }
  .mobile-cat-pills .mcp-pill.active .category-name{
    color: #fff !important;
  }

  /* the sheet, left to right */
  .mcp-pill.spr-all           { --cx:   4px; --cw: 130px }
  .mcp-pill.spr-beauty        { --cx: 143px; --cw: 113px }
  .mcp-pill.spr-trends        { --cx: 265px; --cw: 118px }
  .mcp-pill.spr-sustainability{ --cx: 392px; --cw: 132px }
  .mcp-pill.spr-care-tips     { --cx: 533px; --cw: 122px }
  .mcp-pill.spr-style-guide   { --cx: 664px; --cw: 138px }
  .mcp-pill.spr-color-theory  { --cx: 811px; --cw: 136px }


  /* ─── 47C · The Edit reads like the index ─────────────────────────────
     "The blog should be basically identical in layout to the index page on
     mobile only."

     The index is: hero, then the category strip, then a section heading and
     its grid, repeating. The Edit had the category row ABOVE its hero —
     which is the one structural difference that makes the two pages scan
     differently, because the first thing you meet is a row of filters
     rather than the thing being filtered.

     `order` needs a flex parent, and `.main-content` is `display:block`
     here. Making it a column is safe: every child is already a full-width
     block, and section 44C has been treating `.main-content` as the growing
     member of a column since round three.

     `:has(> .mobile-cat-pills)` rather than a page class, because there is
     no `.blog-route` — `.main-layout` is shared with /shop and /product. */
  .main-content:has(> .mobile-cat-pills){
    display: flex !important;
    flex-direction: column !important;
  }
  /* Everything keeps document order behind these three. Listed first so the
     three below override it — they are one class more specific, so source
     order is not what decides, but reading it in this order is. */
  .main-content:has(> .mobile-cat-pills) > *{ order: 3; }
  .main-content:has(> .mobile-cat-pills) > .timeline-breadcrumb-wrapper{ order: 0; }
  .main-content:has(> .mobile-cat-pills) > .featured-hero{ order: 1; }
  .main-content:has(> .mobile-cat-pills) > .mobile-cat-pills{ order: 2; }

  /* The hero runs to the container's inner edges, like the index's
     slideshow — it is the page's opening image, not a card inside a list. */
  .main-content > .featured-hero{
    margin-left: -12px !important;
    margin-right: -12px !important;
    border-radius: 0 !important;
    border-left: 0 !important;
    border-right: 0 !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   48 · THE NEON FRAME TRAVELS, AND THE DASHBOARD JOINS THE SET
   ═══════════════════════════════════════════════════════════════════════
   Six notes. Two of them (48A, 48C) are the same request in different
   words: a component that exists in one good version on the index or the
   shop, and two or three lesser versions elsewhere. Both are fixed by
   pointing the other copies at the original rather than by re-describing
   it — the chip tokens in section 15 now name `.dashboard-sidebar`, and
   the frame below is the index's own artwork and its own numbers. */

/* ─── 48A · The index's frame, on the blog and lookbook heroes ──────────
   The index hero's purple neon frame is not CSS. It is a nine-slice
   border-image — `/frame_overlay.png`, slices `83 95 83 93` —
   painted by `.slideshow-container::before` at `inset: 0`, over a black
   backing plate drawn by `::after`. Everything it needs is on the
   container: four custom properties and two pseudo-elements. No keyframes,
   no box-shadow, no gradient. That is what makes it liftable.

   `--fk` is the only number that matters: every frame dimension is
   `83/95/93 × --fk`, and the index steps it down the breakpoints along
   with the hero's height. Both heroes below take the same ladder and the
   same heights, because "fit on the page the same as the one on the index"
   is a statement about those two numbers.

   The 6px pullback (`--frame-t - 6px`) is the index's, not an invention:
   the artwork's inner lip sits 6px inside the slice boundary, so content
   aligned to the raw frame width leaves a hairline of backing plate
   showing. `.slide` uses the same expression. */

.featured-hero,
.lb-hero{
  --fk: .26;
  --frame-t: calc(83px * var(--fk));
  --frame-r: calc(95px * var(--fk));
  --frame-l: calc(93px * var(--fk));
  /* the rectangle inside the tube — used by both pseudo-elements and by
     every child that has to sit within the glass */
  --ng-tube: calc(var(--frame-t) - 6px) calc(var(--frame-r) - 6px)
             calc(var(--frame-t) - 6px) calc(var(--frame-l) - 6px);

  position: relative !important;
  /* `visible`, like the index: the frame is `inset: 0` so it does not need
     clipping, and `hidden` would cut the bloom at the box edge. */
  overflow: visible !important;
  isolation: isolate;
  height: 280px !important;
  min-height: 0 !important;
  max-height: none !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  background-color: transparent !important;
}
/* the black plate the imagery sits on */
.featured-hero::after,
.lb-hero::after{
  content: "";
  position: absolute;
  inset: var(--ng-tube);
  background: #060608;
  border-radius: 6px;
  z-index: 0;
}
/* the frame itself */
.featured-hero::before,
.lb-hero::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  border-style: solid;
  border-width: var(--frame-t) var(--frame-r) var(--frame-t) var(--frame-l);
  border-image-source: url(/frame_overlay.png);
  border-image-slice: 83 95 83 93;
  border-image-repeat: stretch;
}
/* the index's own ladder, value for value */
@media (max-width: 968px){
  .featured-hero, .lb-hero{ --fk: .22; height: 250px !important; }
}
@media (max-width: 768px){
  .featured-hero, .lb-hero{ height: 230px !important; }
}
@media (max-width: 640px){
  .featured-hero, .lb-hero{ --fk: .19; height: 210px !important; }
}

/* THE BLOG HERO's children are all absolutely positioned. Padding cannot
   move them — an absolutely positioned box resolves its offsets against
   the containing block's PADDING box, so `inset: 0` lands on the outside
   of any padding added here. Each one is therefore pulled in explicitly,
   which is also how the index does it (`.slide` carries the same
   expression rather than inheriting one). */
/* The image is a REPLACED element, and that changes the rule. On a div,
   four insets plus `width:auto` stretch the box to the containing block.
   On an `<img>` they do not: with `auto` in both axes a replaced element
   takes its INTRINSIC size and the insets only position it. Written as
   `inset: <tube>; width:auto; height:auto` this came out 300×300 — the
   placeholder's natural size — hanging 100px below a 210px hero and
   painting a grey slab behind the category strip underneath.
   So the size is stated, derived from the same frame variables. */
.featured-hero > .hero-image{
  top: calc(var(--frame-t) - 6px) !important;
  left: calc(var(--frame-l) - 6px) !important;
  right: auto !important;
  bottom: auto !important;
  width: calc(100% - var(--frame-l) - var(--frame-r) + 12px) !important;
  height: calc(100% - var(--frame-t) - var(--frame-t) + 12px) !important;
  object-fit: cover !important;
  border-radius: 6px;
  z-index: 1;
}
.featured-hero > .hero-overlay{
  left: calc(var(--frame-l) - 6px) !important;
  right: calc(var(--frame-r) - 6px) !important;
  bottom: calc(var(--frame-t) - 6px) !important;
  border-radius: 0 0 6px 6px;
  z-index: 2;
}
.featured-hero > .hero-top-content{
  top: calc(var(--frame-t) + 2px) !important;
  left: calc(var(--frame-l) + 2px) !important;
  z-index: 3;
}
.featured-hero > .hero-content{
  left: calc(var(--frame-l) - 6px) !important;
  right: calc(var(--frame-r) - 6px) !important;
  bottom: calc(var(--frame-t) - 6px) !important;
  z-index: 3;
}
/* It shared its bottom edge with whatever followed. A framed panel needs
   air around it, the way the index's does. */
.featured-hero{
  margin-bottom: 18px !important;
}

/* THE LOOKBOOK HERO is the easier of the two: its content is in normal
   flow inside a centred flex box, so padding does contain it. The
   background image is on the element itself, so it is clipped to the
   content box — otherwise `cover` paints it out to the border edge and it
   appears to run underneath the frame. */
.lb-hero{
  padding: var(--frame-t) var(--frame-r) var(--frame-t) var(--frame-l) !important;
  background-origin: content-box !important;
  background-clip: content-box !important;
  margin-bottom: 18px !important;
}
.lb-hero > .lb-hero-overlay{
  inset: var(--ng-tube) !important;
  border-radius: 6px;
  z-index: 1;
}
.lb-hero > .lb-hero-content{
  position: relative;
  z-index: 3;
  /* the panel's own padding is now the frame's, so the content block's
     48px would push the copy out of a 280px hero */
  padding: 10px 18px !important;
  max-width: 100% !important;
}


@media (max-width: 768px){

  /* Section 47C gave the blog hero a −12px bleed so it met the container's
     borders. A framed hero is not a bleed-to-the-edge element — the index's
     sits inside its container's padding, and the frame is the edge. */
  .main-content > .featured-hero{
    margin-left: 0 !important;
    margin-right: 0 !important;
  }


  /* ─── 48B · Blog sections, two to a row ───────────────────────────────
     The route sheet turns `.blog-grid-compact` into a single column at
     768px and rewrites every card after the first into a horizontal
     thumbnail row. Neither happens: section 42's `.main-content
     .blog-card-compact` is two classes deep at top level, so it beats the
     route sheet's one-class media rules and the cards stay vertical in a
     one-wide stack — which is the worst of both, a full-width card with a
     120px letterbox image.

     Two to a row, on the index's grid, at the index's gap. The card is a
     `.compact-product-card` in everything but name now: one fixed height,
     an image band, and only the text that survives a 168px column. */
  .blog-grid-compact{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
    padding: 0 !important;
    margin-bottom: 14px !important;
  }
  /* `:first-child` is named explicitly. The route sheet gives it a `44vw`
     hero image and its own padding, and those selectors are two classes
     deep — the same weight as the rule above them — so without naming it
     the first card would be a different size from its neighbour. */
  .main-content .blog-card-compact,
  .main-content .blog-card-compact:first-child{
    display: flex !important;
    flex-direction: column !important;
    /* the compact product card's 200px, plus the 8px the category chip
       needs above the headline */
    height: 208px !important;
    min-height: 0 !important;
    max-height: none !important;
    margin: 0 !important;
    box-shadow: none !important;
    transform: none !important;
  }
  .main-content .blog-image-compact,
  .main-content .blog-card-compact:first-child .blog-image-compact{
    flex: 0 0 104px !important;
    width: 100% !important;
    height: 104px !important;
    min-height: 0 !important;
    max-height: none !important;
    border-radius: 0 !important;
    object-fit: cover !important;
  }
  .main-content .blog-content-compact,
  .main-content .blog-card-compact:first-child .blog-content-compact{
    flex: 1 1 auto !important;
    min-height: 0 !important;
    padding: 9px 10px 10px !important;
    overflow: hidden !important;
  }
  /* What does not fit in a 168px column. The excerpt, the three stat
     counters and the "recent response" block are all still on the card on
     a wide screen; here they would each take a line and leave no room for
     the headline, which is the only thing that makes the card clickable. */
  .main-content .blog-card-compact .blog-excerpt,
  .main-content .blog-card-compact .blog-stats-compact,
  .main-content .blog-card-compact .recent-response,
  .main-content .blog-card-compact:first-child .recent-response,
  .main-content .blog-card-compact .blog-author{
    display: none !important;
  }
  .main-content .blog-card-compact .blog-title-compact,
  .main-content .blog-card-compact:first-child .blog-title-compact{
    font-size: 11.5px !important;
    line-height: 1.32 !important;
    letter-spacing: .2px !important;
    margin: 0 0 5px !important;
    text-align: left !important;
    display: -webkit-box !important;
    -webkit-line-clamp: 3 !important;
    -webkit-box-orient: vertical !important;
    overflow: hidden !important;
  }
  .main-content .blog-card-compact .blog-category-compact{
    margin-bottom: 5px !important;
  }
  .main-content .blog-card-compact .blog-author-date{
    margin-top: auto !important;
  }
  .main-content .blog-card-compact .blog-date{
    font-size: 9px !important;
    letter-spacing: .3px !important;
  }
}


/* ─── 48C · The dashboard's rail is the shop's rail ─────────────────────
   Section 47A gave these rows sprite tiles and stopped there, with a
   comment saying the rail "is not `.category-item`, so it does not inherit
   the shop and blog sidebars' machinery". This is that machinery, for the
   one element that could not borrow the selectors: a chip behind the
   glyph, a bordered row with a radius, the hover nudge, the active inset
   rail, and a count pill instead of a square badge.

   The chip artwork is NOT redeclared here — `.dashboard-sidebar` was added
   to section 15's token rule, so all four rails draw the same three SVGs
   from one place. Everything below is layout and state. The source of
   truth for the values is section 15, lines ~881-947; where a number here
   looks arbitrary it was copied from there. */

.dashboard-nav-list{
  padding: 10px 9px 14px !important;
}
.dashboard-nav-list .nav-item + .nav-item{
  margin-top: 1px;
}
/* The row, mirrored from section 21 — which is where these numbers live
   NOW. Section 15 built the row at 48px with a 13px gap; section 21 later
   rebuilt it as a three-column grid at 42px and 11px, and that is what the
   shop actually renders. Copying 15 would have produced a rail that looked
   like the shop's of two passes ago. Measured first, then written.

   A grid, not a flex row, for the same reason the shop is one: the 38px
   chip column, the label and the count each want their own track, and the
   count has to sit hard right without `margin-left:auto` fighting the
   label's ellipsis. A bare text node between two elements becomes an
   anonymous grid item, so the dashboard's markup — glyph, text, badge —
   lands in the three tracks unchanged. */
.dashboard-nav-list .nav-item a{
  display: grid !important;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  align-items: center !important;
  gap: 11px;
  min-height: 42px !important;
  padding: 4px 11px 4px 6px !important;
  position: relative;
  border: 1px solid transparent !important;
  border-left-width: 1px !important;
  border-radius: 3px !important;
  background: var(--ng-chip) 7px 50% / 38px 38px no-repeat,
              linear-gradient(90deg, rgba(255, 255, 255, .018), rgba(255, 255, 255, 0));
  /* the label is a bare text node here, not a `.category-name` span, so the
     row has to carry its type */
  font-size: 10.5px !important;
  font-weight: 500 !important;
  letter-spacing: 1.7px !important;
  line-height: 1.25;
  color: rgba(255, 255, 255, .92) !important;
  text-decoration: none;
  transition: background .35s ease, border-color .35s ease,
              box-shadow .35s ease, transform .35s var(--ease-luxe, ease) !important;
}
/* The hairline between rows, stopping at the chip so the glyph column
   reads as one continuous rail. */
.dashboard-nav-list .nav-item + .nav-item a::after{
  content: "";
  position: absolute;
  top: -1px;
  left: 44px;
  right: 8px;
  height: 1px;
  background: var(--ng-line);
  opacity: .8;
  pointer-events: none;
}
.dashboard-nav-list .nav-item a:hover::after,
.dashboard-nav-list .nav-item a.active::after{ opacity: 0; }

/* The tile is grid item one and the 38px track centres it, so section 47A's
   margin arithmetic is no longer what positions it. */
.dashboard-nav-list .nav-item a.has-spr::before{
  justify-self: center;
  margin-left: 0 !important;
  margin-right: 0 !important;
}
.dashboard-nav-list .nav-item a.no-spr i{
  justify-self: center;
  width: auto !important;
  margin: 0 !important;
  font-size: 13px !important;
  color: rgba(186, 85, 211, .62) !important;
}
.dashboard-nav-list .nav-item a:hover{
  transform: translateX(2px);
  border-color: rgba(138, 43, 226, .3) !important;
  background: var(--ng-chip-hover) 7px 50% / 38px 38px no-repeat,
              linear-gradient(90deg, rgba(138, 43, 226, .15), rgba(138, 43, 226, .02));
  color: #fff !important;
}
.dashboard-nav-list .nav-item a.active{
  border-color: rgba(186, 85, 211, .45) !important;
  background: var(--ng-chip-active) 7px 50% / 38px 38px no-repeat,
              linear-gradient(90deg, rgba(138, 43, 226, .28), rgba(138, 43, 226, .04));
  box-shadow: inset 3px 0 0 #ba55d3 !important;
  color: #fff !important;
}
/* The count. `.nav-badge` was a square 8px chip with no border and no
   states; `.category-count` is a bordered pill that brightens with its
   row. Same component, so: same pill, same numbers. */
.dashboard-nav-list .nav-badge{
  justify-self: end;
  margin-left: 0 !important;
  min-width: 22px;
  padding: 2px 6px !important;
  border: 1px solid rgba(138, 43, 226, .22) !important;
  border-radius: 999px !important;
  background: rgba(138, 43, 226, .07) !important;
  color: rgba(255, 255, 255, .5) !important;
  font-size: 9px !important;
  font-weight: 500 !important;
  letter-spacing: .6px !important;
  font-variant-numeric: tabular-nums;
  text-align: center;
  transition: border-color .35s ease, background .35s ease, color .35s ease;
}
.dashboard-nav-list .nav-item a:hover .nav-badge{
  border-color: rgba(186, 85, 211, .5) !important;
  background: rgba(138, 43, 226, .16) !important;
  color: rgba(255, 255, 255, .85) !important;
}
.dashboard-nav-list .nav-item a.active .nav-badge{
  border-color: rgba(186, 85, 211, .75) !important;
  background: rgba(138, 43, 226, .28) !important;
  color: #fff !important;
}

/* The rail's own tail gradient, so the empty space under the last row
   reads as intended rather than unfinished — section 21 again. */
.dashboard-sidebar{
  background:
    linear-gradient(180deg, rgba(138, 43, 226, .05) 0%,
                            rgba(138, 43, 226, .012) 34%, transparent 62%),
    #060606f7 !important;
}

/* The header. The shop and The Edit share a branded band — a gradient
   panel, a hairline that travels across it, a shimmering title. The
   dashboard's is a profile card on flat `var(--surface)`. The card is the
   dashboard's own content and stays; the band it sits in is the part that
   should match, so the panel, the pulse and the shimmer come across and
   the avatar and loyalty bar are left alone. */
.dashboard-sidebar .sidebar-header{
  position: relative;
  overflow: hidden;
  padding: 16px 16px 15px !important;
  background: linear-gradient(135deg, rgba(8, 4, 14, .98),
                                      rgba(12, 6, 20, .97),
                                      rgba(8, 4, 14, .98)) !important;
}
.dashboard-sidebar .sidebar-header::after{
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 26%;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--purple), transparent);
  animation: ngSidePulse 9s ease-in-out infinite;
  pointer-events: none;
}
/* the shop's `.sidebar-title` treatment, on the one line that plays that
   part here */
.dashboard-sidebar .user-name{
  background: linear-gradient(105deg, rgba(255, 255, 255, .75), #fff 40%,
                                      #e8bfff, #fff 70%, rgba(255, 255, 255, .7));
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: sectionTitleShim 6s ease-in-out infinite;
}
.dashboard-sidebar .user-tier{
  color: rgba(255, 255, 255, .4) !important;
  font-weight: 300 !important;
}

/* The staff link (dashboard-live.jsx). It is the only row on the rail with
   no sprite tile, so it leans on the `no-spr` glyph path above. */
.dashboard-nav-list .ng-panel-group .nav-group-title{
  color: var(--purple-light) !important;
}
.dashboard-nav-list .ng-panel-link i{
  color: rgba(186, 85, 211, .85) !important;
}


@media (max-width: 768px){

  /* Section 46H set `padding: 0 16px` on these rows for the drawer, which
     would push the chip off its 7px inset. The shop's own phone rule takes
     the row to 46px at this width; this matches it. */
  .dashboard-sidebar .nav-item a{
    padding: 4px 11px 4px 6px !important;
    min-height: 46px !important;
    font-size: 11px !important;
  }


  /* ─── 48D · Recently viewed, two by two ────────────────────────────
     Section 46H made this a swipe rail, which was right when the fix was
     "it is clipped rather than scrollable". Two columns of two is the
     shape the rest of the phone uses — new arrivals, the compact rows, the
     recommendations rail — and it shows four items at once instead of two
     and a half.

     `.recently-viewed-track` is a flex row in the route sheet and the
     items are `flex: 0 0 <rail width>`; both have to be released or the
     grid gets one 164px column and three empty tracks. */
  .recently-viewed-track{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
    overflow: visible !important;
    padding: 0 !important;
    margin-right: 0 !important;
    scroll-snap-type: none !important;
  }
  .recently-viewed-item{
    flex: initial !important;
    width: auto !important;
    min-width: 0 !important;
    height: 200px !important;          /* the compact card's height exactly */
    scroll-snap-align: none !important;
    border-color: var(--ng-line) !important;
  }
  /* Two of two. The endpoint returns eight; a third and fourth row of
     things you have already looked at is not what the section is for. */
  .recently-viewed-item:nth-child(n+5){
    display: none !important;
  }
  /* One action, not two. "View" opens the product, which is what tapping
     the card already does — the same redundancy the recommendation rail
     lost in 2.25.0. */
  .recently-viewed-actions .recently-viewed-btn.quick-view{
    display: none !important;
  }
  .recently-viewed-actions{
    gap: 0 !important;
  }
  .recently-viewed-actions .recently-viewed-btn.add-cart{
    flex: 1 1 auto !important;
    min-height: 30px !important;
    font-size: 9px !important;
    letter-spacing: .5px !important;
    border-radius: 2px !important;
  }
  .recently-viewed-info{
    padding: 8px !important;
  }


  /* ─── 48E · The See More button had nowhere to end ─────────────────
     Measured: the button's bottom edge is at y=1474 and the "Recently
     Viewed" band's top edge is at y=1474. Not an overlap — a zero gap.
     Nothing is mispositioned; the sections simply have no space between
     them, and the band is 370px wide against the button's 346px, so it
     reads as the next section landing on top of the button.

     `.orders-section` and `.recently-viewed-section` both carry
     `margin-bottom: 0` from the route sheet and neither has a top margin,
     so every block on this page is flush with the next. The index keeps
     22px between sections (`.container > * + *`), which does not reach
     here — these are `.dashboard-content > *`, several levels down. */
  .dashboard-content > * + *{
    margin-top: 22px !important;
  }
  .dashboard-content .orders-section{
    margin-bottom: 0 !important;
  }
  /* The wrapper the button sits in carries an inline `margin-top: 14px`
     and has no class, so nothing can reach it — the button's own bottom
     margin is what separates it from the end of its section. */
  .orders-section .view-more-btn{
    margin-bottom: 4px !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   49 · ONE CARD HEIGHT, NO SIDEWAYS SCROLLING, AND THE PRODUCT PAGE
   ═══════════════════════════════════════════════════════════════════════
   Twelve notes. Three of them are one-line fixes to things that were
   invisible from the markup: the footer gap is zero on every page because
   nothing has ever had a bottom margin there, The Edit's tiles are the
   wrong size because a route chunk carries a second copy of the magazine
   block, and half the "scrollers" the brief asks to remove are already
   dead rules. The rest is layout. */

@media (max-width: 768px){

  /* ─── 49A · Every page ends before the footer starts ─────────────────
     Measured on three pages, all zero:

       index     .mag-newsletter  → .container → footer      0px
       shop      .rec-footer      → .recommendations-full-width → footer   0px
       lookbook  .lb-newsletter   → .container → footer      0px

     The reason is the same each time and it is not any of those sections:
     `footer` has no top margin and no top padding at any width, and every
     page's last block has `margin-bottom: 0`. So the last content border
     and the footer's `border-top` are the same line, which is what reads
     as a collision.

     One rule on the footer rather than three on the sections. It is the
     constant in all three cases, it cannot be missed by a page nobody
     thought to check, and `.shop-page` / `.product-page` wrap their own
     footer — so a rule on the sections would have to know that and a rule
     on the footer does not. */
  footer{
    /* Superseded twice. 49A used `margin-top`, which is transparent and
       painted the body through it (50A). 50A moved it to the footer's
       padding, which removed the seam but put the clearance on the
       FOOTER's surface — so the newsletter box's own border still ended
       exactly on the footer's top edge and still read as touching it.
       51A puts the clearance inside the page wrapper instead, where it is
       the wrapper's own black, and the footer goes back to no top pad. */
    padding-top: 0 !important;
  }


  /* ─── 49B · One height for every item card ───────────────────────────
     They ran 176 to 280:

       .arrival-card            176    .recommendation-card      240
       .compact-product-card    200    .shop-page .product-card  240
       .recently-viewed-item    200    .search-product-card      280
       .lb-release              200    .lb-model-card            223

     220px for all of them. Above the 200 that most of them already sat at
     — "slightly tall" — and well under the 240/280 that made the shop and
     the search results scroll twice as far for the same number of things.

     `.blog-card-compact` is deliberately NOT in this list: note 7 asks it
     to carry the excerpt and the latest reply, which do not fit in 220.
     It is a content card, not an item card, and it is sized to what it
     holds in 49F. Neither are The Edit's square tiles, the frequently-
     bought tiles, the trending rows or the review cards — none of those
     is a product in a grid. */
  :is(.compact-product-card,
      .new-arrivals-grid .arrival-card,
      .shop-page .product-card,
      .recommendation-card,
      .recently-viewed-item,
      .search-page .search-product-card,
      .search-products-grid .search-product-card,
      .lb-release,
      .lb-model-card){
    height: 270px !important;
    min-height: 0 !important;
    max-height: none !important;
  }
  /* `.compact-product-card[style]` exists because the component sets an
     inline `cursor`, and `ng-polish.css:189` had to match it to win. Same
     reason here. */
  .compact-product-card[style]{
    height: 270px !important;
  }
  /* The shop card kept a 10px radius from `storefront.css@104708`, which
     is the same rule that set its height — so taking the height means
     taking the radius with it. */
  .shop-page .product-card{
    border-radius: 2px !important;
  }
  /* `.lb-model-card` sized itself by ratio; a fixed height and a ratio
     are the same declaration twice, and the ratio was winning. */
  .lb-model-card{
    aspect-ratio: auto !important;
  }


  /* ─── 49C · The Edit's tiles, and why two of them were short ──────────
     "Some item cards don't show up the correct size and won't fill the
     space, mostly the two columns of two on the second / third rows."

     Exactly two tiles, always the same two, and the cause is not in this
     file. `assets/storefront-AccountRoute.css` carries a COMPLETE SECOND
     COPY of the magazine block — offsets 16838 to 30478, including

         @media (max-width:768px){ .mag-card-sm{ flex:0 0 42vw;
             min-width:130px; max-width:170px; … } }

     Route chunks are injected after `ng-polish.css` and are never
     unloaded, so once `/account` has been opened in a session that
     `max-width: 170px` is live on the index. Section 46C releases
     `width`, `height`, `min-height`, `max-height`, `flex` and
     `aspect-ratio` on the tiles — but never `max-width`, because nothing
     in this file sets one.

     The two tiles it hits are `releases[3]` and `releases[4]`, the only
     `.mag-card-sm` in the bottom row, which land third and fourth in the
     Releases tab: the second row. The `.mag-blog` tiles beside them keep
     the full 168px track. Hence two short tiles, on the rows named, and
     only sometimes — only after a visit to /account.

     Both bounds are released. `min-width` too: the same block sets
     `min-width: 130px`, which is harmless at 168px and would not be on a
     narrower phone. */
  .magazine-section .mag-card.ng-m-edit-lead,
  .magazine-section .mag-card.ng-m-edit-tile,
  .magazine-section .mag-newsletter{
    max-width: none !important;
    min-width: 0 !important;
  }


  /* ─── 49D · Nothing scrolls sideways ─────────────────────────────────
     "There should be NO scrolling sections on mobile, only grid style."

     Fifteen elements still had `overflow-x: auto` inside a mobile media
     query. Six more had it and were already dead — overridden by a later
     grid — which is worth knowing before writing a rule for them.

     The blanket below releases the overflow and lets the row wrap; the
     ones that need to become a real grid get their own rule after it.
     Vertical overflow is untouched: the two drawers (`.category-list`,
     `.dashboard-nav-list`) scroll up and down and have to. */
  :is(.lb-releases-slide,
      .shop-filter-grid .sfg-options,
      .shop-page .sfg-row,
      .shop-page .filter-bar,
      .search-filter-left,
      .hero-meta,
      .perks-chips,
      .product-page .recommendations-full-width,
      .product-page .category-header:has(.sort-controls) .sort-controls,
      .reviews-section .sort-controls){
    overflow-x: visible !important;
    flex-wrap: wrap !important;
    scroll-snap-type: none !important;
    max-width: none !important;
  }

  /* Upcoming releases, the one the note names. It was a flex row of
     180px cards in a snap scroller; it is the same two-up grid as every
     other card block, and the cards take 49B's height with them. */
  .lb-releases-slide{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
    margin-left: 0 !important;
    padding: 0 !important;
  }
  .lb-releases-slide:not(.active-slide){ display: none !important; }
  .lb-release{
    flex: initial !important;
    width: auto !important;
    min-width: 0 !important;
    scroll-snap-align: none !important;
  }
  /* The carousel chrome has nothing left to drive. The tabs below it
     still switch slides, so they stay. */
  .lb-releases-nav{ display: none !important; }
  .lb-releases-viewport,
  .lb-releases-carousel{ overflow: visible !important; }

  /* The review sort row was a 58vw scroller holding nine buttons. Wrapped,
     it is three rows of three and every option is visible at once, which
     is the point of a sort control. */
  .reviews-section .sort-controls{
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 6px !important;
    justify-content: center !important;
    width: 100% !important;
  }


  /* ─── 49E · The lookbook's philosophy block ──────────────────────────
     Note 5. The block has no `text-align` anywhere — it inherits `left`
     from the document — and its overline and signature are flex rows with
     a decorative dash, so centring the text alone would leave those two
     hanging at the left edge. Both are centred as flex containers. */
  .lb-philosophy-text{
    text-align: center !important;
  }
  .lb-philosophy-overline,
  .lb-philosophy-sig{
    justify-content: center !important;
  }
  .lb-philosophy-title,
  .lb-philosophy-body{
    text-align: center !important;
  }

  /* Note 6. Three values in a single-column stack with a hairline over
     the top of the group and nothing around each one. Two up, one across
     the bottom, each in its own bordered cell — the same box the index's
     product sections use, so the block reads as part of the set. */
  .lb-philosophy-values{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 8px !important;
    flex-direction: unset !important;
    margin-top: 14px !important;
    padding-top: 0 !important;
    border-top: 0 !important;
  }
  .lb-philosophy-value{
    flex: initial !important;
    min-width: 0 !important;
    padding: 12px 10px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: linear-gradient(135deg, #070709, #0b0a0f) !important;
    text-align: center !important;
  }
  /* The third spans, so a three-item list reads as a deliberate 2-over-1
     rather than a two-up grid with a gap in it. */
  .lb-philosophy-value:nth-child(3):last-child{
    grid-column: 1 / -1 !important;
  }
  .lb-philosophy-value-icon{
    margin-bottom: 6px !important;
    opacity: .85 !important;
  }
  .lb-philosophy-value-text{
    font-size: 9.5px !important;
    line-height: 1.5 !important;
  }


  /* ─── 49F · The blog card keeps its preview and its reply ────────────
     Section 48B hid the excerpt, the stats, the byline and the response
     block because none of them fitted a 208px card. Note 7 asks for the
     preview and the latest reply back, "as consistent to the desktop
     design as possible but fit for mobile" — so the card grows to hold
     them rather than the content shrinking to fit the card.

     What stays hidden is the three stat chips and the author byline: the
     date already carries the byline's job on a 168px column, and three
     counters on one line at 9px is a row of noise. The excerpt and the
     reply are the two things the note names. */
  .main-content .blog-card-compact,
  .main-content .blog-card-compact:first-child{
    height: 310px !important;
  }
  .main-content .blog-card-compact .blog-excerpt,
  .main-content .blog-card-compact .recent-response,
  .main-content .blog-card-compact:first-child .recent-response{
    display: block !important;
  }
  .main-content .blog-card-compact .blog-excerpt{
    margin: 0 0 7px !important;
    font-size: 9.5px !important;
    line-height: 1.45 !important;
    color: rgba(255, 255, 255, .5) !important;
    display: -webkit-box !important;
    -webkit-line-clamp: 2 !important;
    -webkit-box-orient: vertical !important;
    overflow: hidden !important;
  }
  .main-content .blog-card-compact .blog-title-compact{
    -webkit-line-clamp: 2 !important;
  }
  /* The reply is the card's footer: pinned to the bottom, boxed, and two
     lines at most. `margin-top:auto` was on `.blog-author-date`; the
     reply is below it now, so the date keeps its place and the reply
     takes the floor. */
  .main-content .blog-card-compact .blog-author-date{
    margin-top: 0 !important;
    margin-bottom: 6px !important;
  }
  .main-content .blog-card-compact .recent-response{
    margin-top: auto !important;
    padding: 7px 8px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: rgba(255, 255, 255, .014) !important;
    min-height: 0 !important;
  }
  .main-content .blog-card-compact .response-header{
    display: flex !important;
    align-items: center !important;
    gap: 6px !important;
    margin-bottom: 4px !important;
  }
  .main-content .blog-card-compact .response-avatar{
    width: 18px !important;
    height: 18px !important;
    flex: 0 0 18px !important;
    border-radius: 2px !important;
    font-size: 7.5px !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
  .main-content .blog-card-compact .response-info{
    display: flex !important;
    align-items: baseline !important;
    gap: 5px !important;
    min-width: 0 !important;
  }
  .main-content .blog-card-compact .response-author{
    font-size: 8.5px !important;
    letter-spacing: .3px !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
  }
  .main-content .blog-card-compact .response-time{
    font-size: 8px !important;
    color: rgba(255, 255, 255, .3) !important;
    white-space: nowrap !important;
  }
  .main-content .blog-card-compact .response-text{
    font-size: 9px !important;
    line-height: 1.4 !important;
    display: -webkit-box !important;
    -webkit-line-clamp: 2 !important;
    -webkit-box-orient: vertical !important;
    overflow: hidden !important;
  }


  /* ─── 49G · The product photographs ──────────────────────────────────
     Note 8. What was there: a 440px full-bleed image with no border and
     square corners, sitting directly on a flush 60px strip of thumbnails
     on a `#050505` band, thumbs at 35% opacity with no border and a 2px
     gap, in a sideways scroller. Nothing about it belonged to the rest of
     the page — every other panel on this site is a bordered 2px box on
     the surface colour.

     So: the image becomes a framed panel like everything else, shorter,
     with the thumbnails as a real grid beneath it rather than a strip. */
  .product-page .product-gallery{
    gap: 8px !important;
    padding: 12px 0 0 !important;
  }
  .product-page .main-image-container{
    height: 340px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: linear-gradient(135deg, #070709, #0b0a0f) !important;
    overflow: hidden !important;
  }
  /* `contain`, not `cover`: a cropped garment photo loses the hem and the
     neckline, which are the two things somebody zooms in to check. The
     panel behind it is the same box as the rest of the page, so the
     letterbox reads as a frame rather than as a gap. */
  .product-page .main-product-image{
    object-fit: contain !important;
    padding: 10px !important;
  }
  .product-page .image-gallery-thumbs{
    display: grid !important;
    grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
    gap: 6px !important;
    overflow: visible !important;
    background: none !important;
    padding: 0 !important;
  }
  .product-page .thumb-image{
    flex: initial !important;
    width: 100% !important;
    height: 64px !important;
    object-fit: cover !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    opacity: .55 !important;
    transition: opacity .3s ease, border-color .3s ease !important;
  }
  .product-page .thumb-image.active{
    opacity: 1 !important;
    border-color: rgba(186, 85, 211, .55) !important;
  }


  /* ─── 49H · The rating comes out ─────────────────────────────────────
     Note 9. Hidden rather than removed from the markup: the stars are
     still the anchor for `#reviews` further down, and the component that
     draws them also feeds the review count. Mobile only, so the desktop
     product page is unchanged. */
  .product-page .product-header .product-rating{
    display: none !important;
  }


  /* ─── 49I · Size guide and frequently bought join the set ────────────
     Note 10. Both were bordered panels with an `<h4>` band — close, but
     not the same thing the index uses. The index's product sections are
     `linear-gradient(135deg, #070709, #0b0a0f)` inside `1px solid
     var(--ng-line)` at 2px (ng-polish 42B), and their headings are
     centred `.category-header` bands.

     There is no `.category-header-wrapper` in this markup — the heading
     is a plain `<h4>` — so the band is built on the `<h4>` itself: same
     centring, same hairlines top and bottom, same type. */
  .product-page .size-guide,
  .product-page .frequently-bought{
    background: linear-gradient(135deg, #070709, #0b0a0f) !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    overflow: hidden !important;
    margin-bottom: 14px !important;
  }
  .product-page .size-guide h4,
  .product-page .frequently-bought h4{
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 8px !important;
    margin: 0 !important;
    padding: 12px 14px !important;
    background: rgba(138, 43, 226, .05) !important;
    border-top: 0 !important;
    border-bottom: 1px solid var(--ng-line) !important;
    color: rgba(255, 255, 255, .92) !important;
    font-size: 11px !important;
    font-weight: 500 !important;
    letter-spacing: 2px !important;
    text-transform: uppercase !important;
    text-align: center !important;
  }
  .product-page .size-guide h4 i,
  .product-page .frequently-bought h4 i{
    color: var(--ng-m-accent) !important;
    font-size: 12px !important;
  }


  /* ─── 49J · "Worn this?" is centred ──────────────────────────────────
     Note 11. `.ng-review-invite` is a flex column with
     `align-items: flex-start`, set at top level with no mobile override —
     so the icon, the heading and the copy all sit hard left while the
     button below them is `align-self: stretch` and full width. */
  .ng-review-invite{
    align-items: center !important;
    text-align: center !important;
  }
  .ng-review-invite .ng-invite-title,
  .ng-review-invite .ng-invite-copy{
    text-align: center !important;
    width: 100% !important;
  }
  /* The form inside it keeps its full-width fields — centring a text
     input just makes it narrow. */
  .ng-review-invite-form{
    align-items: stretch !important;
    text-align: left !important;
  }


  /* ─── 49K · The review cards ─────────────────────────────────────────
     Note 12. 248px each, six to a page, so the review list alone ran
     1500px. And the card did not look like the platform: a 10px radius
     where everything else is 2px, an `rgba(255,255,255,.04)` border where
     everything else is `var(--ng-line)`, a 38px avatar in a column header
     that pushed the stars onto their own row, and the body clamped to
     four lines at 12px.

     The buttons are untouched — same classes, same handlers, same
     `.review-report-btn{display:none}` that `storefront.css` already
     applies here. Only the box, the header layout and the type change. */
  .product-page .review-item{
    padding: 13px 14px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: linear-gradient(135deg, #070709, #0b0a0f) !important;
  }
  /* A row, not a column: avatar, then name over date, then the stars hard
     right. That is the same three-track shape the sidebar rows use, and
     it saves a whole line per card. */
  .product-page .review-item .review-header{
    display: grid !important;
    grid-template-columns: auto minmax(0, 1fr) auto !important;
    align-items: center !important;
    gap: 10px !important;
    margin-bottom: 10px !important;
  }
  .product-page .review-item .reviewer-avatar{
    width: 32px !important;
    height: 32px !important;
    flex: 0 0 32px !important;
    border-radius: 2px !important;
    border: 1px solid var(--ng-line) !important;
    font-size: 11px !important;
    align-self: center !important;
    order: 0 !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
  .product-page .review-item .reviewer-info{
    order: 0 !important;
    min-width: 0 !important;
  }
  .product-page .review-item .reviewer-name{
    font-size: 11px !important;
    font-weight: 500 !important;
    letter-spacing: .4px !important;
    color: rgba(255, 255, 255, .92) !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
  }
  .product-page .review-item .review-date{
    font-size: 9px !important;
    letter-spacing: .3px !important;
  }
  .product-page .review-item .review-rating{
    position: static !important;
    display: flex !important;
    gap: 2px !important;
    justify-self: end !important;
  }
  .product-page .review-item .review-rating .star{
    width: 11px !important;
    height: 11px !important;
  }
  /* Three lines instead of four, and the platform's body size. */
  .product-page .review-item .review-content{
    font-size: 11px !important;
    line-height: 1.55 !important;
    color: rgba(255, 255, 255, .6) !important;
    -webkit-line-clamp: 3 !important;
    margin-bottom: 10px !important;
  }
  .product-page .review-item .review-content strong{
    color: rgba(255, 255, 255, .88) !important;
  }
  /* The house reply, when there is one. */
  .product-page .review-item .ng-review-reply{
    margin-top: 8px !important;
    padding: 8px 9px !important;
    border-left: 2px solid rgba(186, 85, 211, .45) !important;
    border-radius: 0 2px 2px 0 !important;
    background: rgba(138, 43, 226, .06) !important;
    font-size: 10px !important;
    line-height: 1.5 !important;
  }
  .product-page .review-item .review-actions{
    gap: 6px !important;
    padding-top: 9px !important;
    border-top: 1px solid var(--ng-line) !important;
  }
  .product-page .review-item .review-action-btn{
    padding: 0 10px !important;
    min-height: 32px !important;
    height: 32px !important;
    border-radius: 2px !important;
    border: 1px solid var(--ng-line) !important;
    background: rgba(255, 255, 255, .016) !important;
    font-size: 9px !important;
    letter-spacing: .5px !important;
  }
  .product-page .reviews-grid{
    gap: 8px !important;
  }


  /* ─── 49L · New Arrivals comes down to two columns ───────────────────
     43E made this grid three-up at 176px tall, back when it was the only
     grid on the index that was not two-up. Note 2 asks for one card height
     everywhere, and 49B gave it 220px — but a third of 346px is a 101px
     column, and 101x220 is a 0.46 sliver with the title truncated to
     "Velvet Drea...". A card that shape is not "sleek and clean", and it
     is the only one on the page that is not 167px wide.

     Two columns, still six items, so the section is three rows instead of
     two and every card on the index is now the same 167x220. The type
     sizes 43E shrank for the 101px column go back to the compact card's. */
  .new-arrivals-grid{
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
  }
  .new-arrivals-grid .arrival-name{
    font-size: 10px !important;
    letter-spacing: .3px !important;
    line-height: 1.3 !important;
  }
  .new-arrivals-grid .arrival-price{ font-size: 11px !important; }
  .new-arrivals-grid .arrival-badge{
    font-size: 7.5px !important;
    padding: 3px 6px !important;
  }


  /* ─── 49M · A gallery of one is not a gallery ────────────────────────
     49G laid the thumbnails out as a four-column grid, which is right for
     a product with three or four photos. A product with one photo gets a
     single 128px tile sitting alone in the first column, and that tile is
     the main image again — tapping it does nothing. Hide the strip when
     there is nothing to switch to; the panel above is the whole gallery. */
  .product-page .image-gallery-thumbs:has(> *:only-child){
    display: none !important;
  }


  /* ─── 49N · The blog excerpt reads left, like the rest of the card ───
     `ng-polish:6159` is an unscoped desktop rule — `.main-content
     .blog-excerpt` at (0,2,0), no !important — that centres the excerpt
     and reserves `calc(1.45em * 3)` of height for three lines. 49F clamps
     the mobile card to two lines at (0,3,0) and wins on the clamp, but it
     never touched either of those two, so the card ends up with centred
     body copy sitting in a box one line taller than its content.

     Centred prose under a flush-left category, a flush-left title, a
     flush-left date and a flush-left reply is the "poor alignment" the
     notes keep pointing at. Left, and the box sized to the two lines it
     is actually allowed to show. */
  .main-content .blog-card-compact .blog-excerpt{
    text-align: left !important;
    height: auto !important;
    max-height: calc(1.45em * 2) !important;
  }
  /* And the category row, which is a flex row centred by `ng-polish:6166`
     — so `text-align` reads "start" on it while the words sit 40px in.
     Measured: the category label started at x=51 against x=11 for the
     title, the date, the excerpt and the reply. */
  .main-content .blog-card-compact .blog-category-compact{
    justify-content: flex-start !important;
  }


  /* ═══════════════════════════════════════════════════════════════════
     SECTION 50 · Round nine — seven notes
     ═══════════════════════════════════════════════════════════════════ */

  /* ─── 50A · Note 1 · The seam above the tab bar ──────────────────────
     Last round's note asked for a gap between the last section and the
     footer, and 49A gave it one with `footer { margin-top: 22px }`. That
     was the wrong property. A margin box is transparent, so those 22px
     painted the BODY — and sampling a column of pixels down the bottom of
     the index reads:

       y 560-610   #000000   the container
       y 612-634   #0e0c12   ← the margin, showing body through it
       y 636-772   #050505   the footer
       y 776       #2d2c2d   the tab bar's top border
       y 780-844   #03020a   the tab bar

     Three blacks stacked with the lightest one in the middle, which is the
     "weird gap" — the page stops looking like one surface right where it
     meets the bottom navigation. The fix is the same 22px expressed as the
     footer's own padding, so the band is footer-coloured and #000 meets
     #050505 directly. The rule itself is edited in place up in 49A. */

  /* ─── 50C · Note 5 · Category headers, lifted off the floor ──────────
     `.category-header` is painted with
       linear-gradient(135deg, rgba(8,4,14,.98), rgba(12,6,20,.97), …)
     — a relative luminance of about 0.004 against a #050508 page. On a
     desktop panel that reads as a subtle band; on a phone in daylight it
     is invisible, and the header is the only thing separating one section
     from the next.

     Same three-stop 135deg gradient, same stops, same border-image, same
     shimmer on the title and the same travelling hairline — only the three
     colours move up. Roughly 3x the luminance, still unmistakably the
     brand's near-black purple rather than a grey. */
  .category-header{
    background: linear-gradient(135deg,
      rgba(26, 15, 42, .98),
      rgba(36, 21, 57, .97),
      rgba(26, 15, 42, .98)) !important;
  }
  /* The header sits on a wrapper that paints its own backing on some
     routes; left darker it shows as a rim around the lighter band. */
  .category-header-wrapper{
    background: transparent !important;
  }

  /* ─── 50D · Note 6 · The blog hero was carrying five chips ───────────
     Measured inside a 346x210 hero: `.hero-meta` holds five
     `.hero-meta-item` chips, and at 390px they wrap onto THREE rows —
     y=119, y=144 and y=169 — which is 70px, a third of the hero, for
     metadata. The See More button is absolutely placed at x=219 y=134, so
     rows two and three run underneath it.

     Two chips, one row, no wrap. The rest of the metadata is on the
     article itself, which is one tap away. */
  .featured-hero .hero-meta{
    flex-wrap: nowrap !important;
    overflow: hidden !important;
    gap: 6px !important;
    max-width: calc(100% - 108px) !important;
  }
  .featured-hero .hero-meta-item:nth-child(n+3){
    display: none !important;
  }
  .featured-hero .hero-bottom-content{
    align-items: center !important;
    gap: 8px !important;
  }
  /* With the meta down to one row the content block is ~50px shorter, so
     the excerpt and the title stop overlapping — measured, they were
     sharing y=69..91. */
  .featured-hero .hero-content{
    padding: 10px 18px 12px !important;
  }

  /* ─── 50E · Note 7 · The blog card is centred after all ──────────────
     This reverses 49N, and the reversal is the point: last round's note
     asked for "the same ideal mobile centering", which I read as "one
     consistent edge" and implemented as flush left. It meant centred.

     Category, title, date and excerpt are centred here. The reply block
     keeps its own layout — it is a quoted comment with an avatar and a
     timestamp on a row, and centring a quotation mid-box reads as broken
     rather than deliberate.

     `:first-child` is named on every selector here. §42 styles the lead
     card separately — `.main-content .blog-card-compact:first-child
     .blog-title-compact` is four classes deep with `text-align: left
     !important`, so a three-class rule loses on the first card and wins on
     every other one, which is worse than losing on all of them. */
  .main-content .blog-card-compact .blog-excerpt,
  .main-content .blog-card-compact:first-child .blog-excerpt,
  .main-content .blog-card-compact .blog-title-compact,
  .main-content .blog-card-compact:first-child .blog-title-compact{
    text-align: center !important;
  }
  .main-content .blog-card-compact .blog-category-compact,
  .main-content .blog-card-compact:first-child .blog-category-compact,
  .main-content .blog-card-compact .blog-author-date,
  .main-content .blog-card-compact:first-child .blog-author-date{
    justify-content: center !important;
  }

  /* ─── 50F · Note 3 · The Edit leaves the sign-in page ────────────────
     `#theEdit` is the account page's own copy of the magazine block — a
     separate implementation from the index's, and the only element on the
     site carrying that id, so it is an exact hook for this one instance.

     Its section header is coupled to it by position rather than by class
     (`account-edit.jsx` hides `head[head.length - 1]` when the payload is
     empty), so the header is matched the same way the eye does it: the
     wrapper immediately before the block. */
  #theEdit{ display: none !important; }
  .category-header-wrapper:has(+ #theEdit){ display: none !important; }

  /* ─── 50G · Note 2 · Guest links on the sign-in page ─────────────────
     Rows are built on the shop rail's proportions — a 38px chip, a label,
     a caret — rather than a fourth copy of that grid. Two columns on a
     phone, because eight full-width rows would push the sign-in form off
     the first screen, which is the one thing this page is for. */
  .ng-guest-nav{
    margin: 18px 14px 0 !important;
    padding: 14px 13px 13px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: linear-gradient(160deg,
      rgba(22, 13, 36, .55), rgba(8, 6, 14, .72)) !important;
  }
  .ng-guest-head{
    display: block !important;
    margin-bottom: 11px !important;
    text-align: center !important;
  }
  .ng-guest-head-title{
    display: block !important;
    font-size: 11px !important;
    font-weight: 500 !important;
    letter-spacing: 1.6px !important;
    text-transform: uppercase !important;
    color: rgba(255, 255, 255, .82) !important;
  }
  .ng-guest-head-sub{
    display: block !important;
    margin-top: 3px !important;
    font-size: 8.5px !important;
    font-weight: 300 !important;
    letter-spacing: 1px !important;
    text-transform: uppercase !important;
    color: rgba(186, 85, 211, .55) !important;
  }
  .ng-guest-grid{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 7px !important;
  }
  .ng-guest-link{
    display: grid !important;
    grid-template-columns: 30px minmax(0, 1fr) !important;
    align-items: center !important;
    gap: 8px !important;
    min-height: 46px !important;
    padding: 7px 8px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: rgba(138, 43, 226, .045) !important;
    text-decoration: none !important;
    -webkit-tap-highlight-color: transparent !important;
  }
  .ng-guest-link:active{ background: rgba(138, 43, 226, .11) !important; }
  .ng-guest-chip{
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    width: 30px !important;
    height: 30px !important;
    border: 1px solid rgba(138, 43, 226, .18) !important;
    border-radius: 2px !important;
    background: rgba(138, 43, 226, .1) !important;
  }
  .ng-guest-chip i{
    font-size: 11px !important;
    color: rgba(186, 85, 211, .85) !important;
  }
  .ng-guest-body{ min-width: 0 !important; }
  .ng-guest-label{
    display: block !important;
    font-size: 9.5px !important;
    font-weight: 500 !important;
    letter-spacing: .7px !important;
    text-transform: uppercase !important;
    color: rgba(255, 255, 255, .8) !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
  }
  /* Two columns is 150px of row; the descriptions belong to the desktop
     layout, where the list is one column wide. */
  .ng-guest-desc,
  .ng-guest-caret{ display: none !important; }
}

/* 50G-desktop is gone: round ten's note 1 says the guest links should not
   appear on the desktop at all, so the one-column desktop list it defined
   has no reason to exist. 51O-desktop below hides all three pieces. */

@media (max-width: 768px){
  /* ═══════════════════════════════════════════════════════════════════
     SECTION 51 · Round ten — fifteen notes (mobile half)
     ═══════════════════════════════════════════════════════════════════ */

  /* ─── 51A · Note 5 · The clearance moves inside the page ────────────
     Third attempt at the same 22px, and the first two are instructive.
     49A used `footer{margin-top}` — transparent, so it painted the body
     and made a light band. 50A moved it to `footer{padding-top}` — no
     band, but the clearance now belongs to the footer, so the newsletter
     box's bottom border still landed exactly on the footer's top edge.
     Measured: `.magazine-section` bottom 6804, footer top 6804.

     The gap has to be inside the LAST THING ON THE PAGE, painted in that
     element's own background, so the box ends, then there is black, then
     the footer begins. `.container` is #000 and the footer is #050505, so
     the join is still invisible — which is the other half of the note
     ("without disconnecting the container from the footer"). */
  /* Round twelve corrects the list. `.main-layout`, `.shop-page` and
     `.product-page` are TRANSPARENT wrappers — nothing paints them, so
     22px of their padding is 22px of body showing through. That is notes
     10 and 13: the blog container stopping short of the footer, and the
     shop's rail detaching from the listing above it. The index worked
     because `.container` really is the painted box.

     The rule now names only elements that paint themselves. The painted
     box inside `.main-layout` is `.main-content`, and it is handled by
     53J below. */
  :is(.container, .search-page, .cart-page, .lb-page, .dashboard-content,
      /* The shop and product pages end with the recommendation rail, and
         the rail is a SIBLING of `.shop-page`, not a child — so padding on
         the page wrapper leaves its buttons 1px off the footer. Measured:
         `.more-like-it-btn` bottom 611, footer top 612. */
      .recommendations-full-width){
    padding-bottom: 22px !important;
  }
  /* § 45 writes `.shop-page > .recommendations-full-width{padding:0 12px}`
     at (0,2,0) — a shorthand, so it resets padding-bottom to 0 and beats
     the (0,1,0) list above. Same specificity, later in the file. */
  .shop-page > .recommendations-full-width,
  .product-page > .recommendations-full-width{
    padding-bottom: 22px !important;
  }

  /* ─── 51B · Note 6 · Section boxes were stacked flush ────────────────
     Measured on the index: `.compact-products-section` → the next
     `.compact-products-section` is a 0px gap, twice — Panties & Bras runs
     straight into Accessories, Beauty Luxe into Shirts & Hoodies. The
     index's `.container > * + *{margin-top:22px}` does not reach them:
     every one of these sections is wrapped in its own
     `.category-header-wrapper` + section pair, so consecutive SECTIONS
     are siblings but a header always sits between a section and the next
     one — except where two sections are adjacent, which is exactly the
     two places that collide. */
  .container > .compact-products-section + .compact-products-section,
  .container > .new-arrivals-section + .compact-products-section,
  .container > .compact-products-section + .new-arrivals-section{
    margin-top: 22px !important;
  }

  /* ─── 51C · Note 7 · Equal air above and below the heading ───────────
     The band measures 15px of padding top and bottom, which looks
     balanced on paper. It is not, because `.category-header-title-row` is
     a flex row sized by its ICON, not its text: the row is 40px tall for
     a 13px title, so the visible gap above the glyphs is 15 + half the
     leftover, while below the subtitle it is just the 15.

     Giving the row and the title the same line-height collapses the
     leftover, and the band is then as tall as its content plus equal
     padding. The icon keeps its size — it is centred in the row. */
  .category-header-title-row{
    line-height: 1 !important;
    align-items: center !important;
  }
  .category-header-text{
    line-height: 1.1 !important;
  }
  .category-header-subtitle{
    line-height: 1.1 !important;
    margin-top: 5px !important;
  }
  /* And then the band's own padding has to account for the tap target.
     § 39 gives `.category-header-text` `min-height: 40px` on purpose — the
     title is the link into the section and 121x14 was not a tappable
     control. The glyphs are centred in those 40px, so a 15px line sits
     inside a 12.5px halo top and bottom, and the halo is invisible. With
     symmetric padding the measured result is 27px of air above the words
     and 20px below the subtitle: balanced boxes, lopsided type.

     So the band leans its padding the other way by exactly the halo. Two
     cases, because the arithmetic differs:
       · subtitle visible — the last glyph is the subtitle, which has no
         halo, so the bottom padding carries the whole 12.5px difference
       · subtitle hidden (the blog and thread at this width) — the last
         glyph is the title itself, halo on both sides, so symmetric
         padding is already correct. */
  .category-header{
    padding-top: 12px !important;
    padding-bottom: 19px !important;
  }
  .main-content .category-header{
    padding-top: 14px !important;
    padding-bottom: 14px !important;
  }

  /* ─── 51D-fix · Note 8 · the inset came from the wrapper ─────────────
     The button was already `width:100%`; its parent `.load-more` carries
     `padding: 0 16px !important` from § 40, which is what made 346 into
     314. The padding belongs to the page, not to this row. */
  .shop-page .load-more{
    padding-left: 0 !important;
    padding-right: 0 !important;
  }

  /* ─── 51E · Note 9 · The product page's headers were the odd ones ────
     Measured at 390px. Index and blog headers: `justify-content:center`,
     `.category-header-content{align-items:center}`, subtitle centred, and
     its offset from the band's centre is 0. The product page's two:
     `flex-start` throughout, subtitle `text-align:left`, offsets -48 and
     -61. So "centered like all other category-headers globally" is
     literally true — these two are the exception, not the rule.

     The reviews header carries `.sort-controls`, which is why it was laid
     out as a row in the first place; 49D already made those wrap, so the
     header can centre its content like every other one. */
  .product-page .category-header{
    justify-content: center !important;
  }
  .product-page .category-header .category-header-content{
    align-items: center !important;
  }
  .product-page .category-header .category-header-subtitle{
    text-align: center !important;
  }

  /* ─── 51D · Note 8 · The shop's See More matched nothing ─────────────
     Measured: the products grid starts at x=22 and is 346 wide; the
     button started at x=38 and was 314 wide — inset 16px on each side
     against the columns above it. */
  .shop-page :is(.load-more-btn, .shop-load-more, .view-all-section-btn){
    width: 100% !important;
    max-width: none !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
  }

  /* ─── 51L · Note 13 · Blog headers sat on their content ──────────────
     Measured in `.main-content`: `.category-header-wrapper` → the grid
     below it is a 0px gap, and `.view-all-section-btn` → the next header
     is 0px too. The band's own border is then the only thing between a
     heading and the cards it labels. */
  .main-content > .category-header-wrapper{
    margin-top: 18px !important;
    margin-bottom: 14px !important;
  }
  .main-content > .category-header-wrapper:first-child{
    margin-top: 0 !important;
  }
  .main-content > .view-all-section-btn{
    margin-bottom: 4px !important;
  }

  /* ─── 51K · Note 12 · The blog's See More ────────────────────────────
     It is a 9px, 12px-padded, hairline button that reads as a disabled
     input rather than the platform's primary "more" control. The shop's
     load-more and the dashboard's are the reference: full-bleed, 44px,
     the house purple wash, 2px radius, 1.5px tracking. */
  .main-content > .view-all-section-btn{
    width: 100% !important;
    min-height: 44px !important;
    padding: 0 16px !important;
    border: 1px solid rgba(138, 43, 226, .22) !important;
    border-radius: 2px !important;
    background: linear-gradient(135deg,
      rgba(138, 43, 226, .14), rgba(138, 43, 226, .06)) !important;
    color: rgba(255, 255, 255, .78) !important;
    font-size: 10px !important;
    font-weight: 500 !important;
    letter-spacing: 1.5px !important;
    text-transform: uppercase !important;
  }
  .main-content > .view-all-section-btn:active{
    background: rgba(138, 43, 226, .2) !important;
  }

  /* ─── 51I · Note 10 · The latest response block ──────────────────────
     An 18px avatar, the name and the timestamp crammed on one line with
     the quote under them at 9px, inside a box with no internal rhythm.
     Rebuilt on the review card's proportions: a 22px chip, the name and
     the time as a two-track row so the time is always hard right, and
     the quote on its own line with a left rule marking it as quoted. */
  .main-content .blog-card-compact .recent-response{
    padding: 8px 9px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: rgba(138, 43, 226, .055) !important;
  }
  .main-content .blog-card-compact .response-header{
    display: grid !important;
    grid-template-columns: 22px minmax(0, 1fr) !important;
    align-items: center !important;
    gap: 7px !important;
    margin-bottom: 6px !important;
  }
  .main-content .blog-card-compact .response-avatar{
    width: 22px !important;
    height: 22px !important;
    border-radius: 50% !important;
    font-size: 8px !important;
    font-weight: 600 !important;
  }
  .main-content .blog-card-compact .response-info{
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) auto !important;
    align-items: baseline !important;
    gap: 6px !important;
    min-width: 0 !important;
  }
  .main-content .blog-card-compact .response-author{
    font-size: 9px !important;
    font-weight: 500 !important;
    letter-spacing: .3px !important;
    color: rgba(186, 85, 211, .8) !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    text-align: left !important;
  }
  .main-content .blog-card-compact .response-time{
    font-size: 8px !important;
    font-weight: 300 !important;
    letter-spacing: .3px !important;
    color: rgba(255, 255, 255, .3) !important;
    white-space: nowrap !important;
  }
  .main-content .blog-card-compact .response-text{
    padding-left: 7px !important;
    border-left: 2px solid rgba(186, 85, 211, .35) !important;
    font-size: 9px !important;
    font-style: italic !important;
    line-height: 1.45 !important;
    color: rgba(255, 255, 255, .46) !important;
    text-align: left !important;
    display: -webkit-box !important;
    -webkit-line-clamp: 2 !important;
    -webkit-box-orient: vertical !important;
    overflow: hidden !important;
  }
  /* The empty state is the same box with nothing to quote, so it loses
     the rule and the italic and reads as a prompt instead. */
  .main-content .blog-card-compact .recent-response-empty .response-text{
    padding-left: 0 !important;
    border-left: 0 !important;
    font-style: normal !important;
  }

  /* ─── 51J · Note 11 · Ghost cards fill the last row ──────────────────
     Same idea as the shop's `.product-card-ghost`: a dashed, hatched
     placeholder with no hover and no content. Every § 42 rule that paints
     a blog card has to be told to skip them, exactly as the shop's rules
     carry `:not(.product-card-ghost)` — otherwise a ghost inherits the
     hover sweep and an empty 52px response box. */
  .main-content .blog-card-ghost{
    pointer-events: none !important;
    cursor: default !important;
    border: 1px dashed rgba(138, 43, 226, .12) !important;
    border-radius: 2px !important;
    background: repeating-linear-gradient(135deg,
      rgba(138, 43, 226, .022) 0 10px, transparent 10px 20px) !important;
    box-shadow: none !important;
  }
  .main-content .blog-card-ghost::before,
  .main-content .blog-card-ghost::after,
  .main-content .blog-card-ghost > *{
    display: none !important;
  }

  /* ─── 51O · Note 1 · The guest links become a drawer ─────────────────
     2.29.0 put them inline under an "Explore without an account" heading.
     They are a drawer now, on the blog sidebar's pattern — toggle, scrim,
     off-canvas panel — because /account is the one storefront route that
     never had a sidebar and the links are navigation, not content. */
  .ng-guest-toggle{
    position: fixed !important;
    top: calc(var(--header-h-shrunk, 48px) + 9px) !important;
    left: 10px !important;
    z-index: 1200 !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    width: 36px !important;
    height: 36px !important;
    border: 1px solid rgba(138, 43, 226, .3) !important;
    border-radius: 2px !important;
    background: rgba(138, 43, 226, .8) !important;
    color: #fff !important;
    font-size: 13px !important;
  }
  .ng-guest-scrim{
    position: fixed !important;
    inset: 0 !important;
    z-index: 1190 !important;
    background: rgba(0, 0, 0, .55) !important;
    opacity: 0 !important;
    pointer-events: none !important;
    transition: opacity .3s ease !important;
  }
  .ng-guest-scrim.open{
    opacity: 1 !important;
    pointer-events: auto !important;
  }
  .ng-guest-nav{
    position: fixed !important;
    top: var(--header-h-shrunk, 48px) !important;
    left: -248px !important;
    width: 248px !important;
    height: calc(100vh - var(--header-h-shrunk, 48px)) !important;
    z-index: 1210 !important;
    margin: 0 !important;
    padding: 14px 12px !important;
    border: 0 !important;
    border-right: 1px solid var(--ng-line) !important;
    border-radius: 0 !important;
    background: #07060c !important;
    overflow-y: auto !important;
    overscroll-behavior: contain !important;
    transition: left .32s var(--ease-luxe, ease) !important;
  }
  .ng-guest-nav.open{ left: 0 !important; }
  body.ng-guest-open{ overflow: hidden !important; }
  .ng-guest-head{
    display: block !important;
    margin-bottom: 12px !important;
    padding-bottom: 10px !important;
    border-bottom: 1px solid var(--ng-line) !important;
    text-align: center !important;
  }
  /* One column in a 248px drawer, and the descriptions come back — there
     is room for them here, which there was not in the two-up page grid. */
  .ng-guest-grid{
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) !important;
    gap: 6px !important;
  }
  .ng-guest-link{
    grid-template-columns: 30px minmax(0, 1fr) auto !important;
  }
  .ng-guest-desc{
    display: block !important;
    margin-top: 2px !important;
    font-size: 8.5px !important;
    font-weight: 300 !important;
    letter-spacing: .2px !important;
    color: rgba(255, 255, 255, .38) !important;
    white-space: normal !important;
  }
  .ng-guest-caret{
    display: block !important;
    font-size: 8px !important;
    color: rgba(186, 85, 211, .45) !important;
  }
  .ng-guest-label{ white-space: normal !important; }
}

/* ═══════════════════════════════════════════════════════════════════════
   SECTION 51 (desktop half) — outside the mobile media query
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 51O-desktop · Note 1 · none of it exists above 768 ──────────────── */
@media (min-width: 769px){
  .ng-guest-toggle,
  .ng-guest-scrim,
  .ng-guest-nav{ display: none !important; }
}

/* ─── 51F · Note 2 · Recommended For You, one row and a real band ─────── */
@media (min-width: 769px){
  /* Superseded by 52A. This hid cards five and six so the dashboard's
     4-column grid made one row. Round eleven's note 1 asks the dashboard
     rail to match the shop's instead — six across, six cards — and 52A
     fixes the column count at the source, so the hide would now be
     throwing away two of the six it is meant to show. */

  /* The band. Every other dashboard section — Recent Orders, Recently
     Viewed — is a direct child of `.dashboard-content`, so its
     `.category-header-wrapper{margin:0 -32px}` cancels `.container`'s
     32px padding exactly and the band's hairlines run from the sidebar
     border to the container's right edge. The rail's header is two levels
     deeper, inside `.recommendations-full-width`, where a (0,2,0) rule
     overrides that to -30px — which cancels the RAIL's padding instead,
     so the band stops 32px short of each edge and reads as part of the
     card box. Measured at 1440: orders band x=342 w=976, rec band x=375
     w=910, container x=340 w=980.

     Rather than re-parent the header (which would break `DashRecs`), the
     rail is stripped of the padding and side borders that make it a box
     on this page, and the band gets the -32px every other one has. */
  .dashboard-content .recommendations-full-width{
    padding-left: 0 !important;
    padding-right: 0 !important;
    border-left: 0 !important;
    border-right: 0 !important;
    background: transparent !important;
  }
  .dashboard-content .recommendations-full-width .category-header-wrapper{
    margin-left: -32px !important;
    margin-right: -32px !important;
  }
  .dashboard-content .recommendations-full-width .category-header{
    padding-left: 32px !important;
    padding-right: 32px !important;
  }
}

/* ─── 51G · Note 3 · See More had nowhere to end ──────────────────────
   Measured at 1440: the orders See More's bottom edge is y=1049 and the
   Recently Viewed band's top edge is y=1049. Not an overlap — a zero gap,
   the same shape as 2.27.0's note 6 on the same page. The button is also
   370px of band against 914px of button, so it reads as landing on it. */
.dashboard-content > * + *,
.dashboard-content .orders-section + .recently-viewed-section{
  margin-top: 22px;
}
.dashboard-content .orders-section .view-more-btn{
  margin-bottom: 4px;
}

/* ─── 51H · Note 4 · Recently viewed is a grid ────────────────────────
   `DashCarousel` no longer emits the wrapper buttons or the scroll ref;
   these are the rules that made the track a scroller. Four across, which
   is what `.recommendations-grid` uses on this page, so the two sections
   read as one system. */
.dashboard-content .recently-viewed-carousel,
.dashboard-content .recently-viewed-wrapper{
  overflow: visible;
}
.dashboard-content .recently-viewed-track{
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  padding: 0;
  overflow: visible;
  scroll-snap-type: none;
}
.dashboard-content .recently-viewed-item{
  flex: initial;
  width: auto;
  min-width: 0;
  scroll-snap-align: none;
  opacity: 1;            /* the carousel dimmed every tile to .65 at rest */
}
.dashboard-content .recently-viewed-fade-left,
.dashboard-content .recently-viewed-fade-right,
.dashboard-content .carousel-btn{
  display: none;
}
/* "the same as recommended for you" is about the count as well as the
   shape: that block is one row of four (51F), and the endpoint returns
   eight here (account.php LIMIT 8). The phone already shows four, from
   round seven's 2x2. Four everywhere. */
.dashboard-content .recently-viewed-track > :nth-child(n+5){
  display: none;
}

/* ─── 51M · Note 14 · The blog sidebar scrolls as one ─────────────────
   § 41A made `.sidebar-list` the only scroller and gave it
   `overscroll-behavior: contain`. That is why you cannot scroll the whole
   aside: the header and the Home button are outside the scroller, the
   list gets `100vh − ~198px`, and once it bottoms out the contain stops
   the wheel from chaining anywhere. The aside is the scroller again, with
   the header sticky inside it — which is what 41A was trying to achieve
   by a different route, and the reason it used two scrollers was that
   `.sidebar-header{position:sticky;top:0}` resolves against the nearest
   scroll container, so it works correctly only when that container is the
   aside itself.

   `contain: layout paint` goes with it: it makes the aside a containing
   block for fixed descendants and clips painting, which fights the sticky
   header once the aside scrolls. */
.main-layout > .blog-sidebar{
  overflow-y: auto;
  overflow-x: hidden;
  contain: none;
  overscroll-behavior: contain;
}
.blog-sidebar .sidebar-list{
  overflow: visible;
  flex: 0 0 auto;
}
/* The other half of "it forces you back to the top": the aside animates
   `top`, `height` and `max-height` over 0.5s every time the header shrink
   threshold is crossed at scrollY 40, so the whole rail slides up and
   settles while you are reading it. The header can animate; the rail it
   is pinned under should not. */
.blog-sidebar{
  transition: left .4s var(--ease-luxe, ease) !important;
}
/* § 41F set `.sidebar-header{position:relative}` back when the LIST was
   the scroller and a sticky header had nothing useful to stick to. Now
   that the aside scrolls, relative is what lets the header scroll away
   with the rows — measured 267px out of view at the bottom of the rail. */
.blog-sidebar .sidebar-header{
  position: sticky !important;
  top: 0 !important;
  z-index: 2 !important;
  background: var(--surface, #000) !important;
}

/* ─── 51N · Note 15 · Article comments get the review card's design ───
   `ng-polish.css` had no `.comment*` rules at all — the article comment
   block is the one list on the site that never went through the polish
   layer, which is why it reads as a different product from the reviews
   next door. These are § 49K's review rules mapped onto the comment
   markup. The structures differ: `.review-item` is a block with the
   avatar inside `.review-header`, while `.comment` IS the row and the
   avatar is a direct child — so the card itself becomes the grid. */
.comments-section .comment{
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr);
  align-items: start;
  gap: 10px;
  padding: 12px 13px;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: linear-gradient(160deg, rgba(22, 13, 36, .5), rgba(8, 6, 14, .72));
  margin-bottom: 8px;
}
.comments-section .comment-avatar{
  width: 32px;
  height: 32px;
  font-size: 10px;
  font-weight: 600;
}
.comments-section .comment-top{
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 5px;
}
.comments-section .comment-author{
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .3px;
  color: rgba(255, 255, 255, .85);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.comments-section .comment-date{
  font-size: 9px;
  font-weight: 300;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .3);
  white-space: nowrap;
}
.comments-section .comment-text{
  font-size: 11.5px;
  font-weight: 300;
  line-height: 1.55;
  color: rgba(255, 255, 255, .62);
  margin-bottom: 9px;
}
/* The action row sits above a hairline, as the review card's does. */
.comments-section .comment-actions{
  gap: 6px;
  padding-top: 9px;
  border-top: 1px solid var(--ng-line);
}
.comments-section .comment-action{
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 30px;
  padding: 0 10px;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: rgba(255, 255, 255, .016);
  color: rgba(255, 255, 255, .46);
  font-size: 9px;
  letter-spacing: .5px;
  text-transform: uppercase;
}
.comments-section .comment-action:hover{
  border-color: rgba(186, 85, 211, .3);
  color: rgba(255, 255, 255, .72);
}
/* Replies are the review card's house-reply: indented, purple left rule,
   a tint, and a smaller chip. The two-column reply grid stays. */
.comments-section .ng-replies > .ng-replies-scroll > .comment{
  border-left: 2px solid rgba(186, 85, 211, .45);
  border-radius: 0 2px 2px 0;
  background: rgba(138, 43, 226, .06);
  grid-template-columns: 22px minmax(0, 1fr);
  gap: 8px;
  padding: 9px 11px;
}
.comments-section .ng-replies .comment-avatar{ width: 22px; height: 22px; font-size: 8px; }
.comments-section .ng-replies .comment-text{ font-size: 10.5px; margin-bottom: 0; }
.comments-section .ng-replies .comment-actions{ padding-top: 7px; }
.comments-section .ng-replies-toggle,
.comments-section .load-more-btn{
  min-height: 40px;
  border: 1px solid rgba(138, 43, 226, .22);
  border-radius: 2px;
  background: linear-gradient(135deg, rgba(138, 43, 226, .14), rgba(138, 43, 226, .06));
  color: rgba(255, 255, 255, .78);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.comments-section .comments-header{
  padding-bottom: 12px;
  border-bottom: 1px solid var(--ng-line);
}


/* ═══════════════════════════════════════════════════════════════════════
   SECTION 52 · Round eleven — nine notes
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 52A · Note 3 · The rail's columns, nailed down ──────────────────
   Reproduced at 1440px:

     /shop, fresh load        6 columns · 1 row · 180px cards
     /shop after /dashboard   4 columns · 2 rows · 276px cards

   `assets/storefront-DashboardRoute.css` declares `.recommendations-grid
   { grid-template-columns: repeat(4,1fr) }` with NO page scope, at the
   same (0,1,0) specificity as `storefront.css`'s `repeat(6,1fr)` and no
   `!important`. Route chunks are appended to <head> by the loader and
   never removed, so once a visitor has opened the dashboard that
   declaration is live on every page for the rest of the session, and
   source order hands it the win.

   Both halves of the complaint are the one cause: four cards stretched
   across a container sized for six are necessarily oversized, and the
   fifth and sixth necessarily wrap. There was never a second bug.

   ng-polish loads BEFORE the route chunks, so re-asserting at (0,1,0)
   would lose. `.recommendations-full-width .recommendations-grid` is
   (0,2,0) and beats any route chunk regardless of order. The whole
   responsive ladder is restated, because overriding only the base would
   leave the chunk's own breakpoints to fight at narrower widths.

   Every band is bounded at BOTH ends. The first draft wrote the base rule
   unscoped and the breakpoints as plain `max-width`, which is how the
   mobile rail — two columns since § 43, at (0,1,0) — lost to a (0,2,0)
   `!important` and went to three 107px columns at 390px. Below 769 this
   section says nothing. */
@media (min-width: 1201px){
  .recommendations-full-width .recommendations-grid{
    grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
  }
}
@media (min-width: 969px) and (max-width: 1200px){
  .recommendations-full-width .recommendations-grid{
    grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
  }
}
@media (min-width: 769px) and (max-width: 968px){
  .recommendations-full-width .recommendations-grid{
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
  }
}
/* The card height leaks the same way (both sheets say 280px, so it has
   never shown) — pinned for the same reason. */
@media (min-width: 769px){
  .recommendations-full-width .recommendation-card{ height: 280px !important; }
}

/* ─── 52B · Note 1 · The dashboard's two rails are the shop's rail ────
   "Size, layout, design, and product count." The recommendations block
   already IS `NgRecRail`, so with 52A holding the columns it now matches
   by construction — six across, 180px cards, one row. Recently viewed is
   a different component with its own card markup, so it is brought to the
   same measurements here rather than rebuilt. */
@media (min-width: 769px){
  .dashboard-content .recently-viewed-track{
    grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
    gap: 12px !important;
  }
  /* Six, like the rail — the endpoint returns eight. 51H capped this at
     four to match the old 4-column rec grid; the target moved. */
  .dashboard-content .recently-viewed-track > :nth-child(n+5){
    display: block;
  }
  .dashboard-content .recently-viewed-track > :nth-child(n+7){
    display: none;
  }
  .dashboard-content .recently-viewed-item{
    height: 280px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
  }
}
@media (max-width: 1200px) and (min-width: 769px){
  .dashboard-content .recently-viewed-track{
    grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
  }
  .dashboard-content .recently-viewed-track > :nth-child(n+5){ display: none; }
}

/* ─── 52E · Note 5 · Size guide and FBT get the real header ───────────
   Both panels label themselves with a bare `<h4>` — 9.5px, left aligned,
   a hairline under it. Everywhere else on the site a section is labelled
   by `.category-header`: the purple gradient band, the blended border
   image, the shimmering title and the hairline that travels around it.
   The markup cannot change here without touching `product.jsx`, which is
   stale against its shipped twin (the guest-review invite exists only in
   the built file), so the `<h4>` is given the band's appearance instead —
   same gradient, same border-image, same two animations, same centring. */
.product-page :is(.size-guide, .frequently-bought) > h4{
  justify-content: center !important;
  position: relative;
  overflow: visible;
  margin: 0 !important;
  padding: 13px 18px !important;
  background: linear-gradient(135deg,
    rgba(26, 15, 42, .98), rgba(36, 21, 57, .97), rgba(26, 15, 42, .98)) !important;
  border-top: 1px solid !important;
  border-bottom: 1px solid !important;
  border-left: none !important;
  border-right: none !important;
  border-image: var(--border-blend) 1 !important;
  font-size: 13px !important;
  font-weight: 500 !important;
  letter-spacing: 2.5px !important;
  text-transform: uppercase !important;
  /* `.category-header-text` gets its shimmer from `background-clip:text`,
     which can only paint ONE background — and on this element that is the
     band itself. So the heading keeps the band and the title takes the
     glow as a text-shadow instead; the travelling hairline below is the
     animation that actually reads at this size. */
  color: rgba(255, 255, 255, .92) !important;
  text-shadow: 0 0 10px rgba(186, 85, 211, .25);
}
.product-page :is(.size-guide, .frequently-bought) > h4 i{
  color: var(--purple-light, #ba55d3) !important;
  font-size: 12px !important;
  filter: drop-shadow(0 0 6px rgba(186, 85, 211, .4));
}
/* The travelling hairline, lifted from `.category-header::before`. */
.product-page :is(.size-guide, .frequently-bought) > h4::before{
  content: "";
  position: absolute;
  width: 15%;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(138, 43, 226, 1), transparent);
  border-radius: 1px;
  z-index: 3;
  pointer-events: none;
  will-change: transform, opacity;
  animation: circularPulse 19.6s linear infinite;
}

/* ─── 52D · Note 4 · Frequently Bought Together ───────────────────────
   `.product-sidebar` is a `1fr 1fr` row BELOW the gallery/details layout,
   holding the size guide on the left and FBT on the right — not, despite
   the name, a column beside anything. When a product has no size chart
   the guide is not rendered at all, so FBT is the only child of a
   two-column grid and sits at half width with the other half empty. That
   is the space the note is about, and it varies by product, which is why
   it has to adapt rather than be set. */
@media (min-width: 769px){
  .product-page .product-sidebar:has(> .frequently-bought:only-child),
  .product-page .product-sidebar:has(> .size-guide:only-child){
    grid-template-columns: minmax(0, 1fr) !important;
  }
  /* Four cards across when the panel is full width, two when it is
     sharing the row — the cards stay the same size either way. */
  .product-page .product-sidebar > .frequently-bought:only-child .compact-recommendations{
    grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
  }

  /* The cards themselves, on the rail's design rather than the 100px
     letterbox they were: the same 2px border, the same hairline, the same
     gradient scrim and centred caption, at the rail's proportions. */
  .product-page .frequently-bought .compact-recommendations{
    gap: 26px 26px !important;
    padding: 18px !important;
    align-content: start !important;
  }
  .product-page .frequently-bought .compact-rec-card{
    /* 54B gives these the shop card's 280px; 52D's 190 was for the old
       letterbox card and is superseded. */
    height: 280px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: linear-gradient(135deg, #070709, #0b0a0f) !important;
  }
  .product-page .frequently-bought .compact-rec-title{
    font-size: 11px !important;
    letter-spacing: .3px !important;
  }
  .product-page .frequently-bought .compact-rec-price{
    font-size: 12px !important;
  }

  /* The "+" that makes it read as a combination.
     One glyph per gap, drawn on the card to the LEFT of each gap — so
     every card except the last in a row carries one, and the last column
     never does. It is `pointer-events:none` so the cards underneath stay
     separately clickable, which is the whole point of the block. */
  .product-page .frequently-bought .compact-rec-card{
    position: relative !important;
    overflow: visible !important;
  }
  .product-page .frequently-bought .compact-rec-card::before{
    content: "+";
    position: absolute;
    top: 50%;
    right: -13px;
    width: 20px;
    height: 20px;
    transform: translate(50%, -50%);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 4;
    border: 1px solid rgba(186, 85, 211, .35);
    border-radius: 50%;
    background: #0a070f;
    color: rgba(186, 85, 211, .9);
    font-size: 11px;
    font-weight: 500;
    line-height: 1;
    pointer-events: none;
  }
  /* No "+" hanging off the end of a row or off the last card. The panel
     is 2-up when it shares the sidebar row and 4-up when it is alone. */
  .product-page .frequently-bought .compact-rec-card:nth-child(2n)::before{ content: none; }
  .product-page .product-sidebar > .frequently-bought:only-child .compact-rec-card:nth-child(2n)::before{ content: "+"; }
  .product-page .product-sidebar > .frequently-bought:only-child .compact-rec-card:nth-child(4n)::before{ content: none; }
  .product-page .frequently-bought .compact-rec-card:last-child::before{ content: none; }
  /* The scrim is drawn by `::after`; the image must stay clipped to the
     card even though the card now overflows for the "+". */
  .product-page .frequently-bought .compact-rec-card .compact-rec-image{
    border-radius: 2px;
  }
}

/* ─── 52F · Note 6 · The cart's remove button ─────────────────────────
   `color: #ffffff25` — 15% white — on an 8px uppercase label with 4px of
   padding, no border and no background. § 43 already called this out and
   rewrote it, but the rewrite was written inside `@media (max-width:
   768px)`, so the desktop button is still the original. This is that
   block, unscoped. */
.ci-remove{
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  gap: 6px !important;
  min-height: 34px !important;
  padding: 0 12px !important;
  border: 1px solid var(--ng-line) !important;
  border-radius: 2px !important;
  background: #0e0e10 !important;
  color: rgba(255, 255, 255, .62) !important;
  transition: color .3s ease, border-color .3s ease, background .3s ease !important;
}
.ci-remove span{
  font-size: 9px !important;
  font-weight: 500 !important;
  letter-spacing: 1.4px !important;
  text-transform: uppercase !important;
}
.ci-remove i{ font-size: 10px !important; }
.ci-remove:hover,
.ci-remove:active,
.ci-remove:focus-visible{
  color: #ff6b6b !important;
  border-color: rgba(255, 107, 107, .45) !important;
  background: rgba(255, 59, 48, .08) !important;
}

/* ─── 52G · Note 8 · "Worn this?" is centred on the desktop too ───────
   § 30 builds the invite as a left-aligned column; § 49J centred it, but
   only inside the mobile query. */
@media (min-width: 769px){
  .ng-review-invite{
    align-items: center !important;
    text-align: center !important;
  }
  .ng-review-invite .ng-invite-copy{ text-align: center !important; }
  .ng-review-invite-form,
  .ng-review-invite .ng-invite-fields{ text-align: left; }
}

/* ─── 52H · Note 9 · Desktop reviews take the mobile design ───────────
   § 49K rebuilt the review card in 2.28.0 and every one of its selectors
   sits inside `@media (max-width: 768px)`, so the desktop card is still
   `storefront.css`'s: a 38px gradient circle, a flex header with the
   rating absolutely positioned in the corner, 20px of padding, and no
   divider above the actions.

   Same structure as the mobile card — a three-track header grid with the
   rating as a flow item — at desktop scale: 38px chip, 12px body, four
   clamped lines instead of three, 36px action chips. */
@media (min-width: 769px){
  .product-page .review-item{
    padding: 16px 17px !important;
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: linear-gradient(135deg, #070709, #0b0a0f) !important;
  }
  .product-page .review-item:hover{ border-color: rgba(186, 85, 211, .32) !important; }
  .product-page .review-item .review-header{
    display: grid !important;
    grid-template-columns: auto minmax(0, 1fr) auto !important;
    align-items: center !important;
    gap: 12px !important;
    margin-bottom: 12px !important;
  }
  .product-page .review-item .reviewer-avatar{
    width: 38px !important;
    height: 38px !important;
    flex: 0 0 38px !important;
    border-radius: 2px !important;
    border: 1px solid var(--ng-line) !important;
    background: linear-gradient(135deg, rgba(138, 43, 226, .3), rgba(186, 85, 211, .16)) !important;
    font-size: 12px !important;
    align-self: center !important;
  }
  .product-page .review-item .reviewer-info{ min-width: 0 !important; }
  .product-page .review-item .reviewer-name{
    font-size: 12.5px !important;
    font-weight: 500 !important;
    letter-spacing: .4px !important;
    color: rgba(255, 255, 255, .92) !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
  }
  .product-page .review-item .review-date{
    font-size: 9.5px !important;
    letter-spacing: .4px !important;
    text-transform: uppercase !important;
    color: rgba(255, 255, 255, .34) !important;
  }
  /* out of the corner, into the row */
  .product-page .review-item .review-rating{
    position: static !important;
    display: flex !important;
    gap: 2px !important;
    justify-self: end !important;
  }
  .product-page .review-item .review-content{
    font-size: 12px !important;
    line-height: 1.6 !important;
    color: rgba(255, 255, 255, .62) !important;
    margin-bottom: 12px !important;
  }
  .product-page .review-item .review-content strong{ color: rgba(255, 255, 255, .88) !important; }
  .product-page .review-item .ng-review-reply{
    margin-top: 10px !important;
    padding: 9px 11px !important;
    border-left: 2px solid rgba(186, 85, 211, .45) !important;
    border-radius: 0 2px 2px 0 !important;
    background: rgba(138, 43, 226, .06) !important;
    font-size: 11px !important;
    line-height: 1.55 !important;
  }
  .product-page .review-item .review-actions{
    gap: 7px !important;
    padding-top: 11px !important;
    border-top: 1px solid var(--ng-line) !important;
  }
  .product-page .review-item .review-action-btn{
    padding: 0 12px !important;
    min-height: 36px !important;
    height: 36px !important;
    border-radius: 2px !important;
    border: 1px solid var(--ng-line) !important;
    background: rgba(255, 255, 255, .016) !important;
    font-size: 9.5px !important;
    letter-spacing: .5px !important;
  }
}

/* ─── 52I · Note 7 · The empty wishlist ───────────────────────────────
   The block that replaces the old one-line "your wishlist is empty".
   The rail below it is the platform rail, so it needs no styling here —
   only the header above it. */
.ng-wl-empty-head{
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 8px;
  padding: 26px 18px 22px;
}
.ng-wl-empty-icon{
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border: 1px solid rgba(186, 85, 211, .3);
  border-radius: 50%;
  background: rgba(138, 43, 226, .12);
  color: var(--purple-light, #ba55d3);
  font-size: 15px;
}
.ng-wl-empty-title{
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .88);
}
.ng-wl-empty-copy{
  margin: 0;
  max-width: 440px;
  font-size: 11.5px;
  font-weight: 300;
  line-height: 1.6;
  color: rgba(255, 255, 255, .5);
}
@media (max-width: 768px){
  .ng-wl-empty-head{ padding: 20px 14px 16px; }
  .ng-wl-empty-icon{ width: 36px; height: 36px; font-size: 13px; }
  .ng-wl-empty-title{ font-size: 11.5px; letter-spacing: 1.6px; }
  .ng-wl-empty-copy{ font-size: 10.5px; }
}


/* ═══════════════════════════════════════════════════════════════════════
   SECTION 53 · Round twelve — sixteen notes
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 53J · Notes 10 + 13 · The 22px lands on something painted ───────
   Same 22px, fifth revision, and this one is a correction rather than a
   new idea. § 51A moved the clearance onto the page wrapper's padding,
   which is right when the wrapper paints itself — `.container` is #000 —
   and wrong everywhere else. On /blog, /shop, /product and /search the
   painted box is `.main-content`; `.main-layout` and `.shop-page` are
   transparent, so their padding showed `--ng-m-ground` #0e0c12 plus the
   stardust texture between two black boxes.

   On /shop that band sat between the listing and the rail — and § 45 had
   deliberately removed the listing's bottom border so the two would read
   as one container, which made the seam worse, not better: the box did
   not end, it just stopped. */
@media (max-width: 768px){
  .main-layout > .main-content{
    padding-bottom: 22px !important;
  }
  /* …and where a rail follows the listing, there is nothing to clear:
     they are meant to be one box. § 45 already joins their borders. */
  :is(.shop-page, .product-page):has(> .recommendations-full-width) .main-layout > .main-content{
    padding-bottom: 0 !important;
  }
}

/* ─── 53A · Note 1 · The slack under the Claim button ─────────────────
   `.product-layout` is `1fr 1fr` with no `align-items`, so both tracks
   stretch to the row height — and the row height is set by the gallery,
   which is fixed: 440px image + 10px gap + 70px thumbnails = 520px. The
   details column is content-driven and normally lands at 390–430px, so
   90–130px of empty track opens under `.action-buttons`, and neither
   column has anything that grows into it.

   The gallery becomes the elastic one instead of the fixed one: the image
   takes the slack, so the row is as tall as the details column and there
   is none left over. A floor keeps it sane on a short product. */
@media (min-width: 769px){
  /* First attempt gave the gallery `height:100%` and the image
     `height:auto`. That made it worse: a percentage height on a grid item
     in an auto-sized row resolves against the row, the image's own height
     became indefinite, and the gallery ended up DRIVING the row to 522px
     against 384px of details — 138px of slack instead of 90.

     Two changes that each do one thing. The image comes down to 360px, so
     the gallery's natural height is 360 + 10 + 70 = 440 rather than 520 —
     and it keeps `flex: 1 1 auto`, so on a product whose details run
     longer than 440 the grid stretches the gallery and the image takes
     the extra rather than leaving a hole under the thumbnails. Then the
     details column spreads what is left instead of stacking it all at the
     top, which is where the gap under the Claim button came from. */
  .product-page .main-image-container{
    flex: 1 1 auto !important;
    height: 360px !important;
    min-height: 0 !important;
  }
  .product-page .image-gallery-thumbs{
    flex: 0 0 auto !important;
  }
  .product-page .product-details{
    justify-content: space-between !important;
  }
}

/* ─── 53B · Note 2 · The review action row ────────────────────────────
   Two problems. The row's `border-top` was inset by the card's padding,
   so it floated with a gap at each end instead of meeting the card's own
   border — it has to be pulled out by exactly the padding it sits in.
   And the row is `justify-content: space-between` over a left group and a
   report button, so the gap between the two changes with the width of the
   counts: every card spaced differently. */
.product-page .review-item .review-actions{
  display: flex !important;
  align-items: center !important;
  justify-content: flex-start !important;
  gap: 6px !important;
  margin-top: auto !important;
}
/* The left group stops being a group: its children join the row, so one
   gap governs the whole bar and every card measures the same. */
.product-page .review-item .review-actions-left{
  display: contents !important;
}
/* The flag is the only thing on the right, on every card and every
   width. `margin-left:auto` does that without `space-between`'s
   dependence on how many siblings there happen to be. */
.product-page .review-item .review-actions > .review-report-btn,
.product-page .review-item .review-actions > .ng-report-select{
  margin-left: auto !important;
}
@media (min-width: 769px){
  /* pull the rule out to the card's edges: 17px of padding each side */
  .product-page .review-item .review-actions{
    margin-left: -17px !important;
    margin-right: -17px !important;
    padding: 11px 17px 0 !important;
    width: auto !important;
  }
}
@media (max-width: 768px){
  .product-page .review-item .review-actions{
    margin-left: -14px !important;
    margin-right: -14px !important;
    padding: 9px 14px 0 !important;
    width: auto !important;
  }
}
/* The card is a column so the action bar can be pushed to the bottom —
   which is what makes every card's bar line up. */
.product-page .review-item:not(.ng-review-invite):not(.ng-review-empty){
  display: flex !important;
  flex-direction: column !important;
}

/* ─── 53C · Note 3 · A placeholder when the row is short ──────────────
   The grid is two columns above 968. An odd number of cards — which is
   every product with one review, and every page where the invite lands
   alone on a new row — left a hole. Same idea as the shop's
   `.product-card-ghost` and the blog's `.blog-card-ghost`. */
.product-page .reviews-grid .review-item-ghost{
  pointer-events: none;
  border: 1px dashed rgba(138, 43, 226, .12);
  border-radius: 2px;
  background: repeating-linear-gradient(135deg,
    rgba(138, 43, 226, .022) 0 10px, transparent 10px 20px);
  box-shadow: none;
  min-height: 120px;
}
@media (max-width: 968px){
  /* one column — a ghost would be a whole empty row */
  .product-page .reviews-grid .review-item-ghost{ display: none; }
}

/* ─── 53D · Note 4 · The stars were a 12px target ─────────────────────
   The handlers were always wired — `onClick: () => setRating(n)` on each
   of the five — and they do fire. What they are attached to is a 12×12
   div with no padding: `.product-page .review-rating .star{width:12px;
   height:12px}`, and the editor carries `review-rating` as well as
   `ng-rating-input`, so it inherits the read-only size. A click a few
   pixels off lands on the container and does nothing, which is exactly
   what "clicking them does nothing" looks like.

   That same inheritance also kills the feedback: `.product-page
   .review-rating .star:hover{transform:none}` was written for the
   read-only display and switches off the 1.2x scale on the editor too, so
   there is no hover response to tell you the target is live.

   The glyph stays 12px; the hit area becomes 34px with padding, and the
   hover and active states come back. */
.ng-rating-input{
  display: inline-flex !important;
  align-items: center !important;
  gap: 2px !important;
  margin: 2px 0 !important;
}
.product-page .ng-rating-input .star,
.ng-rating-input .star{
  width: 34px !important;
  height: 34px !important;
  padding: 11px !important;
  box-sizing: border-box !important;
  cursor: pointer !important;
  border-radius: 2px !important;
  transition: transform .18s ease, background .18s ease !important;
}
/* `::before` is the star shape and is `inset:0` — with padding on the box
   it has to be inset by the padding too, or the glyph fills the target. */
.product-page .ng-rating-input .star::before,
.ng-rating-input .star::before{
  inset: 11px !important;
}
.product-page .ng-rating-input .star:hover,
.ng-rating-input .star:hover{
  transform: scale(1.12) !important;
  background: rgba(138, 43, 226, .1) !important;
}
.product-page .ng-rating-input .star:active,
.ng-rating-input .star:active{ transform: scale(.96) !important; }
.product-page .ng-rating-input .star:focus-visible,
.ng-rating-input .star:focus-visible{
  outline: 1px solid rgba(186, 85, 211, .7) !important;
  outline-offset: -1px !important;
}

/* ─── 53E · Note 11 · "or sign in to your account" ────────────────────
   It was a 10px underlined text link sitting under a 9.5px uppercase
   bordered button — two different species of control stacked. It becomes
   the button's quieter twin: same height, same radius, same tracking,
   hollow instead of filled. */
.ng-review-invite .ng-invite-alt{
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  align-self: stretch !important;
  min-height: 38px !important;
  padding: 0 16px !important;
  border: 1px solid var(--ng-line) !important;
  border-bottom: 1px solid var(--ng-line) !important;
  border-radius: 2px !important;
  background: rgba(255, 255, 255, .015) !important;
  color: rgba(255, 255, 255, .55) !important;
  font-size: 9.5px !important;
  font-weight: 500 !important;
  letter-spacing: 2px !important;
  text-transform: uppercase !important;
  text-decoration: none !important;
}
.ng-review-invite .ng-invite-alt:hover{
  border-color: rgba(186, 85, 211, .4) !important;
  color: rgba(255, 255, 255, .8) !important;
  background: rgba(138, 43, 226, .08) !important;
}
.ng-review-invite .ng-invite-btn{
  min-height: 38px !important;
  border-radius: 2px !important;
}

/* ─── 53G · Note 7 · The dashboard sidebar and the shrinking header ───
   Every other rail on the site follows the header down when it shrinks:
   the shop's at `storefront.css` @125131, the product's @107364, the
   blog's at § 41A line 5834 — all `body:has(header.header-shrunk) …
   { top: var(--header-h-shrunk) }`. `.dashboard-sidebar` never got one,
   so it stays pinned at `top: var(--header-h)` (80px on this route, from
   the route chunk's own `:root`) while the header goes to 56px, leaving a
   24px band of page showing above it for the whole of every scroll.

   This is the same symptom the § 41A comment describes for the blog
   before it was fixed. The transition is here too — the header animates
   its height over .5s and the rail should track it, not jump. */
.dashboard-sidebar{
  transition: top .5s var(--ease-luxe, ease),
              height .5s var(--ease-luxe, ease),
              max-height .5s var(--ease-luxe, ease),
              left .4s var(--ease-luxe, ease);
}
body:has(header.header-shrunk) .dashboard-sidebar{
  top: var(--header-h-shrunk);
  height: calc(100vh - var(--header-h-shrunk));
  max-height: calc(100vh - var(--header-h-shrunk));
}

/* ─── 53H · Note 8 · The sidebar group headings ───────────────────────
   Three headings, two with a sprite tile and one with an empty 22px slot
   where a tile would be. The result reads as a mistake rather than a
   choice: a line of 9px purple text at 60% opacity, floating, with a
   ragged left edge between the tiled and untiled ones.

   Given a rule to sit on they read as section dividers. The tile keeps
   its slot; the heading gets a hairline that runs to the end of the rail,
   and the text gets enough weight to hold its own against it. */
.dashboard-nav-list .nav-group-title{
  position: relative;
  margin: 10px 0 2px;
  padding: 0 18px 7px !important;
  opacity: 1 !important;
  font-size: 8.5px !important;
  font-weight: 600 !important;
  letter-spacing: 1.8px !important;
  color: rgba(186, 85, 211, .72) !important;
  border-bottom: 1px solid var(--ng-line);
}
.dashboard-nav-list .nav-group:first-child .nav-group-title,
.dashboard-nav-list > li:first-child .nav-group-title{
  margin-top: 2px;
}
/* The tile-less heading keeps the 22px slot so every label starts on the
   same x — but the slot gets a mark of its own instead of being blank. */
.dashboard-nav-list .nav-group-title.no-spr::before{
  content: "" !important;
  flex: 0 0 22px !important;
  height: 1px !important;
  margin-right: 8px !important;
  background: linear-gradient(90deg,
    rgba(186, 85, 211, .55), rgba(186, 85, 211, .08)) !important;
}

/* ─── 53I · Note 9 · The blog card's latest response ──────────────────
   § 51I rebuilt this on the review card's proportions and it is tidier
   than it was, but it still reads as a box bolted to the bottom of the
   card: a full border around a quote that is already inside a bordered
   card, and an avatar chip competing with the card's own imagery.

   It is a quotation now rather than a panel — no outer border, a tinted
   ground, the speaker's initials small and inline with the name, and the
   purple rule carrying the whole block rather than just the text. Same on
   both breakpoints; the note asks for both. */
.main-content .blog-card-compact .recent-response,
.ng-related-grid .recent-response{
  padding: 8px 10px !important;
  border: 0 !important;
  border-left: 2px solid rgba(186, 85, 211, .4) !important;
  border-radius: 0 2px 2px 0 !important;
  background: rgba(138, 43, 226, .05) !important;
}
.main-content .blog-card-compact .response-header{
  display: flex !important;
  align-items: center !important;
  gap: 6px !important;
  margin-bottom: 5px !important;
}
.main-content .blog-card-compact .response-avatar{
  width: 18px !important;
  height: 18px !important;
  flex: 0 0 18px !important;
  border-radius: 50% !important;
  font-size: 7px !important;
  font-weight: 600 !important;
  letter-spacing: 0 !important;
}
.main-content .blog-card-compact .response-info{
  display: flex !important;
  align-items: baseline !important;
  gap: 6px !important;
  min-width: 0 !important;
  flex: 1 1 auto !important;
}
.main-content .blog-card-compact .response-author{
  flex: 0 1 auto !important;
  font-size: 9px !important;
  font-weight: 500 !important;
  color: rgba(255, 255, 255, .72) !important;
  white-space: nowrap !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
}
.main-content .blog-card-compact .response-time{
  flex: 0 0 auto !important;
  margin-left: auto !important;
  font-size: 8px !important;
  font-weight: 300 !important;
  color: rgba(255, 255, 255, .28) !important;
}
.main-content .blog-card-compact .response-text{
  padding-left: 0 !important;
  border-left: 0 !important;
  font-size: 9.5px !important;
  font-style: normal !important;
  line-height: 1.5 !important;
  color: rgba(255, 255, 255, .5) !important;
}
.main-content .blog-card-compact .recent-response-empty{
  border-left-color: rgba(255, 255, 255, .12) !important;
  background: rgba(255, 255, 255, .012) !important;
}

/* ─── 53K · Note 14 · Article comments, as a feed ─────────────────────
   § 51N gave each comment the review card's box — border, radius, panel
   ground. That was right for a product review, which is a standalone
   artefact you compare against its neighbours. A discussion is not that:
   a column of identical bordered boxes reads as a table of records, not
   as people talking.

   So the boxes come off. Comments are separated by a hairline the way a
   timeline is, the avatar is round again, the name and the timestamp sit
   on one line with the name carrying the weight, and the actions are
   quiet text affordances rather than pills. Replies keep the purple rule
   that marks them as nested. Both breakpoints. */
.comments-section .comment{
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  align-items: start;
  gap: 11px;
  margin: 0;
  padding: 14px 2px;
  border: 0;
  border-bottom: 1px solid var(--ng-line);
  border-radius: 0;
  background: transparent;
}
.comments-section .comment:hover{ background: transparent; }
.comments-section > .comment:last-of-type{ border-bottom: 0; }
.comments-section .comment-avatar{
  width: 34px;
  height: 34px;
  border-radius: 50%;
  font-size: 11px;
  font-weight: 600;
}
.comments-section .comment-top{
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-bottom: 4px;
}
.comments-section .comment-author{
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .2px;
  color: rgba(255, 255, 255, .9);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* the timestamp reads as metadata beside the name, not as a right-hand
   column — the feed convention */
.comments-section .comment-date{
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 300;
  letter-spacing: 0;
  text-transform: none;
  /* .3 measured too faint against #0e0c12 at 10px; .38 is what the review
     card's date sits at and it reads without competing with the name. */
  color: rgba(255, 255, 255, .38);
}
.comments-section .comment-date::before{
  content: "·";
  margin-right: 7px;
  color: rgba(255, 255, 255, .22);
}
.comments-section .comment-text{
  font-size: 12.5px;
  font-weight: 300;
  line-height: 1.6;
  color: rgba(255, 255, 255, .72);
  margin-bottom: 7px;
}
.comments-section .comment-actions{
  display: flex;
  gap: 18px;
  padding-top: 0;
  border-top: 0;
}
.comments-section .comment-action{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 26px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: rgba(255, 255, 255, .38);
  font-size: 10.5px;
  font-weight: 400;
  letter-spacing: .2px;
  text-transform: none;
  transition: color .25s ease;
}
.comments-section .comment-action i{ font-size: 11px; }
.comments-section .comment-action:hover{
  border: 0;
  color: var(--purple-light, #ba55d3);
}
/* Replies: the nesting is carried by a rule and an indent, not a card. */
.comments-section .ng-replies > .ng-replies-scroll > .comment{
  border: 0;
  border-left: 2px solid rgba(186, 85, 211, .3);
  border-radius: 0;
  background: transparent;
  grid-template-columns: 26px minmax(0, 1fr);
  gap: 9px;
  padding: 10px 0 10px 11px;
}
.comments-section .ng-replies .comment-avatar{ width: 26px; height: 26px; font-size: 9px; }
.comments-section .ng-replies .comment-author{ font-size: 11px; }
.comments-section .ng-replies .comment-text{ font-size: 11.5px; margin-bottom: 5px; }
.comments-section .ng-replies .comment-actions{ padding-top: 0; gap: 16px; }
.comments-section .ng-reply-add{
  border: 1px dashed rgba(138, 43, 226, .2) !important;
  border-left: 2px dashed rgba(138, 43, 226, .3) !important;
  background: transparent !important;
}
.comments-section .comments-title{
  font-size: 13px;
  letter-spacing: 1.8px;
}
.comments-section .comments-count-label{
  font-size: 10.5px;
  color: rgba(255, 255, 255, .34);
}
@media (max-width: 768px){
  .comments-section .comment{ grid-template-columns: 30px minmax(0, 1fr); gap: 9px; padding: 12px 2px; }
  .comments-section .comment-avatar{ width: 30px; height: 30px; font-size: 10px; }
  .comments-section .comment-author{ font-size: 11.5px; }
  .comments-section .comment-text{ font-size: 12px; }
  .comments-section .comment-actions{ gap: 16px; }
}

/* ─── 53L · Note 15 · Related cards are the index's cards ─────────────
   § 42 already writes every card rule twice — once for
   `.main-content .blog-card-compact` and once for
   `.ng-related-grid .related-card` — so the shell, the mask, the veil and
   the type are identical by construction. Three things still differed:

     · the index card has `min-height: 380px` from its route sheet; the
       thread sheet does not carry that rule at all
     · the index card always draws a `.recent-response` box, which takes
       the slack; related articles have no response payload, so § 42
       redirects the slack to the stat row and the card ends shorter
     · the thread grid is 4 columns against the index's 3, so the cards
       are narrower as well as shorter

   All three, matched. */
.ng-related-grid{
  grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
}
.ng-related-grid .related-card{
  min-height: 380px !important;
}
@media (max-width: 1024px){
  .ng-related-grid{ grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .ng-related-grid > .related-card:nth-child(4){ display: block !important; }
}
@media (max-width: 768px){
  .ng-related-grid{ grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .ng-related-grid .related-card{ min-height: 310px !important; }
}

/* ─── 53M · Note 16 · One hamburger on the account page ───────────────
   `ng-mobile.js` appends its own `#ngDrawerToggle.sidebar-toggle` to
   <body> on any page that has a drawer, never removes it, and stands down
   only for a `.sidebar-toggle` it did not create. `.ng-guest-toggle` is
   not one, so on /account — reached by SPA navigation from a page that
   does have a drawer — both appeared, and the one in the header slot was
   the dead one. The guard is fixed in `ng-mobile.js`; this is the belt to
   its braces, because that button is `display:none`d inline and two
   `!important` rules used to beat the inline style.

   And the survivor takes the platform's toggle design: the same 33x44
   transparent hit box in the header slot that the shop and blog use,
   rather than a purple square floating 57px down the left edge. */
@media (max-width: 768px){
  body:has(.ng-guest-toggle) #ngDrawerToggle,
  body:has(.ng-guest-toggle) .ng-m-drawer-btn{
    display: none !important;
  }
  .ng-guest-toggle{
    top: 2px !important;
    left: 20px !important;
    width: 33px !important;
    height: 44px !important;
    z-index: 1001 !important;
    border: none !important;
    border-radius: 0 !important;
    background: none !important;
    color: rgba(255, 255, 255, .9) !important;
    font-size: 17px !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   SECTION 54 · Round thirteen — sixteen notes
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 54A · Note 1 · Size guide and FBT, header off the box ───────────
   § 52E gave both panels the purple band, but the band is the panel's
   first child, so it reads as the box's lid rather than as the section
   label every other band on the site is. On mobile the band should run
   to the container's edges like the others, then the content sits in its
   own box below it with air in between. */
@media (max-width: 768px){
  .product-page :is(.size-guide, .frequently-bought){
    border: 0 !important;
    border-radius: 0 !important;
    background: none !important;
    overflow: visible !important;
    padding: 0 !important;
  }
  /* the band breaks the page gutter, as `.category-header-wrapper` does */
  .product-page :is(.size-guide, .frequently-bought) > h4{
    margin: 0 -14px 12px !important;
    padding: 13px 18px !important;
  }
  /* and the content becomes the box */
  .product-page .frequently-bought .compact-recommendations,
  .product-page .size-guide .size-chart{
    border: 1px solid var(--ng-line) !important;
    border-radius: 2px !important;
    background: linear-gradient(160deg, rgba(22, 13, 36, .45), rgba(8, 6, 14, .7)) !important;
  }
  .product-page .frequently-bought .compact-recommendations{
    padding: 10px !important;
    margin-bottom: 18px !important;
  }
  .product-page .size-guide .size-chart{
    margin-bottom: 18px !important;
    overflow: hidden !important;
  }
}

/* ─── 54B · Note 3 · FBT cards are shop product cards ─────────────────
   The markup is the shop card's now — same classes, same children, same
   handlers. What is left is scope: almost every polish rule for the card
   is written `.shop-page .product-card`, which does not follow the card
   onto the product page. These are the ones that have to travel. */
.product-page .frequently-bought .product-card{
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: linear-gradient(135deg, #070709, #0b0a0f);
  cursor: pointer;
  transition: border-color .3s ease, transform .3s ease;
}
.product-page .frequently-bought .product-card:hover{
  border-color: rgba(186, 85, 211, .4);
}
.product-page .frequently-bought .product-image-container{
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}
.product-page .frequently-bought .product-main-image{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.product-page .frequently-bought .product-details{
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 9px 10px 10px;
}
.product-page .frequently-bought .product-title{
  font-size: 11px;
  font-weight: 400;
  line-height: 1.3;
  color: rgba(255, 255, 255, .84);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.product-page .frequently-bought .product-pricing{
  display: flex;
  align-items: baseline;
  gap: 7px;
}
.product-page .frequently-bought .current-price{
  font-size: 12px;
  font-weight: 600;
  color: var(--purple-light, #ba55d3);
}
.product-page .frequently-bought .original-price{
  font-size: 10px;
  color: rgba(255, 255, 255, .3);
  text-decoration: line-through;
}
.product-page .frequently-bought .product-colors{ display: flex; gap: 4px; }
.product-page .frequently-bought .color-dot{
  width: 8px; height: 8px; border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .14);
}
.product-page .frequently-bought .arrival-add-btn{
  min-height: 30px;
  border: 1px solid rgba(138, 43, 226, .3);
  border-radius: 2px;
  background: rgba(138, 43, 226, .12);
  color: rgba(255, 255, 255, .85);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  cursor: pointer;
}
.product-page .frequently-bought .arrival-add-btn.added{
  border-color: rgba(120, 200, 140, .4);
  color: rgba(160, 220, 180, .9);
}
.product-page .frequently-bought .favorite-btn{
  position: absolute;
  top: 7px; right: 7px;
  z-index: 3;
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  border: 1px solid var(--ng-line);
  border-radius: 50%;
  background: rgba(8, 6, 12, .72);
  color: rgba(255, 255, 255, .55);
  font-size: 10px;
  cursor: pointer;
}
.product-page .frequently-bought .favorite-btn.active{ color: #ba55d3; }
.product-page .frequently-bought :is(.tier-tag, .product-badge){
  position: absolute;
  top: 7px; left: 7px;
  z-index: 3;
  padding: 2px 6px;
  border-radius: 2px;
  background: rgba(138, 43, 226, .85);
  color: #fff;
  font-size: 7px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
}
/* The same height the shop card uses, at both breakpoints. */
@media (min-width: 769px){
  .product-page .frequently-bought .product-card{ height: 280px; }
  .product-page .frequently-bought .compact-recommendations{
    gap: 26px 26px;
  }
}
@media (max-width: 768px){
  .product-page .frequently-bought .product-card{ height: 270px !important; }
  .product-page .frequently-bought .compact-recommendations{
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
  }
  /* the "+" connector is desktop-only and would collide at this size */
  .product-page .frequently-bought .product-card::before{ content: none !important; }
}

/* ─── 54C · Note 4 · The related-article stat chips ───────────────────
   `.blog-stat-item` is styled in exactly one place — `storefront-
   BlogRoute.css` — and the thread route loads `storefront-ThreadRoute`
   chunks only. So on a hard refresh of an article the four chips render
   as unstyled block divs at the card's inherited size: white text, no
   pill, and wide enough to wrap onto two rows. Arriving from /blog they
   look right, because that sheet is already in <head> and never removed.
   That is the "bland white text on hard refresh".

   The rules move into ng-polish, which is a plain <link> in index.html
   and therefore always present. `nowrap` so four chips stay on one row,
   and the row loses its bottom margin so it does not leave a gap at the
   card's floor. */
:is(.main-content, .ng-related-grid) .blog-stat-item{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  padding: 3px 7px;
  border: 1px solid rgba(138, 43, 226, .14);
  border-radius: 2px;
  background: rgba(138, 43, 226, .07);
  font-size: 8.5px;
  font-weight: 400;
  letter-spacing: .2px;
  color: rgba(255, 255, 255, .42);
  white-space: nowrap;
}
:is(.main-content, .ng-related-grid) .blog-stats-compact i{
  font-size: 8px;
  color: rgba(186, 85, 211, .6);
}
:is(.main-content, .ng-related-grid) .blog-stats-compact{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  flex-wrap: nowrap;
  overflow: hidden;
}
.ng-related-grid .blog-stats-compact{
  margin-top: auto;
  margin-bottom: 0;
  padding-top: 2px;
}

/* ─── 54D · Notes 4 + 6 · The byline and stats come back on mobile ────
   § 48A hides `.blog-author` and `.blog-stats-compact` at ≤768, and
   § 49F restores only the excerpt and the reply — its comment says the
   omission was deliberate. Round thirteen asks for the desktop
   arrangement instead: author hard left, date hard right, stats centred
   under the summary. */
@media (max-width: 768px){
  .main-content .blog-card-compact .blog-author,
  .main-content .blog-card-compact .blog-stats-compact,
  .ng-related-grid .related-card .blog-author,
  .ng-related-grid .related-card .blog-stats-compact{
    display: flex !important;
  }
  .main-content .blog-card-compact .blog-author,
  .ng-related-grid .related-card .blog-author{
    display: inline-flex !important;
    align-items: center !important;
    gap: 4px !important;
    min-width: 0 !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    white-space: nowrap !important;
    font-size: 8.5px !important;
    letter-spacing: .2px !important;
  }
  .main-content .blog-card-compact .blog-date,
  .ng-related-grid .related-card .blog-date{
    flex: 0 0 auto !important;
    font-size: 8.5px !important;
    letter-spacing: .2px !important;
  }
  /* § 50E centred this row when the author was hidden and the date was
     its only child. With both back it is a two-ended row again. */
  .main-content .blog-card-compact .blog-author-date,
  .main-content .blog-card-compact:first-child .blog-author-date,
  .ng-related-grid .related-card .blog-author-date{
    display: flex !important;
    justify-content: space-between !important;
    align-items: center !important;
    gap: 8px !important;
    margin: 0 0 6px !important;
  }
  :is(.main-content, .ng-related-grid) .blog-stat-item{
    padding: 2px 5px !important;
    font-size: 7.5px !important;
  }
  :is(.main-content, .ng-related-grid) .blog-stats-compact{
    gap: 4px !important;
    margin: 0 0 6px !important;
  }
  /* the related card has no reply block, so the stats are its floor */
  .ng-related-grid .related-card .blog-stats-compact{
    margin-top: auto !important;
    margin-bottom: 0 !important;
  }
}

/* ─── 54E · Note 8 · The blog ghost was invisible, not absent ─────────
   Measured: `blCols()` returns 2 at 390px, the grid is 2 columns, nine
   posts produce exactly one ghost, and the renderer emits it at full
   height. It simply could not be seen — a 1px dashed border at
   `rgba(138,43,226,.12)` over a `.022`-alpha hatch, on a #050505 ground,
   is under the perceptual floor on a phone. */
.main-content .blog-card-ghost{
  border: 1px dashed rgba(138, 43, 226, .3) !important;
  background: repeating-linear-gradient(135deg,
    rgba(138, 43, 226, .055) 0 10px, transparent 10px 20px) !important;
}
.product-page .reviews-grid .review-item-ghost{
  border-color: rgba(138, 43, 226, .3);
  background: repeating-linear-gradient(135deg,
    rgba(138, 43, 226, .055) 0 10px, transparent 10px 20px);
}

/* ─── 54F · Note 10 · Trending This Week needs air ────────────────────
   § 51L gives `.main-content > .category-header-wrapper` 18px above and
   14px below. The second band on the blog follows a See More button that
   has its own margin collapsed away, so it reads as tighter than the
   first. */
@media (max-width: 768px){
  .main-content > .view-all-section-btn + .category-header-wrapper{
    margin-top: 26px !important;
    margin-bottom: 18px !important;
  }
  .main-content > .category-header-wrapper + .trending-sidebar{
    margin-top: 4px !important;
  }
}

/* ─── 54G · Note 11 · "Your Bag" is centred on mobile ─────────────────
   `storefront-CartRoute.css` flattens the band at ≤768 and leaves
   `justify-content:flex-start`, so the title row and its subtitle sit
   left while every other page's band is centred. */
@media (max-width: 768px){
  /* `.ch` is the bag/checkout band and exists nowhere else, so it needs
     no page scope — and there is no `.cart-page` class on this route to
     scope it with, which is what the first attempt assumed. */
  .ch{
    justify-content: center !important;
  }
  .ch-inner{
    align-items: center !important;
    text-align: center !important;
  }
  .ch-title-row{ justify-content: center !important; }
  .ch-sub{ text-align: center !important; }
}

/* ─── 54H · Note 12 · The bag's rail on desktop ───────────────────────
   Three things. The rail carries `border-left/right` from
   `storefront.css`, which draws a second pair of rails 32px inside the
   container's own — a box inside a box. Its header band is padded to
   `0 30px` so it stops short of the container edge instead of meeting
   it. And `.cart-layout` has no bottom margin while the rail has no top
   one, so the band butts straight onto the order-summary column. */
@media (min-width: 769px){
  /* Scoped by what the bag actually has — a `.cart-layout` — rather than
     by a `.cart-page` class, which this route does not carry. */
  .container:has(> .cart-layout) > .recommendations-full-width,
  .container:has(> .checkout-section) > .recommendations-full-width{
    margin-top: 30px !important;
    border-left: 0 !important;
    border-right: 0 !important;
    background: none !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  /* the band breaks the container's 32px padding, like every other one */
  .container:has(> .cart-layout) > .recommendations-full-width .category-header-wrapper,
  .container:has(> .checkout-section) > .recommendations-full-width .category-header-wrapper{
    margin-left: -32px !important;
    margin-right: -32px !important;
  }
  .container:has(> .cart-layout) > .recommendations-full-width .category-header,
  .container:has(> .checkout-section) > .recommendations-full-width .category-header{
    padding-left: 32px !important;
    padding-right: 32px !important;
  }
}

/* ─── 54I · superseded by section 55 ─────────────────────────────────
   The first consent sheet was a single Allow / Not now pair. Round
   fourteen replaces it with a three-category panel that meets what
   regulators actually enforce, and its styles live in § 55A. */

/* ─── 54J · Note 14 · The search results page ─────────────────────────
   It shares no card classes with the shop at all — `.search-product-card`
   and its `.card-*` children are a separate design. Rather than rewrite
   the route, the search classes are given the shop's visual system: the
   same header band proportions, the same filter row, and a card that is
   the shop's card in everything but class name. */
.search-page .search-header{
  margin: 0 0 18px;
  padding: 18px 0 16px;
  border-bottom: 1px solid var(--ng-line);
  text-align: center;
}
.search-page .search-query-label{
  display: block;
  font-size: 9px; font-weight: 500; letter-spacing: 2px;
  text-transform: uppercase; color: rgba(186, 85, 211, .6);
}
.search-page .search-query-text{
  margin: 6px 0 0;
  font-size: 20px; font-weight: 300; letter-spacing: 2px;
  text-transform: uppercase; color: rgba(255, 255, 255, .92);
}
.search-page .search-meta{
  display: flex; align-items: center; justify-content: center;
  gap: 18px; margin-top: 10px; flex-wrap: wrap;
}
.search-page .search-meta-item{
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 9.5px; font-weight: 300; letter-spacing: .6px;
  text-transform: uppercase; color: rgba(255, 255, 255, .4);
}
.search-page .search-meta-item i{ font-size: 9px; color: rgba(186, 85, 211, .6); }
.search-page .search-meta-value{ color: rgba(255, 255, 255, .78); font-weight: 500; }

/* the filter row, on the shop's chip grid proportions */
.search-page .search-filter-bar{
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
  margin: 0 0 18px; padding: 10px 12px;
  border: 1px solid var(--ng-line); border-radius: 2px;
  background: rgba(255, 255, 255, .012);
}
.search-page .search-filter-left{ display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.search-page .search-filter-group{ display: inline-flex; align-items: center; gap: 7px; }
.search-page .search-filter-label{
  font-size: 8.5px; font-weight: 500; letter-spacing: 1.4px;
  text-transform: uppercase; color: rgba(255, 255, 255, .34);
}
.search-page .search-filter-dropdown{
  min-height: 32px; padding: 0 26px 0 10px;
  border: 1px solid var(--ng-line); border-radius: 2px;
  background: rgba(138, 43, 226, .06);
  color: rgba(255, 255, 255, .78);
  font-size: 9.5px; font-weight: 500; letter-spacing: 1px;
  text-transform: uppercase; cursor: pointer;
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, rgba(186,85,211,.7) 50%),
                    linear-gradient(135deg, rgba(186,85,211,.7) 50%, transparent 50%);
  background-position: calc(100% - 14px) 14px, calc(100% - 9px) 14px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.search-page .search-filter-dropdown:hover{ border-color: rgba(186, 85, 211, .35); }
.search-page .search-result-count{
  font-size: 9px; font-weight: 400; letter-spacing: 1.2px;
  text-transform: uppercase; color: rgba(255, 255, 255, .34);
}

/* the card, as the shop's */
.search-page .search-product-card{
  position: relative;
  display: flex; flex-direction: column; overflow: hidden;
  border: 1px solid var(--ng-line); border-radius: 2px;
  background: linear-gradient(135deg, #070709, #0b0a0f);
  cursor: pointer;
  transition: border-color .3s ease;
}
.search-page .search-product-card:hover{ border-color: rgba(186, 85, 211, .4); }
.search-page .card-img-wrap{ position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; }
.search-page .card-img-wrap img{ width: 100%; height: 100%; object-fit: cover; display: block; }
.search-page .card-info{
  flex: 0 0 auto; display: flex; flex-direction: column; gap: 5px;
  padding: 9px 10px 10px;
}
.search-page .card-title{
  font-size: 11px; font-weight: 400; line-height: 1.3;
  color: rgba(255, 255, 255, .84);
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
.search-page .card-pricing{ display: flex; align-items: baseline; gap: 7px; }
.search-page .card-price{ font-size: 12px; font-weight: 600; color: var(--purple-light, #ba55d3); }
.search-page .card-original{ font-size: 10px; color: rgba(255, 255, 255, .3); text-decoration: line-through; }
.search-page .card-add-btn{
  min-height: 30px;
  border: 1px solid rgba(138, 43, 226, .3); border-radius: 2px;
  background: rgba(138, 43, 226, .12);
  color: rgba(255, 255, 255, .85);
  font-size: 9px; font-weight: 500; letter-spacing: 1.4px;
  text-transform: uppercase; cursor: pointer;
}
.search-page .card-fav{
  position: absolute; top: 7px; right: 7px; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  border: 1px solid var(--ng-line); border-radius: 50%;
  background: rgba(8, 6, 12, .72);
  color: rgba(255, 255, 255, .55); font-size: 10px; cursor: pointer;
}
.search-page .card-badge{
  position: absolute; top: 7px; left: 7px; z-index: 3;
  padding: 2px 6px; border-radius: 2px;
  background: rgba(138, 43, 226, .85); color: #fff;
  font-size: 7px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase;
}
.search-page .card-relevance{
  position: absolute; left: 0; bottom: 0; height: 2px; z-index: 4;
  background: linear-gradient(90deg, rgba(138, 43, 226, .9), rgba(186, 85, 211, .5));
}
.search-page .search-products-grid{ gap: 12px; }
@media (min-width: 769px){
  .search-page .search-product-card{ height: 280px; }
}
@media (max-width: 768px){
  .search-page .search-product-card{ height: 270px !important; }
  .search-page .search-products-grid{
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
  }
  .search-page .search-header{ padding: 14px 0 12px; }
  .search-page .search-query-text{ font-size: 15px; letter-spacing: 1.4px; }
  .search-page .search-filter-bar{ padding: 9px 10px; gap: 10px; }
  .search-page .search-filter-left{ gap: 10px; width: 100%; }
  .search-page .search-filter-group{ flex: 1 1 auto; }
  .search-page .search-filter-dropdown{ width: 100%; }
  .search-page .search-filter-right{ width: 100%; text-align: center; }
}
/* the empty state, and its fallback rail */
.search-page .search-empty{ text-align: center; padding: 34px 16px 26px; }
.search-page .search-empty-icon{
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; margin-bottom: 12px;
  border: 1px solid rgba(186, 85, 211, .28); border-radius: 50%;
  background: rgba(138, 43, 226, .1);
  color: var(--purple-light, #ba55d3); font-size: 16px;
}
.search-page .search-empty-title{
  display: block; font-size: 14px; font-weight: 500; letter-spacing: 2px;
  text-transform: uppercase; color: rgba(255, 255, 255, .88);
}
.search-page .search-empty-sub{
  display: block; margin-top: 7px; font-size: 11px; font-weight: 300;
  color: rgba(255, 255, 255, .45);
}
.search-page .search-empty-suggestions{ margin-top: 24px; text-align: left; }
.search-page .search-empty-suggestions h4{
  margin: 0 0 12px; padding-bottom: 9px;
  border-bottom: 1px solid var(--ng-line);
  font-size: 10px; font-weight: 500; letter-spacing: 2px;
  text-transform: uppercase; color: rgba(255, 255, 255, .7);
  text-align: center;
}

/* ─── 54K · Note 16 · The loyalty line was clipped, not hidden ────────
   There is no `display:none` on it anywhere. § 47 adds
   `overflow:hidden` to `.dashboard-sidebar .sidebar-header`, and on a
   flex item a non-visible overflow drops the automatic minimum size to
   zero — so the header, which has no `flex-shrink:0`, is free to shrink
   below its content inside a `calc(100vh - header)` column, and the
   loyalty block is its last child. At ≤768 the avatar and padding are
   smaller, the content fits, and it survives. That is why it looked
   mobile-only.

   A floor on the header is the whole fix; the bottom padding is note
   16's second half. */
.dashboard-sidebar .sidebar-header{
  flex: 0 0 auto !important;
  padding-bottom: 20px !important;
}
.dashboard-sidebar .loyalty-progress{
  margin-top: 12px !important;
  margin-bottom: 2px !important;
}
.dashboard-sidebar .loyalty-text{
  line-height: 1.45 !important;
}

/* ─── 54L · Note 7 · The account toggle matches the others ────────────
   § 53M got the box right (2px / 20px / 33×44) but missed four things
   § 39B carries: `padding:0` (the button keeps the UA's `1px 6px`, which
   squeezes a 33px box's content area to 21px), `margin:0`, a `min-width`
   floor, and `line-height:1` on the glyph — § 39B puts the 17px on the
   `<i>`, § 53M put it on the button, so the icon inherits `normal` and
   sits about 1.7px low against the shop's. */
@media (max-width: 768px){
  .ng-guest-toggle{
    margin: 0 !important;
    padding: 0 !important;
    min-width: 33px !important;
    right: auto !important;
    bottom: auto !important;
    display: inline-flex !important;
    font-size: 0 !important;       /* the glyph carries its own size */
  }
  /* `storefront.css` sets `.shop-page .sidebar-toggle i{font-size:14px
     !important}` at (0,2,1), which beats § 39B's 17px — so the shop's
     glyph is 14px and the blog's is 17px, and the account one could only
     ever match one of them. All three are stated here, at the same
     specificity as the shop's rule but later in the cascade. */
  .shop-page .sidebar-toggle i,
  .product-page .sidebar-toggle i,
  .ng-guest-toggle i{
    font-size: 17px !important;
    line-height: 1 !important;
    color: rgba(255, 255, 255, .9) !important;
  }
  .ng-guest-toggle[aria-expanded="true"] i{ color: var(--purple-light) !important; }
}

/* ─── 54M · Note 7b · The guest drawer is the platform's sidebar ──────
   Same panel, same header band, same row rhythm as the shop and blog
   rails, so the account page stops being the one route with a sidebar of
   its own design. Sprite tiles go in when the sheet arrives; the 22px
   slot is reserved now so adding them is a background swap. */
@media (max-width: 768px){
  .ng-guest-nav{
    width: min(280px, 82vw) !important;
    left: calc(-1 * min(280px, 82vw)) !important;
    padding: 0 !important;
    background: var(--surface, #000) !important;
    border-right: 1px solid var(--border-purple, rgba(138, 43, 226, .25)) !important;
  }
  .ng-guest-nav.open{ left: 0 !important; }
  .ng-guest-head{
    margin: 0 !important;
    padding: 16px 16px 15px !important;
    border-bottom: 1px solid !important;
    border-image: var(--border-blend) 1 !important;
    background: linear-gradient(135deg, rgba(14, 8, 22, .96), rgba(8, 5, 14, .98)) !important;
    text-align: center !important;
  }
  .ng-guest-head-title{
    font-size: 11px !important; font-weight: 500 !important;
    letter-spacing: 1.5px !important; color: #fff !important;
  }
  .ng-guest-head-sub{
    font-size: 9px !important; font-weight: 300 !important;
    letter-spacing: 1px !important; color: rgba(255, 255, 255, .31) !important;
  }
  .ng-guest-grid{
    display: block !important;
    padding: 6px 0 14px !important;
  }
  .ng-guest-link{
    display: flex !important;
    align-items: center !important;
    gap: 0 !important;
    min-height: 46px !important;
    padding: 9px 16px !important;
    border: 0 !important;
    border-left: 2px solid transparent !important;
    border-radius: 0 !important;
    background: none !important;
  }
  .ng-guest-link:active{
    background: rgba(138, 43, 226, .08) !important;
    border-left-color: rgba(186, 85, 211, .5) !important;
  }
  /* the 22px tile slot the shop and blog rails use */
  .ng-guest-chip{
    flex: 0 0 22px !important;
    width: 22px !important; height: 22px !important;
    margin-right: 10px !important;
    border: 0 !important;
    border-radius: 0 !important;
    background: none !important;
  }
  .ng-guest-chip i{ font-size: 11px !important; color: rgba(186, 85, 211, .55) !important; }
  .ng-guest-label{
    font-size: 10px !important; font-weight: 400 !important;
    letter-spacing: .5px !important; text-transform: none !important;
    color: rgba(255, 255, 255, .44) !important;
  }
  .ng-guest-desc{ display: none !important; }
  .ng-guest-caret{ margin-left: auto !important; font-size: 8px !important; }
}

/* ─── 54N · Note 15 · Back to the shop from a staff account ───────────
   `.ng-panel-link` (2.27.0) takes a staff member from the shop dashboard
   into the employee panel. The return trip is this one, added to the
   same Staff group so the pair reads as a switch. */
.dashboard-nav-list .ng-shop-link i{ color: rgba(186, 85, 211, .6); }

/* ─── 54O · Note 9 · The index's View More buttons ────────────────────
   On the desktop these are a light purple wash at 2px; on the phone § 43
   left them on a near-black ground with a hairline, which reads as
   disabled next to their desktop selves. Same palette, same radius. */
@media (max-width: 768px){
  .container :is(.view-more-btn, .compact-view-more-btn, .view-all-section-btn){
    border: 1px solid rgba(138, 43, 226, .26) !important;
    border-radius: 2px !important;
    background: linear-gradient(135deg,
      rgba(138, 43, 226, .16), rgba(138, 43, 226, .07)) !important;
    color: rgba(255, 255, 255, .8) !important;
  }
  .container :is(.view-more-btn, .compact-view-more-btn, .view-all-section-btn):active{
    background: rgba(138, 43, 226, .24) !important;
  }
}


/* ─── 54P · Note 5 · The thread's comments, finished ──────────────────
   § 53K made them a feed; this is what was still wrong with them.

   · Replies were laid out as a TWO-COLUMN GRID
     (`storefront-ThreadRoute.css`: `.ng-replies-scroll{grid-template-
     columns:repeat(2,minmax(0,1fr))}`), so a conversation read
     left-to-right-then-down. Two people answering the same comment
     appeared side by side as though they were unrelated.
   · Two replies showed before any interaction, and the odd cell was
     filled by a dashed "Add to this thread" button whose only job was to
     square off that grid.
   · The toggle only appeared from the third reply onward, so a comment
     with one or two replies had no way to be collapsed at all.

   A single column, nothing open until asked, and the count on the
   toggle — which is the convention the note names. The filler button is
   gone from the markup; the grid it existed for no longer exists. */
.comments-section .ng-replies-scroll{
  display: block !important;
  grid-template-columns: none !important;
  max-height: none !important;
  overflow: visible !important;
  padding-right: 0 !important;
}
/* Closed means closed — the old rule only hid from the third onward. */
.comments-section .ng-replies:not(.is-open):not([data-expanded]) > .ng-replies-scroll{
  display: none !important;
}
.comments-section .ng-replies{ margin-top: 0 !important; }
.comments-section .ng-replies > .ng-replies-scroll > .comment{
  margin: 0 !important;
  padding: 10px 0 10px 12px !important;
  border-left: 2px solid rgba(186, 85, 211, .22) !important;
}
.comments-section .ng-replies > .ng-replies-scroll > .comment + .comment{
  border-top: 1px solid var(--ng-line) !important;
}
/* The toggle is a line of text with a short rule before it, not a
   button-shaped object — Instagram's "View 3 replies". */
.comments-section .ng-replies-toggle{
  display: inline-flex !important;
  align-items: center !important;
  gap: 10px !important;
  min-height: 0 !important;
  margin: 8px 0 2px !important;
  padding: 4px 0 !important;
  border: 0 !important;
  border-radius: 0 !important;
  background: none !important;
  color: rgba(255, 255, 255, .42) !important;
  font-size: 11px !important;
  font-weight: 500 !important;
  letter-spacing: .2px !important;
  text-transform: none !important;
  cursor: pointer;
}
.comments-section .ng-replies-toggle:hover{ color: var(--purple-light, #ba55d3) !important; }
.comments-section .ng-replies-rule{
  display: block;
  width: 24px;
  height: 1px;
  background: rgba(255, 255, 255, .18);
}
.comments-section .ng-replies-toggle:hover .ng-replies-rule{
  background: rgba(186, 85, 211, .5);
}
/* the filler is gone from the markup; make sure no copy survives in a
   cached chunk */
.comments-section .ng-reply-add{ display: none !important; }

/* The action row reads as two affordances rather than two loose numbers:
   the count sits against its icon, and "Reply" carries the weight. */
.comments-section .comment-actions{ align-items: center !important; }
.comments-section .comment-action{ font-variant-numeric: tabular-nums; }
.comments-section .comment-action i{ opacity: .75; }
.comments-section .comment-action:last-child{
  font-weight: 500;
  color: rgba(255, 255, 255, .5);
}
/* The composer belongs to the same system as the comments it posts. */
.comments-section .ng-comment-form{
  margin-bottom: 18px !important;
  padding-bottom: 16px !important;
  border-bottom: 1px solid var(--ng-line) !important;
}
.comments-section .ng-cf-note{
  margin-top: 10px !important;
  padding: 10px 12px !important;
  border: 1px solid var(--ng-line) !important;
  border-radius: 2px !important;
  background: rgba(138, 43, 226, .05) !important;
}
@media (max-width: 768px){
  .comments-section .ng-replies > .ng-replies-scroll > .comment{
    padding-left: 10px !important;
  }
  .comments-section .ng-replies-toggle{ font-size: 10.5px !important; }
}


/* ═══════════════════════════════════════════════════════════════════════
   SECTION 55 · Round fourteen
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 55A · The consent sheet and its preferences panel ───────────────
   Built to what regulators now enforce rather than to what is easy:

     · Reject and Accept are the SAME control — same size, same weight,
       same colour. Rejecting is one click, exactly like accepting. A
       quieter "Manage choices" sits below them, which is allowed; what
       is not allowed is hiding Reject behind it.
     · Nothing is pre-ticked. Both optional categories start off, and the
       only switch that is on is the locked one.
     · The sheet does not block the page. The preferences panel does,
       because it is a real dialog — scrim, focus trap, ESC.
     · "Cookie settings" lives in the footer forever, and withdrawing
       costs the same two clicks as granting. */
.ng-consent{
  position: fixed;
  left: 50%;
  bottom: 0;
  z-index: 2000;
  width: min(560px, calc(100vw - 20px));
  transform: translate(-50%, 130%);
  opacity: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: calc(var(--ng-m-tabbar, 64px) + env(safe-area-inset-bottom, 0px) + 12px);
  padding: 16px 16px 14px;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: linear-gradient(160deg, rgba(24, 14, 38, .98), rgba(8, 6, 14, .99));
  box-shadow: 0 18px 50px rgba(0, 0, 0, .62);
  transition: transform .32s var(--ease-luxe, cubic-bezier(.19,1,.22,1)), opacity .26s ease;
}
.ng-consent.open{ transform: translate(-50%, 0); opacity: 1; }
.ng-consent-copy{ display: flex; flex-direction: column; gap: 6px; }
.ng-consent-title{
  font-size: 11px; font-weight: 600; letter-spacing: 1.8px;
  text-transform: uppercase; color: rgba(255, 255, 255, .92);
}
.ng-consent-text{
  font-size: 11px; font-weight: 300; line-height: 1.62;
  color: rgba(255, 255, 255, .56);
}
.ng-consent-actions{ display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.ng-consent-panel .ng-consent-actions{ grid-template-columns: repeat(3, 1fr); }
/* identical treatment — this is the part that must not be a dark pattern */
.ng-consent-btn{
  min-height: 40px;
  padding: 0 12px;
  border: 1px solid rgba(186, 85, 211, .4);
  border-radius: 2px;
  background: rgba(138, 43, 226, .16);
  color: rgba(255, 255, 255, .9);
  font-size: 9.5px; font-weight: 600; letter-spacing: 1.8px;
  text-transform: uppercase; cursor: pointer;
  transition: background .22s ease, border-color .22s ease;
}
.ng-consent-btn:hover{ background: rgba(138, 43, 226, .26); }
.ng-consent-btn:focus-visible{ outline: 1px solid rgba(186,85,211,.8); outline-offset: 1px; }
.ng-consent-link{
  align-self: center;
  padding: 2px 4px;
  border: 0;
  background: none;
  color: rgba(255, 255, 255, .42);
  font-size: 10px; letter-spacing: .4px;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.ng-consent-link:hover{ color: var(--purple-light, #ba55d3); }

.ng-consent-scrim{
  position: fixed; inset: 0; z-index: 2100;
  background: rgba(0, 0, 0, .62);
  opacity: 0; pointer-events: none;
  transition: opacity .26s ease;
}
.ng-consent-scrim.open{ opacity: 1; pointer-events: auto; }
body.ng-consent-locked{ overflow: hidden; }
.ng-consent-panel{
  position: fixed;
  left: 50%; top: 50%;
  z-index: 2110;
  width: min(520px, calc(100vw - 24px));
  max-height: min(78vh, 620px);
  overflow-y: auto;
  transform: translate(-50%, -46%);
  opacity: 0;
  display: flex; flex-direction: column; gap: 16px;
  padding: 20px 18px 18px;
  border: 1px solid var(--ng-line);
  border-radius: 2px;
  background: linear-gradient(160deg, rgba(24, 14, 38, .99), rgba(8, 6, 14, 1));
  box-shadow: 0 24px 70px rgba(0, 0, 0, .7);
  transition: transform .28s var(--ease-luxe, cubic-bezier(.19,1,.22,1)), opacity .24s ease;
}
.ng-consent-panel.open{ transform: translate(-50%, -50%); opacity: 1; }
.ng-consent-panel-head{ display: flex; flex-direction: column; gap: 5px; }
.ng-consent-panel-title{
  font-size: 12px; font-weight: 600; letter-spacing: 2px;
  text-transform: uppercase; color: rgba(255, 255, 255, .92);
}
.ng-consent-panel-sub{
  font-size: 10.5px; font-weight: 300; color: rgba(255, 255, 255, .45);
}
.ng-consent-cats{ display: flex; flex-direction: column; gap: 2px; }
.ng-consent-cat{
  display: grid;
  grid-template-columns: minmax(0, 1fr) 42px;
  align-items: center;
  gap: 14px;
  padding: 13px 2px;
  border-bottom: 1px solid var(--ng-line);
  cursor: pointer;
}
.ng-consent-cat:last-child{ border-bottom: 0; }
.ng-consent-cat.locked{ cursor: default; opacity: .72; }
.ng-consent-cat-copy{ display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ng-consent-cat-name{
  font-size: 11px; font-weight: 500; letter-spacing: .4px;
  color: rgba(255, 255, 255, .88);
}
.ng-consent-cat-desc{
  font-size: 10px; font-weight: 300; line-height: 1.55;
  color: rgba(255, 255, 255, .44);
}
.ng-consent-switch{ position: relative; width: 42px; height: 22px; flex: 0 0 42px; }
.ng-consent-input{
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer; z-index: 2;
}
.ng-consent-input:disabled{ cursor: default; }
.ng-consent-track{
  position: absolute; inset: 0;
  border: 1px solid var(--ng-line);
  border-radius: 11px;
  background: rgba(255, 255, 255, .04);
  transition: background .22s ease, border-color .22s ease;
}
.ng-consent-track::after{
  content: "";
  position: absolute; top: 3px; left: 3px;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .4);
  transition: transform .22s var(--ease-luxe, ease), background .22s ease;
}
.ng-consent-input:checked + .ng-consent-track{
  border-color: rgba(186, 85, 211, .55);
  background: rgba(138, 43, 226, .26);
}
.ng-consent-input:checked + .ng-consent-track::after{
  transform: translateX(20px);
  background: var(--purple-light, #ba55d3);
}
.ng-consent-input:focus-visible + .ng-consent-track{
  outline: 1px solid rgba(186, 85, 211, .8); outline-offset: 2px;
}
.ng-cookie-settings{
  display: inline-block;
  margin: 10px auto 0;
  padding: 4px 6px;
  border: 0; background: none;
  color: rgba(255, 255, 255, .34);
  font-size: 9.5px; letter-spacing: 1px; text-transform: uppercase;
  text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer;
}
.ng-cookie-settings:hover{ color: var(--purple-light, #ba55d3); }
@media (min-width: 769px){
  .ng-consent{
    left: 24px; right: auto;
    width: min(440px, calc(100vw - 48px));
    transform: translate(0, 130%);
    margin-bottom: 24px;
  }
  .ng-consent.open{ transform: translate(0, 0); }
}
@media (prefers-reduced-motion: reduce){
  .ng-consent, .ng-consent-panel, .ng-consent-scrim{ transition: opacity .18s ease; }
  .ng-consent{ transform: translate(-50%, 0); }
  .ng-consent-panel{ transform: translate(-50%, -50%); }
  @media (min-width: 769px){ .ng-consent{ transform: translate(0, 0); } }
}

/* ─── 55B · Note 5 · The container meets the chrome ───────────────────
   REVISED in 2.35.0. The first pass read "connect smoothly to the header
   and footer" as "run edge to edge", took the 9px side gutter away and
   made the container full-bleed. That was wrong: the gutter is what lets
   the body's texture show down both sides of the column, which is the
   look on the desktop and the look the client wants on the phone. The
   note was only ever about the TOP and BOTTOM — a rounded corner and a
   second border drawn against the header's own bottom line and the
   footer's top line, which is what made a seam.

   So the side gutters are back exactly as they were, and only the
   vertical seam fix stays: square corners, no top border, no bottom
   border. The container's two side rules now run straight up into the
   header's line and straight down into the footer's. */
@media (max-width: 768px){
  .main-layout > .main-content,
  .main-layout,
  .container,
  .shop-page > .recommendations-full-width,
  .product-page > .recommendations-full-width{
    border-radius: 0 !important;
  }
  .main-layout > .main-content,
  .container{
    border-top: 0 !important;
    border-bottom: 0 !important;
  }
}

/* ─── 55C · Note 1 · The shop rail's subtitle ─────────────────────────
   The rail's band is centred everywhere else; on the shop at ≤768 the
   subtitle was still inheriting the left-aligned variant. */
@media (max-width: 768px){
  .shop-page .recommendations-full-width .category-header{
    justify-content: center !important;
  }
  .shop-page .recommendations-full-width .category-header .category-header-content{
    align-items: center !important;
  }
  .shop-page .recommendations-full-width .category-header-subtitle{
    text-align: center !important;
  }
}

/* ─── 55D · Note 2 · The shop's See More radius ───────────────────────
   2px is the platform's only radius; this one was still rounded. */
@media (max-width: 768px){
  .shop-page :is(.load-more-btn, .shop-load-more, .view-all-section-btn),
  .shop-page .load-more > button{
    border-radius: 2px !important;
  }
}

/* ─── 55E · Note 3 · Trending This Week reaches the container ─────────
   § 51L gives `.main-content > .category-header-wrapper` its margins but
   leaves it inside the content box's padding, so the band stops short of
   the left and right borders. Every other band on the site breaks that
   padding; this one should too. */
@media (max-width: 768px){
  .main-content > .category-header-wrapper{
    margin-left: -12px !important;
    margin-right: -12px !important;
  }
  .main-content > .category-header-wrapper > .category-header{
    border-left: 0 !important;
    border-right: 0 !important;
  }
  .main-content > .view-all-section-btn + .category-header-wrapper{
    margin-top: 26px !important;
    margin-bottom: 18px !important;
  }
}

/* ─── 55F · superseded by § 56A (2.35.0) ─────────────────────────────── */

/* ─── 55G · Note 7 · "Members post instantly" is the invite box ───────
   The reviews section's "Bought this?" card is the reference: a centred
   column in a dashed purple panel, icon over title over copy over a
   full-width action. The comment composer's note was a left-aligned flex
   row, which is a different object entirely. */
/* The two boxes are declared together so they cannot drift. § 2548
   already asked for the dashed treatment on the invite and lost it to a
   route chunk — which is why the thread's box and the product's box came
   out as two different objects. Doubling the class takes the invite to
   (0,3,0) with !important, which no route chunk reaches, and both sides
   now resolve to the same declarations from the same place. */
.comments-section .ng-cf-note,
.product-page .ng-review-invite.ng-review-invite{
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  justify-content: center !important;
  gap: 9px !important;
  border: 1px dashed rgba(138, 43, 226, .26) !important;
  border-radius: 2px !important;
  background: linear-gradient(135deg, rgba(138, 43, 226, .07), rgba(186, 85, 211, .03)) !important;
  text-align: center !important;
}
.comments-section .ng-cf-note{ padding: 16px 16px 15px !important; }
.comments-section .ng-cf-note-icon{
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; flex: 0 0 34px;
  border: 1px solid rgba(186, 85, 211, .3);
  border-radius: 50%;
  background: rgba(138, 43, 226, .14);
  color: var(--purple-light, #ba55d3);
  font-size: 13px;
}
.comments-section .ng-cf-note-body{
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  text-align: center;
}
.comments-section .ng-cf-note-head{
  font-size: 12px; font-weight: 600; letter-spacing: 1.6px;
  text-transform: uppercase; color: #fff;
}
.comments-section .ng-cf-note-sub{
  font-size: 11.5px; font-weight: 300; line-height: 1.6;
  color: rgba(255, 255, 255, .58);
}
.comments-section .ng-cf-note-cta{
  display: flex; align-items: center; justify-content: center; gap: 7px;
  align-self: stretch;
  min-height: 38px;
  padding: 0 16px;
  border: 1px solid rgba(186, 85, 211, .4);
  border-radius: 2px;
  background: rgba(138, 43, 226, .13);
  color: #fff;
  font-size: 9.5px; font-weight: 600; letter-spacing: 2px;
  text-transform: uppercase; text-decoration: none;
}
.comments-section .ng-cf-note-cta:hover{ background: rgba(138, 43, 226, .22); }

/* ─── 55H · Note 6 · Related articles ─────────────────────────────────
   Two things. The grid was three across on the desktop, so four articles
   wrapped to a second row holding one; four across puts them on one
   line, and the phone's 2×2 is already the grid of four the note asks
   for.

   And the negative space: § 53L pinned these cards to the index card's
   380px `min-height`, but the index card fills that height with its
   `.recent-response` block and a related card has no response payload to
   fill it with — so § 42 pushed the stat row to the floor with
   `margin-top:auto` and left the slack in the middle of the card. The
   cards size to their content now; the grid still equalises the row. */
.ng-related-grid{
  grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
}
.ng-related-grid .related-card{
  min-height: 0 !important;
}
.ng-related-grid .blog-stats-compact{
  margin-top: 0 !important;
  margin-bottom: 0 !important;
  padding-top: 2px !important;
}
.ng-related-grid .blog-content-compact{
  padding-bottom: 12px !important;
}
@media (max-width: 1200px){
  .ng-related-grid{ grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
}
@media (max-width: 768px){
  .ng-related-grid{ grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .ng-related-grid .related-card{ min-height: 0 !important; }
  .ng-related-grid > .related-card:nth-child(n+5){ display: none !important; }
}

/* ─── 55I · Note 8 · One newsletter design ────────────────────────────
   There are exactly three newsletter boxes on the platform:

     .mag-newsletter.mag-newsletter   index, "The Edit"       — the reference
     .lb-newsletter.lb-newsletter    lookbook
     .newsletter-section.newsletter-section  blog thread          — never touched before

   Measured side by side, none of the three agreed, and the reference did
   not even agree with itself: at 390px its form and button picked up a
   6px radius from a breakpoint override, which is three times the house
   maximum. So rather than aliasing two boxes onto a third that is itself
   drifting, the design is written ONCE here as a grouped selector and all
   three wear it. One object, three mount points — change it in one place
   and every page follows.

   The reference's own values are the source of truth: 1px --ng-line
   shell on the dark gradient, a 36px purple-ringed icon circle, an
   extra-light wide-tracked title, a muted 10.5px line, a single joined
   pill holding input and gradient button, and a whisper of a note. Title
   tracking and size are clamped rather than re-stated per breakpoint,
   which is what let the two breakpoints disagree in the first place.

   WHY !important, everywhere in this block. Doubling the classes took
   these rules to (0,2,0), which was enough for most of it, but each of
   the three boxes still lost a different handful of declarations to its
   own route chunk — the index's title size, the lookbook's form border
   and text-align, the thread's subtitle colour. Route chunks are
   appended to <head> after this file and are never unloaded, so at equal
   specificity they always win, and the result was three boxes that each
   differed from the reference in a different place. The whole point of
   the block is that one declaration governs all three, so it is stated
   with the weight to do that. */

.mag-newsletter.mag-newsletter,
.lb-newsletter.lb-newsletter,
.newsletter-section.newsletter-section{
  position: relative !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  min-height: 0 !important;
  padding: 0 !important;
  border: 1px solid var(--ng-line) !important;
  border-radius: 2px !important;
  background: linear-gradient(135deg, rgba(8, 8, 8, .98), rgba(14, 10, 18, .98)) !important;
  overflow: hidden !important;
}
.newsletter-section.newsletter-section{ margin: 0 0 28px; }

.mag-nl-inner.mag-nl-inner,
.lb-nl-inner.lb-nl-inner,
.newsletter-section.newsletter-section > *{ position: relative; z-index: 1; }

.mag-nl-inner.mag-nl-inner,
.lb-nl-inner.lb-nl-inner{
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  gap: 10px !important;
  width: 100% !important;
  padding: 24px 18px !important;
  text-align: center !important;
}
/* the thread's box has no inner wrapper, so the section is the column */
.newsletter-section.newsletter-section{
  flex-direction: column !important;
  gap: 10px !important;
  padding: 24px 18px !important;
  text-align: center !important;
}

/* ── the icon circle ──────────────────────────────────────────────────
   The thread's markup has no icon element, only an envelope glyph tucked
   inside the input. The circle is drawn with a mask so it never depends
   on the Font Awesome stylesheet resolving. */
.mag-nl-icon.mag-nl-icon,
.lb-nl-icon.lb-nl-icon,
.newsletter-section.newsletter-section::before{
  display: flex; align-items: center; justify-content: center !important;
  flex: 0 0 auto !important;
  width: 36px; height: 36px !important;
  border: 1px solid rgba(138, 43, 226, .3) !important;
  border-radius: 50% !important;
  background: rgba(138, 43, 226, .06) !important;
  color: var(--purple-light, #ba55d3) !important;
  font-size: 13px !important;
}
.newsletter-section.newsletter-section::before{
  content: "" !important;
  background-color: var(--purple-light, #ba55d3) !important;
  background-image: none !important;
  -webkit-mask: var(--ng-nl-mask) center / 15px 15px no-repeat !important;
          mask: var(--ng-nl-mask) center / 15px 15px no-repeat !important;
}
:root{
  --ng-nl-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23fff'%3E%3Cpath d='M2 5.5A1.5 1.5 0 0 1 3.5 4h17A1.5 1.5 0 0 1 22 5.5v13a1.5 1.5 0 0 1-1.5 1.5h-17A1.5 1.5 0 0 1 2 18.5v-13Zm2.3.5 7.7 5.9L19.7 6H4.3ZM20 7.6l-7.4 5.7a1 1 0 0 1-1.2 0L4 7.6V18h16V7.6Z'/%3E%3C/svg%3E");
}
/* the envelope that used to sit inside the field is now the circle */
.newsletter-input-wrap.newsletter-input-wrap > i{ display: none !important; }

/* ── type ─────────────────────────────────────────────────────────────
   Clamped so one declaration covers both breakpoints. */
.mag-nl-title.mag-nl-title,
.lb-nl-title.lb-nl-title,
.newsletter-title.newsletter-title{
  margin: 0 !important;
  font-size: clamp(14px, 1.3vw + 9px, 18px) !important;
  font-weight: 200 !important;
  letter-spacing: clamp(3px, .45vw + 1.4px, 5px) !important;
  line-height: 1.25 !important;
  text-transform: uppercase !important;
  /* stated rather than inherited: the lookbook's title carries its own
     left alignment, which the column's centring does not override */
  text-align: center !important;
  color: rgba(255, 255, 255, .95) !important;
}
.mag-nl-sub.mag-nl-sub,
.lb-nl-sub.lb-nl-sub,
.newsletter-subtitle.newsletter-subtitle{
  max-width: 340px !important;
  margin: 0 auto !important;
  font-size: 10.5px !important;
  font-weight: 300 !important;
  letter-spacing: .4px !important;
  line-height: 1.55 !important;
  text-align: center !important;
  color: rgba(255, 255, 255, .42) !important;
}

/* ── the joined pill ──────────────────────────────────────────────────
   One bordered rail, input and button inside it, no gap and no second
   border down the middle — which is what made the thread's version read
   as two separate controls. */
.mag-nl-form.mag-nl-form,
.lb-nl-form.lb-nl-form,
.newsletter-form.newsletter-form{
  display: flex !important;
  flex-direction: row !important;
  gap: 0 !important;
  width: min(340px, 100%) !important;
  max-width: min(340px, 100%) !important;
  min-height: 40px !important;
  margin: 0 auto !important;
  border: 1px solid rgba(138, 43, 226, .25) !important;
  border-radius: 2px !important;
  background: rgba(0, 0, 0, .3) !important;
  overflow: hidden !important;
}
.newsletter-input-wrap.newsletter-input-wrap{
  display: flex !important;
  flex: 1 1 auto !important;
  min-width: 0 !important;
}
.mag-nl-input.mag-nl-input,
.lb-nl-input.lb-nl-input,
.newsletter-input.newsletter-input{
  flex: 1 1 auto !important;
  width: 100% !important;
  min-width: 0 !important;
  min-height: 38px !important;
  padding: 0 12px !important;
  border: 0 !important;
  border-radius: 0 !important;
  background: transparent !important;
  color: rgba(255, 255, 255, .88) !important;
  font-size: 11px !important;
  font-weight: 300 !important;
  letter-spacing: .3px !important;
}
.mag-nl-input.mag-nl-input::placeholder,
.lb-nl-input.lb-nl-input::placeholder,
.newsletter-input.newsletter-input::placeholder{ color: rgba(255, 255, 255, .3); }
.mag-nl-input.mag-nl-input:focus,
.lb-nl-input.lb-nl-input:focus,
.newsletter-input.newsletter-input:focus{ outline: 0; }
.mag-nl-form.mag-nl-form:focus-within,
.lb-nl-form.lb-nl-form:focus-within,
.newsletter-form.newsletter-form:focus-within{ border-color: rgba(186, 85, 211, .55); }

.mag-nl-btn.mag-nl-btn,
.lb-nl-btn.lb-nl-btn,
.newsletter-submit.newsletter-submit{
  display: flex; align-items: center; justify-content: center !important;
  flex: 0 0 auto !important;
  gap: 6px !important;
  min-width: 40px !important;
  min-height: 38px !important;
  padding: 0 16px !important;
  border: 0 !important;
  border-radius: 0 !important;
  background: linear-gradient(135deg, #8a2be2, #ba55d3) !important;
  color: #fff !important;
  font-size: 9px; font-weight: 600; letter-spacing: 1.6px !important;
  text-transform: uppercase !important;
  cursor: pointer !important;
}
/* the index's button is the arrow glyph alone — no text to track */
.mag-nl-btn.mag-nl-btn{ padding: 0 12px; letter-spacing: normal; }

.mag-nl-note.mag-nl-note,
.lb-nl-note.lb-nl-note,
.newsletter-note.newsletter-note{
  font-size: 8.5px; font-weight: 300; letter-spacing: .4px !important;
  color: rgba(255, 255, 255, .26) !important;
}

/* The 6px that the reference picked up at 390px, and anything else a
   route chunk asserts later in the cascade. (0,2,0) so source order
   stops mattering — route chunks load after this file and never
   unload. */
.mag-newsletter.mag-newsletter .mag-nl-form.mag-nl-form,
.lb-newsletter.lb-newsletter .lb-nl-form.lb-nl-form,
.newsletter-section.newsletter-section .newsletter-form.newsletter-form,
.mag-newsletter.mag-newsletter .mag-nl-btn.mag-nl-btn,
.lb-newsletter.lb-newsletter .lb-nl-btn.lb-nl-btn,
.newsletter-section.newsletter-section .newsletter-submit.newsletter-submit,
.mag-newsletter.mag-newsletter .mag-nl-input.mag-nl-input,
.lb-newsletter.lb-newsletter .lb-nl-input.lb-nl-input,
.newsletter-section.newsletter-section .newsletter-input.newsletter-input{ border-radius: 0; }
.mag-newsletter.mag-newsletter .mag-nl-form.mag-nl-form,
.lb-newsletter.lb-newsletter .lb-nl-form.lb-nl-form,
.newsletter-section.newsletter-section .newsletter-form.newsletter-form{ border-radius: 2px; }

@media (max-width: 768px){
  .mag-newsletter.mag-newsletter,
  .lb-newsletter.lb-newsletter,
  .newsletter-section.newsletter-section{ padding: 0; }
  .mag-nl-inner.mag-nl-inner,
  .lb-nl-inner.lb-nl-inner,
  .newsletter-section.newsletter-section{ padding: 20px 16px; }
  /* 16px keeps iOS from zooming the field on focus without breaking the
     11px rhythm anywhere it matters visually: the transform is a no-op
     at rest and only the rendered size changes. */
  .mag-nl-input.mag-nl-input,
  .lb-nl-input.lb-nl-input,
  .newsletter-input.newsletter-input{ font-size: 16px; transform-origin: left center; }
}

/* ─── 55J · Size guide and FBT on the phone ────────────────────────────
   REVISED in 2.35.0, twice over.

   THE BANDS. 2.34 pinned the two headings to the VIEWPORT — `width:
   calc(100vw - 2px)` pulled back with `calc(1px - 50vw + 50%)` margins —
   to reach a container that was then full-bleed. With the side gutter
   restored that would hang 8px outside the column, and viewport-sized
   boxes are exactly what iOS Safari is twitchy about: a fraction of a
   pixel past the glass and it treats the page as wider than the screen
   and zooms out to fit it. That is the "zooming the page out then back
   in" on the product page, and it is the one place on the site a box was
   sized from the viewport. The break-out is now relative to the column:
   the heading sits inside `.product-sidebar` (16px padding) inside
   `.main-content` (12px), so it steps out by exactly those 28px and lands
   on the container's border line — and can never be wider than the
   column it lives in.

   THE CARDS. The first pass gave the FBT card a full-width portrait image
   and stacked the details under it, inside a card that the compact rail
   still held to a fixed height with `overflow: hidden` — so the name,
   price, colours and Claim were simply cut off, and the tier tag and the
   badge were both drawn in the same corner. The platform card is an
   OVERLAY: the image fills the card, a scrim rises from the bottom, and
   the details sit on it — name centred, price left, colourway pill
   right, Claim full width, one tag top-left. The values below are read
   off the shop grid's card at 390px, part by part, so the two are the
   same object — the same 270px height, and (with the sections sharing
   the column's edges, below) the same width. */
:root{ --ng-fbt-bleed: 28px; }

@media (max-width: 768px){
  /* The two sections sat 16px further in than everything else on the
     page — `.product-sidebar`'s own padding, on top of the column's — so
     their content was narrower than the gallery and details above it, and
     the FBT cards came out 150px wide where every platform card is 167px.
     The sections now share the column's edges: the cards are the shop
     grid's exact size (167 × 270), and the band steps out by the column's
     12px alone. */
  .product-page .product-sidebar{
    padding-left: 0 !important;
    padding-right: 0 !important;
    --ng-fbt-bleed: 12px;
  }
  /* ── the two bands reach the container's border line ────────────── */
  .product-page :is(.size-guide, .frequently-bought) > h4{
    box-sizing: border-box !important;
    width: auto !important;
    max-width: none !important;
    margin-left: calc(-1 * var(--ng-fbt-bleed)) !important;
    margin-right: calc(-1 * var(--ng-fbt-bleed)) !important;
    margin-bottom: 12px !important;
    padding-left: 16px !important;
    padding-right: 16px !important;
    border-left: 0 !important;
    border-right: 0 !important;
  }
  .product-page :is(.size-guide, .frequently-bought) > h4 :is(i, svg){
    display: none !important;
  }

  /* ── the size chart uses the room ───────────────────────────────── */
  .product-page .size-guide .size-chart{
    width: 100% !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
    table-layout: fixed !important;
  }
  .product-page .size-guide .size-chart :is(th, td){
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
  }

  /* ── the FBT grid: two up, the shop grid's gap, edge to edge ────── */
  .product-page .frequently-bought .compact-recommendations{
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 12px !important;
    margin: 0 !important;
    padding: 0 !important;
  }

  /* ── the card is the platform card ──────────────────────────────── */
  .product-page .frequently-bought .compact-recommendations > .product-card{
    display: block !important;
    position: relative !important;
    width: auto !important;
    /* the platform card's height on a phone, at every phone width —
       uniform rows were a note of their own (2.28) */
    height: 270px !important;
    min-height: 0 !important;
    aspect-ratio: auto !important;
    margin: 0 !important;
    padding: 0 !important;
    overflow: hidden !important;
    border-radius: 2px !important;
    background: transparent !important;
    transform: none !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card:not(.tier-signature, .tier-seasonal, .tier-exclusive, .tier-limited){
    border: 1px solid var(--ng-line) !important;
    box-shadow: none !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card:is(.tier-signature, .tier-seasonal, .tier-exclusive, .tier-limited){
    border: 1px solid color-mix(in srgb, var(--neon) 72%, transparent) !important;
    box-shadow:
      0 0 6px -2px color-mix(in srgb, var(--neon) 55%, transparent),
      inset 0 0 6px -4px color-mix(in srgb, var(--neon) 45%, transparent) !important;
  }
  /* the "+" belongs to the desktop combination row */
  .product-page .frequently-bought .compact-recommendations > .product-card::before{
    content: none !important;
  }
  /* the scrim the details sit on */
  .product-page .frequently-bought .compact-recommendations > .product-card::after{
    content: "" !important;
    display: block !important;
    position: absolute !important;
    left: 0 !important; right: 0 !important; bottom: 0 !important; top: auto !important;
    width: auto !important;
    height: 62% !important;
    z-index: 1 !important;
    pointer-events: none !important;
    border: 0 !important;
    border-radius: 0 !important;
    opacity: 1 !important;
    background: linear-gradient(to top, rgba(0, 0, 0, .96) 0%, rgba(0, 0, 0, .86) 28%, rgba(0, 0, 0, .5) 55%, rgba(0, 0, 0, 0) 100%) !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .product-image-container{
    display: block !important;
    position: absolute !important;
    inset: 0 !important;
    width: auto !important;
    height: auto !important;
    max-width: none !important;
    min-height: 0 !important;
    aspect-ratio: auto !important;
    margin: 0 !important;
    overflow: hidden !important;
    border-radius: 0 !important;
    background: transparent !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .product-image-container
    :is(img, .product-main-image, .compact-rec-image){
    position: static !important;
    display: block !important;
    width: 100% !important;
    height: 100% !important;
    max-width: none !important;
    max-height: none !important;
    margin: 0 !important;
    object-fit: cover !important;
    border-radius: 0 !important;
    transform: none !important;
  }
  /* one tag, top-left — the platform card shows the badge and keeps the
     tier in the border colour; two labels in one corner was the overlap */
  .product-page .frequently-bought .compact-recommendations > .product-card .tier-tag{
    display: none !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .product-badge{
    position: absolute !important;
    top: 10px !important; left: 10px !important; right: auto !important; bottom: auto !important;
    z-index: 3 !important;
    max-width: calc(100% - 64px) !important;
    margin: 0 !important;
    padding: 3px 7px !important;
    overflow: hidden !important;
    white-space: nowrap !important;
    text-overflow: ellipsis !important;
    font-size: 9.5px !important;
    font-weight: 600 !important;
    letter-spacing: .5px !important;
    line-height: normal !important;
    text-transform: uppercase !important;
    color: rgba(255, 255, 255, .9) !important;
    background: linear-gradient(135deg, rgba(138, 43, 226, .9), rgba(186, 85, 211, .85)) !important;
    border: 0 !important;
    border-radius: 2px !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .favorite-btn{
    position: absolute !important;
    top: 8px !important; right: 8px !important; left: auto !important; bottom: auto !important;
    z-index: 3 !important;
    width: 36px !important; height: 36px !important;
    display: flex !important; align-items: center !important; justify-content: center !important;
    margin: 0 !important;
    border: 1px solid rgba(255, 255, 255, .1) !important;
    border-radius: 50% !important;
    background: rgba(0, 0, 0, .6) !important;
    -webkit-backdrop-filter: blur(8px) !important;
            backdrop-filter: blur(8px) !important;
    color: #fff !important;
    font-size: 14px !important;
  }
  /* the details: an overlay grid anchored to the card's floor */
  .product-page .frequently-bought .compact-recommendations > .product-card .product-details{
    position: absolute !important;
    left: 0 !important; right: 0 !important; bottom: 0 !important; top: auto !important;
    z-index: 2 !important;
    display: grid !important;
    grid-template-columns: max-content minmax(0, 1fr) !important;
    align-items: center !important;
    column-gap: 8px !important;
    row-gap: 7px !important;
    width: auto !important;
    margin: 0 !important;
    padding: 0 10px 10px !important;
    text-align: center !important;
    background: none !important;
    border: 0 !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .product-title{
    grid-column: 1 / -1 !important;
    order: 0 !important;
    display: block !important;
    margin: 0 !important;
    height: 16px !important;
    overflow: hidden !important;
    white-space: nowrap !important;
    text-overflow: ellipsis !important;
    font-size: 11.5px !important;
    font-weight: 400 !important;
    letter-spacing: .3px !important;
    line-height: 16px !important;
    text-align: center !important;
    text-transform: none !important;
    color: #fff !important;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .85) !important;
    -webkit-line-clamp: unset !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .product-pricing{
    display: contents !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .current-price{
    grid-column: 1 !important;
    order: 1 !important;
    justify-self: start !important;
    margin: 0 !important;
    font-size: 15px !important;
    font-weight: 700 !important;
    line-height: 15px !important;
    white-space: nowrap !important;
    color: #c86be6 !important;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .9) !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .original-price{
    grid-column: 1 / -1 !important;
    order: -1 !important;
    justify-self: center !important;
    margin: 0 0 1px !important;
    font-size: 10px !important;
    letter-spacing: .2px !important;
    line-height: 10px !important;
    white-space: nowrap !important;
    text-decoration: line-through !important;
    color: rgba(255, 255, 255, .62) !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .product-colors{
    grid-column: 2 !important;
    order: 2 !important;
    justify-self: end !important;
    align-self: end !important;
    position: relative !important;
    display: flex !important;
    justify-content: center !important;
    gap: 5px !important;
    margin: -3px -7px -3px 0 !important;
    padding: 3px 7px !important;
    border-radius: 999px !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .color-dot{
    width: 12px !important;
    height: 12px !important;
    margin: 0 !important;
    border: 1px solid rgba(255, 255, 255, .4) !important;
    border-radius: 50% !important;
  }
  .product-page .frequently-bought .compact-recommendations > .product-card .arrival-add-btn{
    grid-column: 1 / -1 !important;
    order: 3 !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 3px !important;
    width: 100% !important;
    height: 36px !important;
    min-height: 36px !important;
    margin: 0 !important;
    padding: 0 6px !important;
    font-size: 10px !important;
    font-weight: 500 !important;
    letter-spacing: .6px !important;
    text-transform: uppercase !important;
    color: #fff !important;
    background: linear-gradient(135deg, rgba(138, 43, 226, .1), rgba(138, 43, 226, .15)) !important;
    border: 1px solid rgba(138, 43, 226, .4) !important;
    border-radius: 2px !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   SECTION 56 · 2.35.0 — the final mobile pass
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 56A · A comment IS a review card ─────────────────────────────────
   2.34 bordered the comment and gave its buttons the review pills, but
   kept the feed's skeleton underneath: avatar in one column, everything
   else indented into a second. So the "divider" was a rule under the
   text that started at the text's indent and stopped short of both
   edges — it divided nothing from anything, and the action bar sat in a
   narrow column beside an empty gutter.

   The review card's anatomy, measured, applied to the comment's markup:

     ┌──────────────────────────────────────────┐
     │ [AV]  Name                                │   head: avatar · who
     │       7H AGO                              │
     │ Body text runs the full width of the      │   text: edge to edge
     │ card, not an indented column.             │
     ├──────────────────────────────────────────┤   a rule that spans the
     │ [♥ 5] [↩ REPLY]                     [⚑]  │   card, border to border
     └──────────────────────────────────────────┘

   `.comment-body` becomes `display: contents`, so its children — the
   name line, the text, the action bar, the replies — are laid out by the
   card's own grid: the name beside the avatar, everything else across
   both columns. The action bar steps out by the card's padding so its
   top rule runs from border to border, exactly as the review's does.

   Replies live inside their parent's card, under a toggle band of their
   own, as a threaded list on one purple rail — no box inside a box. Past
   five they scroll; at five or fewer they simply show. */
.comments-section > .comment{
  display: grid !important;
  grid-template-columns: 32px minmax(0, 1fr) !important;
  column-gap: 10px !important;
  row-gap: 0 !important;
  align-items: center !important;
  margin: 0 0 10px !important;
  padding: 13px 14px !important;
  border: 1px solid var(--ng-line) !important;
  border-radius: 2px !important;
  background: linear-gradient(135deg, #070709, #0b0a0f) !important;
}
.comments-section > .comment:last-of-type{ margin-bottom: 0 !important; }
.comments-section .comment > .comment-body{ display: contents !important; }

/* head */
.comments-section .comment > .comment-avatar{
  grid-column: 1 !important;
  grid-row: 1 !important;
  align-self: center !important;
  width: 32px !important; height: 32px !important;
  display: flex !important; align-items: center !important; justify-content: center !important;
  border: 1px solid var(--ng-line) !important;
  border-radius: 2px !important;
  background: linear-gradient(135deg, #8a2be2, #ba55d3) !important;
  color: #fff !important;
  font-size: 11px !important;
  font-weight: 600 !important;
  letter-spacing: .3px !important;
}
.comments-section .comment > .comment-body > .comment-top{
  grid-column: 2 !important;
  grid-row: 1 !important;
  display: flex !important;
  flex-direction: column !important;
  align-items: flex-start !important;
  justify-content: center !important;
  gap: 3px !important;
  min-width: 0 !important;
  margin: 0 !important;
}
.comments-section .comment .comment-author{
  max-width: 100% !important;
  overflow: hidden !important;
  white-space: nowrap !important;
  text-overflow: ellipsis !important;
  font-size: 11px !important;
  font-weight: 500 !important;
  letter-spacing: .4px !important;
  color: rgba(255, 255, 255, .92) !important;
}
.comments-section .comment .comment-date{
  font-size: 9px !important;
  font-weight: 400 !important;
  letter-spacing: .3px !important;
  text-transform: uppercase !important;
  color: rgba(255, 255, 255, .64) !important;
}
.comments-section .comment .comment-date::before{ content: none !important; }

/* text: the full width of the card */
.comments-section .comment > .comment-body > .comment-text{
  grid-column: 1 / -1 !important;
  margin: 10px 0 !important;
  font-size: 11px !important;
  font-weight: 300 !important;
  letter-spacing: .2px !important;
  line-height: 1.55 !important;
  color: rgba(255, 255, 255, .6) !important;
  overflow-wrap: anywhere;
}

/* the action bar: a rule from border to border, pills below it */
.comments-section .comment > .comment-body > .comment-actions{
  grid-column: 1 / -1 !important;
  display: flex !important;
  align-items: center !important;
  gap: 6px !important;
  margin: 0 -14px !important;
  padding: 9px 14px 0 !important;
  border-top: 1px solid var(--ng-line) !important;
}
.comments-section .comment .comment-action{
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  gap: 5px !important;
  height: 32px !important;
  min-height: 32px !important;
  margin: 0 !important;
  padding: 0 10px !important;
  border: 1px solid var(--ng-line) !important;
  border-radius: 2px !important;
  background: rgba(255, 255, 255, .016) !important;
  color: rgba(255, 255, 255, .4) !important;
  font-size: 9px !important;
  font-weight: 500 !important;
  letter-spacing: .5px !important;
  text-transform: uppercase !important;
  font-variant-numeric: tabular-nums;
  box-shadow: none !important;
  cursor: pointer;
  transition: border-color .2s ease, color .2s ease, background-color .2s ease;
}
.comments-section .comment .comment-action i{ opacity: 1 !important; font-size: 10px !important; }
.comments-section .comment .comment-action:hover{
  border-color: rgba(186, 85, 211, .32) !important;
  background: rgba(138, 43, 226, .07) !important;
  color: rgba(255, 255, 255, .78) !important;
}
.comments-section .comment .comment-action[aria-pressed="true"]{
  border-color: rgba(186, 85, 211, .42) !important;
  color: #ba55d3 !important;
}
/* the flag sits right, as Report does on a review, and is the glyph alone */
.comments-section .comment .comment-actions > :is(.ng-flag, .ng-flag-pick){ margin-left: auto !important; }
.comments-section .comment .ng-flag .report-text{ display: none !important; }
.comments-section .comment .ng-flag.is-done{
  border-color: rgba(186, 85, 211, .3) !important;
  color: rgba(186, 85, 211, .8) !important;
  cursor: default;
}
.comments-section .comment .ng-flag-select{
  -webkit-appearance: none; appearance: none;
  max-width: 210px;
  padding: 0 26px 0 10px !important;
  background:
    url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath fill='%23ba55d3' d='M0 0h10L5 6z'/%3E%3C/svg%3E") right 9px center / 8px 5px no-repeat,
    rgba(10, 8, 14, .96) !important;
  color: rgba(255, 255, 255, .8) !important;
  text-transform: none !important;
  letter-spacing: .2px !important;
}

/* the reply composer opens under the action bar, across the card */
.comments-section .comment > .comment-body > div:has(> .ng-comment-form),
.comments-section .comment > .comment-body > .ng-comment-form{
  grid-column: 1 / -1 !important;
  margin-top: 10px !important;
}

/* ── replies: a toggle band, then the thread on one rail ─────────────── */
.comments-section .comment > .comment-body > .ng-replies-toggle{
  grid-column: 1 / -1 !important;
  order: 1 !important;
  display: flex !important;
  align-items: center !important;
  justify-content: flex-start !important;
  gap: 10px !important;
  width: auto !important;
  min-height: 0 !important;
  margin: 10px -14px 0 !important;
  padding: 10px 14px 0 !important;
  border: 0 !important;
  border-top: 1px solid var(--ng-line) !important;
  border-radius: 0 !important;
  background: none !important;
  color: rgba(186, 85, 211, .85) !important;
  font-size: 9px !important;
  font-weight: 600 !important;
  letter-spacing: 1.4px !important;
  text-transform: uppercase !important;
  text-align: left !important;
  cursor: pointer;
}
.comments-section .comment > .comment-body > .ng-replies-toggle:hover{ color: #d18cf0 !important; }
.comments-section .comment .ng-replies-rule{
  display: block !important;
  flex: 0 0 18px !important;
  width: 18px !important;
  height: 1px !important;
  background: rgba(186, 85, 211, .5) !important;
}
.comments-section .comment > .comment-body > .ng-replies{
  grid-column: 1 / -1 !important;
  order: 2 !important;
  margin: 0 !important;
}
.comments-section .comment > .comment-body > .ng-replies:not(.is-open):not([data-expanded]){ display: none !important; }
.comments-section .ng-replies.ng-replies > .ng-replies-scroll{
  display: flex !important;
  flex-direction: column !important;
  max-height: none !important;
  overflow: visible !important;
  margin: 12px 0 0 !important;
  padding: 0 0 0 12px !important;
  border-left: 2px solid rgba(186, 85, 211, .38) !important;
}
/* past five, the thread scrolls in place rather than growing the page */
.comments-section .ng-replies.is-long.is-open > .ng-replies-scroll,
.comments-section .ng-replies.is-long[data-expanded] > .ng-replies-scroll{
  max-height: var(--ng-replies-five, 560px) !important;
  overflow-y: auto !important;
  overscroll-behavior: contain !important;
  padding-right: 8px !important;
  scrollbar-width: thin;
  scrollbar-color: rgba(138, 43, 226, .45) transparent;
}
.comments-section .ng-replies-scroll::-webkit-scrollbar{ width: 4px; }
.comments-section .ng-replies-scroll::-webkit-scrollbar-track{ background: transparent; }
.comments-section .ng-replies-scroll::-webkit-scrollbar-thumb{ background: rgba(138, 43, 226, .45); border-radius: 2px; }

/* a reply: the same anatomy, one size down, no box of its own */
.comments-section .ng-replies.ng-replies > .ng-replies-scroll > .comment.comment{
  display: grid !important;
  grid-template-columns: 26px minmax(0, 1fr) !important;
  column-gap: 9px !important;
  row-gap: 0 !important;
  align-items: center !important;
  margin: 0 !important;
  padding: 12px 0 !important;
  border: 0 !important;
  border-bottom: 1px solid var(--ng-line) !important;
  border-radius: 0 !important;
  background: none !important;
}
.comments-section .ng-replies.ng-replies > .ng-replies-scroll > .comment.comment:first-child{ padding-top: 0 !important; }
.comments-section .ng-replies.ng-replies > .ng-replies-scroll > .comment.comment:last-child{ padding-bottom: 2px !important; border-bottom: 0 !important; }
.comments-section .ng-replies .comment > .comment-avatar{
  width: 26px !important; height: 26px !important;
  font-size: 9px !important;
  background: linear-gradient(135deg, rgba(138, 43, 226, .55), rgba(186, 85, 211, .35)) !important;
}
.comments-section .ng-replies .comment > .comment-body > .comment-text{
  margin: 8px 0 !important;
  font-size: 11px !important;
}
.comments-section .ng-replies .comment > .comment-body > .comment-actions{
  margin: 0 !important;
  padding: 0 !important;
  border-top: 0 !important;
}
.comments-section .ng-replies .comment .comment-action{
  height: 28px !important;
  min-height: 28px !important;
  padding: 0 9px !important;
  font-size: 8.5px !important;
}

/* the desktop review card is one size up: 16/17 padding, 38px avatar,
   12px text, 36px pills — the comment follows it there too */
@media (min-width: 769px){
  .comments-section > .comment{
    grid-template-columns: 38px minmax(0, 1fr) !important;
    column-gap: 12px !important;
    padding: 16px 17px !important;
  }
  .comments-section .comment > .comment-avatar{
    width: 38px !important; height: 38px !important;
    font-size: 12px !important;
    background: linear-gradient(135deg, rgba(138, 43, 226, .3), rgba(186, 85, 211, .16)) !important;
  }
  .comments-section .comment .comment-author{ font-size: 12.5px !important; }
  .comments-section .comment .comment-date{
    font-size: 9.5px !important;
    letter-spacing: .4px !important;
    color: rgba(255, 255, 255, .34) !important;
  }
  .comments-section .comment > .comment-body > .comment-text{
    margin: 12px 0 !important;
    font-size: 12px !important;
    line-height: 1.6 !important;
    color: rgba(255, 255, 255, .62) !important;
  }
  .comments-section .comment > .comment-body > .comment-actions{
    margin: 0 -17px !important;
    padding: 11px 17px 0 !important;
  }
  .comments-section .comment .comment-action{
    height: 36px !important;
    min-height: 36px !important;
    padding: 0 12px !important;
    font-size: 9.5px !important;
  }
  .comments-section .comment > .comment-body > .ng-replies-toggle{
    margin: 12px -17px 0 !important;
    padding: 11px 17px 0 !important;
    font-size: 9.5px !important;
  }
  .comments-section .ng-replies.ng-replies > .ng-replies-scroll > .comment.comment{
    grid-template-columns: 30px minmax(0, 1fr) !important;
    column-gap: 10px !important;
  }
  .comments-section .ng-replies .comment > .comment-avatar{
    width: 30px !important; height: 30px !important; font-size: 10px !important;
  }
  .comments-section .ng-replies .comment > .comment-body > .comment-text{ font-size: 11.5px !important; }
  .comments-section .ng-replies .comment .comment-action{ height: 30px !important; min-height: 30px !important; }
}


/* ─── 56B · Related articles: no hole, four to a row ────────────────────
   Measured at 390px, every related card was 310px tall with 65–71px of
   empty card between the byline and the stat chips. Two rules did it,
   both written for OTHER cards:

     · `.main-content .blog-card-compact { height: 310px }` (§ 49F) sizes
       the blog INDEX card on a phone, which has a latest-reply block to
       fill that height. A related card is also `.blog-card-compact` and
       also inside `.main-content`, so it got the index card's height
       with nothing to fill it.
     · `.ng-related-grid .related-card .blog-stats-compact { margin-top:
       auto }` (§ 54-) then pushed the chips to the floor, so the slack
       collected in the middle of the card — the gap in the note.

   2.34 zeroed the margin at (0,2,0) and was simply outranked by the
   (0,3,0) rule on the phone. Now: the card is exactly as tall as its
   content, the chips sit 8px under the byline on every card, and the
   variable parts are fixed — category one line, title two, excerpt two
   (three on the desktop) — so every card in a row is the same height on
   its own and the grid has no slack to hand out. Four across on the
   desktop, a grid of four (two by two) on the phone, like the shop's
   rails. */
.main-content .ng-related-grid > .related-card.blog-card-compact,
.ng-related-grid > .related-card.blog-card-compact{
  height: auto !important;
  min-height: 0 !important;
  max-height: none !important;
}
/* Every card the same object. On the phone the fourth card was `display:
   block` (a § 53L rule that un-hid it at ≤1024 restated its display), so
   its image fell back to inline and left a 4px descender gap; and the
   first card kept the INDEX's featured-card padding and three-line title.
   Stated for all four, at a weight none of those reach. */
.main-content .ng-related-grid > .related-card.blog-card-compact:nth-child(n){
  display: flex !important;
  flex-direction: column !important;
}
.main-content .ng-related-grid > .related-card.blog-card-compact:nth-child(n) > .related-card-image{
  display: block !important;
}
.main-content .ng-related-grid > .related-card.blog-card-compact:nth-child(n) .related-card-content{
  padding-bottom: 12px !important;
}
.main-content .ng-related-grid > .related-card.blog-card-compact:nth-child(n) .related-card-title{
  -webkit-line-clamp: 2 !important;
}
.main-content .ng-related-grid > .related-card.blog-card-compact:nth-child(n) .related-card-excerpt{
  display: -webkit-box !important;
}
.ng-related-grid > .related-card .related-card-content.blog-content-compact{
  flex: 0 0 auto !important;
  justify-content: flex-start !important;
}
.ng-related-grid > .related-card .related-card-category{
  overflow: hidden !important;
  white-space: nowrap !important;
  text-overflow: ellipsis !important;
}
.ng-related-grid > .related-card .related-card-title{
  display: -webkit-box !important;
  -webkit-box-orient: vertical !important;
  -webkit-line-clamp: 2 !important;
  overflow: hidden !important;
  height: calc(2 * var(--ng-rel-title-lh, 16.2px)) !important;
  min-height: 0 !important;
  max-height: none !important;
  line-height: var(--ng-rel-title-lh, 16.2px) !important;
}
.ng-related-grid > .related-card .related-card-excerpt{
  display: -webkit-box !important;
  -webkit-box-orient: vertical !important;
  -webkit-line-clamp: 3 !important;
  overflow: hidden !important;
  height: calc(3 * var(--ng-rel-ex-lh, 15.95px)) !important;
  max-height: none !important;
  line-height: var(--ng-rel-ex-lh, 15.95px) !important;
}
.ng-related-grid > .related-card .related-card-meta{
  white-space: nowrap !important;
}
.ng-related-grid > .related-card .blog-stats-compact,
.main-content .ng-related-grid > .related-card .blog-stats-compact{
  margin-top: 8px !important;
  margin-bottom: 0 !important;
  flex-wrap: nowrap !important;
}
@media (max-width: 768px){
  .ng-related-grid > .related-card .related-card-title{
    --ng-rel-title-lh: 15.2px;
  }
  .ng-related-grid > .related-card .related-card-excerpt{
    --ng-rel-ex-lh: 13.8px;
    -webkit-line-clamp: 2 !important;
    height: calc(2 * var(--ng-rel-ex-lh)) !important;
  }
  .main-content .ng-related-grid > .related-card .blog-stats-compact,
  .ng-related-grid > .related-card .blog-stats-compact{
    margin-top: 7px !important;
  }
}


/* ─── 56C · 2px, everywhere, on the phone too ───────────────────────────
   Audited by computed style on every route at 390px: twenty-three
   controls came out rounder than the house maximum — and all but four
   only on the phone, because they are mobile overrides that drifted, not
   the desktop design. The worst was the rails' VIEW MORE (`.more-like-
   it-btn`) at 10px on the shop, the product page, the articles and the
   bag — the exact control the note names. Alongside it: the product
   page's size, quantity, Claim and wishlist buttons and its review pager
   (8–10px), the size-chart pill (20px), every card's Claim (4px), and the
   badges and tags (3–4px).

   One rule, the house value. True circles keep 50%: the colour swatches
   are made actually round on the phone (they were 40×38, which made 50%
   an ellipse) and the wishlist hearts, avatars and dots are untouched. */
:is(.more-like-it-btn, .compact-view-more-btn, .view-more-btn, .view-all-section-btn,
    .hero-see-more-btn, .load-more-btn, .shop-load-more, .rec-refresh-btn, .view-all-link,
    .arrival-add-btn, .compact-add-to-cart-btn, .card-add-btn, .mag-card-btn,
    .add-to-cart-btn, .wishlist-btn, .size-option, .qty-btn, .page-btn, .sp-size-guide-btn,
    .clear-cart-btn, .arrival-badge, .mag-card-tag, .product-badge, .lb-release-tag,
    .card-badge, .tier-tag):not(.ng-x-circle){
  border-radius: 2px !important;
}
.shop-page .product-card .original-price[data-ng-off]::after,
.original-price[data-ng-off]::after{ border-radius: 2px !important; }
@media (max-width: 768px){
  /* the rails, the cards and the product page on a phone */
  .recommendations-full-width .more-like-it-btn,
  .rec-footer .more-like-it-btn,
  .product-page :is(.size-option, .qty-btn, .add-to-cart-btn, .wishlist-btn, .page-btn),
  .shop-page :is(.sp-size-guide-btn, .load-more-btn, .arrival-add-btn, .product-badge),
  :is(.home-page, .container) :is(.arrival-add-btn, .compact-add-to-cart-btn, .arrival-badge, .mag-card-btn, .mag-card-tag){
    border-radius: 2px !important;
  }
  .shop-page .sfg-btn.sfg-color-btn{
    width: 40px !important;
    height: 40px !important;
    min-width: 40px !important;
    border-radius: 50% !important;
  }
}


/* ─── 56D · The Me tab's sign-in, centred and compact ───────────────────
   Two things on the phone's sign-in card:

     · "Sign in / Welcome back — enter your credentials" sat flush left
       under a centred pair of tabs, so the card had two axes. Centred,
       like every other heading on the phone.
     · About 100px of empty card under Sign in / Forgot?. § 45 gave the
       card `padding-bottom: tab bar + 16px` so the NightGuard tick box
       could clear the fixed tab bar when the page bottomed out — but the
       footer follows the card, so the page already scrolls well past it,
       and `body` reserves the bar's height besides. The reserve was paid
       twice and showed up as dead space inside the card. The card's
       bottom inset now matches its top. */
@media (max-width: 768px){
  .auth-shell{
    padding-bottom: 14px !important;
  }
  .auth-shell .panel > .panel-header,
  .auth-grid .panel > .panel-header{
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    text-align: center !important;
  }
  .auth-grid .panel > .panel-header :is(.panel-title, .panel-subtitle){
    width: auto !important;
    text-align: center !important;
  }
  .auth-grid .panel{
    padding-bottom: 4px !important;
  }
}


/* ─── 56E · The travelling light, on the compositor ─────────────────────
   The pulse that runs along every section band — top edge left to right,
   then back along the bottom — moved by animating `left`, `top` and
   `bottom`. Those are layout properties: every frame, on every band on
   the page, the browser re-ran layout, and a phone does that while it is
   also scrolling. On iOS that is the stutter that reads as the page
   "breathing", and it is how a band that ever measured a fraction of a
   pixel wider than the screen (§ 55J's old viewport sizing, now gone)
   kept re-triggering the zoom-to-fit.

   Same loop, same 19.6s, same 15% bar, same timings — done with
   `transform` and `opacity` only, which never touch layout. One bar can
   only travel one edge with a transform, so the run is split across the
   band's two pseudo-elements: `::before` takes the top edge, `::after`
   the bottom, each visible only for its half of the loop. 566.667% is
   85% of the band expressed in the bar's own width (85 / 15), so the bar
   still ends flush with the band's right edge and never overhangs it.

   Route sheets that are never unloaded redefine `@keyframes circularPulse`
   with their own timings (the blog's runs 45% where the base runs 48%), so
   the loop also stopped being the same loop from page to page. These
   keyframes have their own names, owned here. */
.category-header.category-header::before,
.product-page :is(.size-guide, .frequently-bought) > h4::before{
  content: "" !important;
  display: block !important;
  position: absolute !important;
  top: -2px !important; bottom: auto !important;
  left: 0 !important; right: auto !important;
  width: 15% !important;
  height: 2px !important;
  border-radius: 1px !important;
  z-index: 3 !important;
  pointer-events: none !important;
  opacity: 0;
  will-change: transform, opacity;
  animation: ngLightTop 19.6s linear infinite !important;
}
.category-header.category-header::after,
.product-page :is(.size-guide, .frequently-bought) > h4::after{
  content: "" !important;
  display: block !important;
  position: absolute !important;
  top: auto !important; bottom: -2px !important;
  left: 0 !important; right: auto !important;
  width: 15% !important;
  height: 2px !important;
  border-radius: 1px !important;
  background: linear-gradient(90deg, transparent, var(--brand, var(--purple, #8a2be2)), transparent) !important;
  z-index: 3 !important;
  pointer-events: none !important;
  opacity: 0;
  will-change: transform, opacity;
  animation: ngLightBottom 19.6s linear infinite !important;
}
.product-page :is(.size-guide, .frequently-bought) > h4{ position: relative !important; }
@keyframes ngLightTop{
  0%   { transform: translate3d(0, 0, 0);        opacity: 0; }
  8%   { transform: translate3d(0, 0, 0);        opacity: 1; }
  38%  { transform: translate3d(566.667%, 0, 0); opacity: 1; }
  48%  { transform: translate3d(566.667%, 0, 0); opacity: 0; }
  100% { transform: translate3d(566.667%, 0, 0); opacity: 0; }
}
@keyframes ngLightBottom{
  0%, 52% { transform: translate3d(566.667%, 0, 0); opacity: 0; }
  62%  { transform: translate3d(566.667%, 0, 0); opacity: 1; }
  84%  { transform: translate3d(0, 0, 0);        opacity: 1; }
  94%  { transform: translate3d(0, 0, 0);        opacity: 0; }
  100% { transform: translate3d(0, 0, 0);        opacity: 0; }
}
@media (prefers-reduced-motion: reduce){
  .category-header.category-header::before,
  .category-header.category-header::after,
  .product-page :is(.size-guide, .frequently-bought) > h4::before,
  .product-page :is(.size-guide, .frequently-bought) > h4::after{
    animation: none !important;
    opacity: 0 !important;
  }
}


/* ─── 56F · The model application on a phone ────────────────────────────
   Found while checking every page's column: on the phone the application
   card's head and body lost their side padding (desktop 28px, phone 0),
   so every label and field ran into the card's own border; and each
   two-field row kept its desktop 20px column gap as a ROW gap once the
   fields stacked, on top of the field's 18px margin — 38px between two
   fields in one row, 18px between rows, an uneven ladder. The inset
   comes back at the phone's 14px, and the row gap goes, so every field
   is 18px from the next. */
@media (max-width: 768px){
  .f-card > .f-head{ padding-left: 14px !important; padding-right: 14px !important; }
  .f-card > .f-body{ padding-left: 14px !important; padding-right: 14px !important; }
  .f-card .fld-row{ gap: 0 !important; row-gap: 0 !important; }
}


/* ─── 56G · No site-wide smooth scrolling ───────────────────────────────
   THE SLIDE. Three route sheets — the lookbook's, the bag's and the 404's
   — declare `html { scroll-behavior: smooth }`. Route sheets are appended
   to <head> and never removed, so after one visit to any of them EVERY
   programmatic scroll on the site became an animation: the route change
   that should snap a new page to its top glided there instead, and a
   restored position was not placed but slid into, over about a second.
   That is "it will slide you back down to where you were before if you
   give it a second" — the restore had landed; the browser was animating
   it. Smooth scrolling where it is wanted is already asked for
   explicitly, per call (`scrollTo({behavior: "smooth"})` for in-page
   anchors and the tab bar's second tap), so the global one goes. */
html{ scroll-behavior: auto !important; }


/* ─── 56H · The search page's column has rails like every other ─────────
   `storefront-SearchRoute.css` sets `.search-page .main-content { border:
   none !important }`, which ties § 44's column rule on specificity and,
   loading later, wins — so on the phone the search results were the one
   column with no side rails, and read as a different surface. */
@media (max-width: 768px){
  .search-page .main-layout > .main-content{
    border-left: 1px solid var(--ng-line) !important;
    border-right: 1px solid var(--ng-line) !important;
    border-top: 0 !important;
    border-bottom: 0 !important;
    border-radius: 0 !important;
    background: var(--surface) !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   57 · 2.36.0 — THE LAUNCH CATALOGUE
   The eight pieces with production tech packs replace the placeholder
   catalogue. What the storefront needed for them: their colourways, a
   pre-order that says so, the pack's details and its measurements, and
   the pack's illustrations shown whole.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 57A · The colourways ───────────────────────────────────────────────
   The robe and both socks come in five pinstripes (the pack's COLOUR
   OPTION 01–05, its two stripe hexes each); the trucker hat is black with
   white front panels. A swatch is drawn the way the cloth is: two-tone
   stripes, or the hat's split. Unscoped and !important on purpose — the
   same class is a 10px card dot, a 36px product-page swatch and a compact
   card's option, and each of those carries a scoped default background. */
.color-pin-sky-blue, .compact-color-pin-sky-blue{ background: repeating-linear-gradient(90deg, #ADCBEB 0 25%, #D0E5F7 25% 50%) !important; }
.color-pin-pink,     .compact-color-pin-pink    { background: repeating-linear-gradient(90deg, #F699CD 0 25%, #FEC5E5 25% 50%) !important; }
.color-pin-red,      .compact-color-pin-red     { background: repeating-linear-gradient(90deg, #BC545B 0 25%, #9B1003 25% 50%) !important; }
.color-pin-black,    .compact-color-pin-black   { background: repeating-linear-gradient(90deg, #000000 0 25%, #414042 25% 50%) !important; }
.color-pin-lavender, .compact-color-pin-lavender{ background: repeating-linear-gradient(90deg, #9881BB 0 25%, #DFC5FE 25% 50%) !important; }
.color-black-white,  .compact-color-black-white { background: linear-gradient(135deg, #000000 0 50%, #FFFFFF 50% 100%) !important; }
/* a light swatch needs an edge on the dark page */
:is(.color-pin-sky-blue, .compact-color-pin-sky-blue, .color-pin-pink, .compact-color-pin-pink,
    .color-pin-lavender, .compact-color-pin-lavender, .color-black-white, .compact-color-black-white){
  border-color: rgba(255, 255, 255, .28) !important;
}

/* ─── 57B · Pre-order, said where you buy ────────────────────────────────
   Directly under the Pre-order button: the store's pre-order message
   (Store Settings → Pre-order message), so the promise is read before the
   tap that accepts it. */
.product-page .ng-preorder-note{
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 12px;
  padding: 11px 14px;
  border: 1px solid rgba(186, 85, 211, .35);
  border-radius: 2px;
  background: rgba(138, 43, 226, .07);
  color: rgba(255, 255, 255, .78);
  font-size: 11px;
  font-weight: 300;
  line-height: 1.6;
  letter-spacing: .2px;
}
.product-page .ng-preorder-note i{ color: var(--ng-orchid); font-size: 11px; line-height: 1.6; flex: 0 0 auto; }

/* ─── 57C · Details ──────────────────────────────────────────────────────
   Composition, construction, care and origin, line for line from the
   tech pack — the facts a shopper (or anyone checking the listing against
   the garment) looks for, under the description rather than in a drawer. */
.product-page .ng-pdp-details{
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--ng-line);
}
.product-page .ng-pdp-details-label{
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .4);
  margin-bottom: 10px;
}
.product-page .ng-pdp-details-list{
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 6px;
}
.product-page .ng-pdp-details-list li{
  position: relative;
  padding-left: 14px;
  font-size: 11px;
  font-weight: 300;
  line-height: 1.55;
  color: rgba(255, 255, 255, .72);
}
.product-page .ng-pdp-details-list li::before{
  content: "";
  position: absolute;
  left: 0;
  top: .62em;
  width: 5px;
  height: 1px;
  background: var(--ng-orchid);
}
/* the last line says what the pictures are — set apart, never hidden */
.product-page .ng-pdp-details-list li:last-child{ color: rgba(255, 255, 255, .5); font-style: italic; }

/* ─── 57D · A product's own size chart ───────────────────────────────────
   The tech packs' layout: a row per point of measure, a column per size —
   at most five columns, so it fits a phone without the ellipses seven
   would need. The unit sits in the corner; the note on how it was
   measured sits underneath. */
.product-page .size-guide .size-chart.size-chart-pom{ table-layout: fixed !important; }
.product-page .size-guide .size-chart.size-chart-pom :is(th, td):first-child{
  width: 38% !important;
  text-align: left !important;
  padding-left: 14px !important;
  white-space: normal !important;
  overflow: visible !important;
  text-overflow: clip !important;
}
.product-page .size-guide .size-chart.size-chart-pom td:first-child{ color: rgba(255, 255, 255, .62) !important; }
.product-page .size-guide .size-chart.size-chart-pom thead th:first-child{ text-transform: lowercase !important; letter-spacing: 1px !important; }
.product-page .size-guide .size-chart-note{
  margin: 10px 14px 4px;
  font-size: 11px;
  font-weight: 300;
  line-height: 1.6;
  color: rgba(255, 255, 255, .45);
  letter-spacing: .2px;
}
@media (max-width: 768px){
  .product-page .size-guide .size-chart-note{ margin: -8px 2px 18px; }
}

/* ─── 57E · The illustrations, whole ─────────────────────────────────────
   The catalogue's images are the production tech pack's illustrations on
   its own pale ground (#EEECF1). On the phone the main image is already
   contained; on the desktop it was cropped to fill the frame, which cut
   the robe's feather hem and both ends of the socks. Contained there too,
   for the catalogue's images only — a photograph uploaded later keeps the
   desktop's full-bleed treatment. */
@media (min-width: 769px){
  .product-page .main-product-image[src*="/media/shop/catalog/"]{
    object-fit: contain !important;
    padding: 10px !important;
    background: transparent !important;
  }
}

/* ─── 57F · Pre-order in the bag and at checkout ─────────────────────────
   A pre-order line used to read "In stock" in green, because the bag only
   counts stock when stock is being enforced. It now reads what it is, and
   the summary carries the store's pre-order message once, above the
   security line, in the bag and at checkout alike. */
.ci-stock.preorder{ color: var(--ng-orchid); opacity: .9; }
.ci-stock.preorder i{ color: var(--ng-orchid); }
.order-summary .os-preorder{
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 14px 0 0;
  padding: 10px 12px;
  border: 1px solid rgba(186, 85, 211, .35);
  border-radius: 2px;
  background: rgba(138, 43, 226, .07);
  color: rgba(255, 255, 255, .75);
  font-size: 11px;
  font-weight: 300;
  line-height: 1.6;
  text-align: left;
}
.order-summary .os-preorder i{ color: var(--ng-orchid); font-size: 11px; line-height: 1.6; flex: 0 0 auto; }

/* ─── 57G · Placeholder cards wherever a list has empty slots ────────────
   The shop grid always finished its last row with dashed placeholders
   (`.product-card-ghost`). With eight products most lists now end part-way
   along a row, so every product list does the same: New Arrivals, the
   home page's compact sections (an empty one shows a full row), "You May
   Also Like", Frequently Bought Together (four slots), search results,
   and an empty category's grid. Same recipe as the shop's: a dashed
   frame on a faint hatch, nothing inside, not focusable or clickable, and
   none of the card's own frame, scrim or hover. */
:is(.arrival-card-ghost, .compact-product-card-ghost, .recommendation-card-ghost, .search-product-card-ghost),
.product-page .frequently-bought .product-card-ghost,
.shop-page .products-grid > .product-card-ghost{
  cursor: default !important;
  pointer-events: none !important;
  border: 1px dashed rgba(138, 43, 226, .16) !important;
  border-radius: 2px !important;
  background: repeating-linear-gradient(135deg, rgba(138, 43, 226, .03) 0 10px, transparent 10px 20px) !important;
  box-shadow: none !important;
  transform: none !important;
  animation: none !important;
  opacity: 1 !important;
  filter: none !important;
}
:is(.arrival-card-ghost, .compact-product-card-ghost, .recommendation-card-ghost, .search-product-card-ghost)::before,
:is(.arrival-card-ghost, .compact-product-card-ghost, .recommendation-card-ghost, .search-product-card-ghost)::after,
.product-page .frequently-bought .product-card-ghost::before,
.product-page .frequently-bought .product-card-ghost::after,
.shop-page .products-grid > .product-card-ghost::before,
.shop-page .products-grid > .product-card-ghost::after{
  content: none !important;
  display: none !important;
}
/* a row of nothing but placeholders still has the cards' height */
.arrival-card-ghost{ min-height: 330px; }
.compact-product-card-ghost{ min-height: 200px; }
.recommendation-card-ghost{ min-height: 280px; }
.product-page .frequently-bought .product-card-ghost{ min-height: 280px; }
.shop-page .products-grid > .product-card-ghost{ min-height: 310px; }
.search-product-card-ghost{ min-height: 280px; }
@media (max-width: 768px){
  :is(.arrival-card-ghost, .compact-product-card-ghost, .recommendation-card-ghost, .search-product-card-ghost),
  .product-page .frequently-bought .product-card-ghost,
  .shop-page .products-grid > .product-card-ghost{ min-height: 270px; }
}
/* the FBT "+" joiner sits on real cards only */
.product-page .frequently-bought .product-card-ghost::before{ content: none !important; }

/* an empty category says so above its placeholders */
.shop-page .shop-empty.shop-empty-soon{
  padding: 18px 0 16px;
  min-height: 0;
}
.shop-page .shop-empty.shop-empty-soon p{ margin: 6px 0 0; }
.shop-page .shop-empty.shop-empty-soon + .products-grid{ margin-top: 0; }

/* ─── 57H · "One Size" in the size keys ──────────────────────────────────
   The size keys are fixed 26px squares for two-letter labels; the hats'
   "One Size" painted across its neighbour. A key whose label is longer
   than two characters grows to fit it (the phone already lets every key
   grow — § 31). */
.shop-page .shop-filter-grid .sfg-btn.sfg-btn-sm.ng-wide-key{ width: auto !important; padding: 0 8px !important; white-space: nowrap; }


/* ═══════════════════════════════════════════════════════════════════════
   50 · 2.38.0 — THE FOOTER LINE, AND THE TAB BAR'S ARTWORK
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 50A · The credit line is centred, the cookie link beneath it ──────
   On desktop the footer's last row is a flex row, and the "Cookie settings"
   button that ng-continuity.js appends joined it — pushing "All rights
   reserved" and "Designed with ❤ by DzinerStudios" off to the left. The row
   is now a two-column grid: the two lines centred side by side, the cookie
   link on its own row, centred directly beneath them. */
@media (min-width: 769px){
  footer .footer-bottom{
    display: grid !important;
    grid-template-columns: auto auto !important;
    justify-content: center !important;
    align-items: center !important;
    column-gap: 14px !important;
    row-gap: 4px !important;
    text-align: center !important;
  }
  footer .footer-bottom > p{ margin: 0 !important; text-align: center !important; }
  footer .footer-bottom > .ng-cookie-settings{
    grid-column: 1 / -1 !important;
    justify-self: center !important;
    margin: 6px 0 0 !important;
  }
}

/* ─── 50B · The phone tab bar wears the header's neon artwork ───────────
   The desktop header draws its links with the neon sprite (section 34); the
   phone tab bar still used Font Awesome outlines. `ng-tab-sprite.png` is
   cut from the same artwork — the house from the panel header sheet, the
   bag, open book, page and crowned figure from the header sheet — five
   columns, white on the top row and purple beneath, the same two-layer
   cross-fade as the header. The cell is chosen by the tab's address. */
.mobile-tab-bar .mobile-tab i{
  position: relative !important;
  display: block !important;
  width: 34px !important;
  height: 29px !important;
  font-size: 0 !important;
  line-height: 0 !important;
  -webkit-text-stroke: 0 !important;
  text-shadow: none !important;
  filter: none !important;
  animation: none !important;
  background-image: url(/assets/ng-tab-sprite.png?v=2380);
  background-repeat: no-repeat;
  background-size: 500% 200%;                 /* 5 columns, 2 rows */
  background-position: calc(var(--ng-tab-col, 0) * 25%) 0%;
  opacity: .78;
  transition: opacity .35s ease;
}
.mobile-tab-bar .mobile-tab i:before{ content: none !important; }
.mobile-tab-bar .mobile-tab i:after{
  content: "";
  position: absolute;
  inset: 0;
  background-image: url(/assets/ng-tab-sprite.png?v=2380);
  background-repeat: no-repeat;
  background-size: 500% 200%;
  background-position: calc(var(--ng-tab-col, 0) * 25%) 100%;   /* purple row */
  opacity: 0;
  transition: opacity .35s ease;
  pointer-events: none;
}
.mobile-tab-bar .mobile-tab.active i,
.mobile-tab-bar .mobile-tab[aria-current=page] i,
.mobile-tab-bar .mobile-tab:active i{ opacity: 1; }
.mobile-tab-bar .mobile-tab.active i:after,
.mobile-tab-bar .mobile-tab[aria-current=page] i:after,
.mobile-tab-bar .mobile-tab:active i:after{ opacity: 1; }
.mobile-tab-bar .mobile-tab[href="/"]                                  { --ng-tab-col: 0; }
.mobile-tab-bar .mobile-tab[href^="/shop"]                             { --ng-tab-col: 1; }
.mobile-tab-bar .mobile-tab[href^="/lookbook"]                         { --ng-tab-col: 2; }
.mobile-tab-bar .mobile-tab[href^="/blog"]                             { --ng-tab-col: 3; }
.mobile-tab-bar .mobile-tab:is([href^="/account"], [href^="/dashboard"]){ --ng-tab-col: 4; }
