/* ── Local fonts ────────────────────────────────────── */
@font-face {
  font-family: 'SpaceGrotesk';
  src: url('fonts/SpaceGrotesk-VariableFont_wght.ttf') format('truetype-variations');
  font-weight: 300 700;
  font-style: normal;
}

@font-face {
  font-family: 'LibreCaslonCondensed-Regular';
  src: url('fonts/LibreCaslonCondensed-Regular.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'LibreCaslonCondensed-Medium';
  src: url('fonts/LibreCaslonCondensed-Medium.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'LibreCaslonCondensed-SemiBold';
  src: url('fonts/LibreCaslonCondensed-SemiBold.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'LibreCaslonCondensed-BoldItalic';
  src: url('fonts/LibreCaslonCondensed-BoldItalic.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'LibreCaslonCondensed-Bold';
  src: url('fonts/LibreCaslonCondensed-Bold.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* Keyboard focus ring: only on Tab (focus-visible), never on clicks or taps.
   Text fields keep their own focus styles below. */
button:focus:not(:focus-visible) {
  outline: none;
}
a:focus-visible,
button:focus-visible,
select:focus-visible,
summary:focus-visible,
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid #2C56A0 !important;
  outline-offset: 2px;
}
.card-overlay :focus-visible {
  outline-color: #FED861 !important;
}

/* A field being typed into gets a solid yellow border -- except the search
   bars (top bar, mobile menu, home page), which keep their own. */
input:not([type="file"]):not([type="checkbox"]):not([type="radio"]):not(.header-search-input):not(.mobile-nav-search-input):not(.home-search-input):focus,
textarea:focus {
  outline: none;
  border-color: #FED861;
}
.header-search-input:focus,
.mobile-nav-search-input:focus,
.home-search-input:focus {
  outline: none;
}

:root {
  --cream: #FDFBF7;
  --dark: #102A71;
  --muted: #6F747D;
  --search-bg: #f9f4f1;
  --white: #ffffff;
  --roll-bg: #102A71;
  --overlay-bg: rgba(38, 26, 18, 0.84);
  --card-radius: 10px;
}


body {
  background-color: var(--cream);
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  /* ss03 = alternate lowercase y (single stroke, no overlap). ss02 =
     alternate g -- confirmed ON, not off: the font's BASE g (features off)
     is the pointy/quirky one, verified directly by turning ss02 off and
     seeing every g stay pointy. Correcting that assumption from an earlier
     pass here, which had this backwards. Both are genuinely opt-in
     features in this file (checked the GSUB table directly) -- there's no
     "default on in an older version" behavior at play, just which of the
     two glyphs happens to be the unfeatured default vs. the alternate.
     Set ONCE here rather than per-rule: font-feature-settings inherits,
     but does NOT merge -- any selector below that sets its OWN
     font-feature-settings (e.g. "tnum" on for tabular prices) will
     silently drop ss02/ss03 for that element unless it repeats them
     alongside its own features. There are no such per-rule overrides
     anywhere in this file today (checked) -- if one is ever added, it
     needs 'ss02' on, 'ss03' on folded into it, not just its own feature. */
  font-feature-settings: 'ss02' on, 'ss03' on;
  color: var(--dark);
  /* dvh = the visible height with the phone browser's bars showing; vh is
     the taller bars-hidden height, which made every page scroll by the
     bars' height on iPhone. vh stays as the fallback for older browsers. */
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: auto;
}

/* Form controls are a separate inheritance boundary in most browsers --
   confirmed as the explanation for "some y's fixed, some not": text set
   directly by CSS (headings, body copy, custom <div>/<span> "buttons")
   inherited body's font-feature-settings fine, but real <button>/<input>
   elements (LOCK, ROLL, Save/Cancel, the gender toggle, etc. are all real
   buttons) did NOT pick it up from body at all, same long-standing gap
   that already required this file to re-declare font-family on
   .save-modal-input rather than rely on inheritance. */
button, input, select, textarea, optgroup {
  font-feature-settings: 'ss02' on, 'ss03' on;
}
body.search-page {
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* ══════════════════════════════════════════════════════
   HOME PAGE
══════════════════════════════════════════════════════ */
/* ── Centered stacked control column: headline → gender toggle →
   search bar → category chip row ─────────────────────────────── */
.home-main {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 76px 24px 90px;
}

.home-headline {
  font-family: 'LibreCaslonCondensed-Regular', serif;
  font-weight: normal;
  font-size: 72px;
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: #102A71;
  max-width: 800px;
  margin-bottom: 30px;
}

/* ── Gender toggle: single segmented control ──────────────────── */
.gender-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border: 1px solid #F2F1EC;
  border-radius: 40px;
  background: #FCFDFF;
  margin-bottom: 20px;
}

/* Absolutely-positioned pill that slides to sit behind the active
   segment — JS (index.html) sets its left/width per segment on load
   and on every toggle, since segment widths differ (Women/Men/Unisex). */
.gender-toggle-pill {
  position: absolute;
  top: 3px;
  bottom: 3px;
  z-index: 0;
  border-radius: 30px;
  background: #FED861;
  transition: left 0.25s ease, width 0.25s ease;
}

.gender-toggle-seg {
  position: relative;
  z-index: 1;
  border: none;
  background: transparent;
  padding: 7px 18px;
  border-radius: 30px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #6F747D;
  cursor: pointer;
  transition: color 0.15s ease;
}

.gender-toggle-seg.is-active {
  color: #102A71;
}

/* ── Search bar ────────────────────────────────────────────────── */
.home-search-form {
  width: 100%;
  max-width: 700px;
}

.home-search-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: #FCFDFF;
  border: 1.5px solid #2C56A0;
  border-radius: 50px;
  padding: 18px 20px 18px 20px;
  /* Bottom-only now, not the offset bottom-right "sticker" shadow. */
  box-shadow: 0 2px 0 #2C56A0;
  cursor: text;
}

.home-search-icon {
  flex-shrink: 0;
}

/* Search placeholder + typed text */
.home-search-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  letter-spacing: 0;
  color: var(--dark);
  outline: none;
  text-align: left;
}

.home-search-input::placeholder {
  color: #6F747D;
}

.home-cam-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0;
  color: #102A71;
  opacity: 0.75;
  cursor: pointer;
  transition: opacity 0.15s;
}

.home-cam-btn:hover {
  opacity: 1;
}

/* Only a layout box on mobile (bottom-anchored controls); on desktop its
   children lay out as direct children of .home-main. */
.home-controls {
  display: contents;
}

/* ── Category chip row — wraps onto more lines when it doesn't fit ── */
.category-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 22px;
  max-width: 700px;
  width: 100%;
  justify-content: center;
}

/* Sized so all seven home chips fit on one line inside the row's 700px cap
   (which matches the search bar above it); narrower screens wrap. */
.category-chip[hidden] { display: none; }

.category-chip {
  flex-shrink: 0;
  /* Same fill/outline as .gender-btn (the filter row's unselected
     gender buttons) -- same border width too, 1px not 1.5px. */
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  padding: 9px 13px;
  border-radius: 40px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: #102A71;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s ease;
}

.category-chip:active {
  background: #FED861;
}

@media (hover: hover) {
  .category-chip:hover {
    background: #FED861;
  }
}

/* ══════════════════════════════════════════════════════
   BROWSE PAGE — photo cards, four per row
══════════════════════════════════════════════════════ */
@font-face {
  font-family: 'LibreCaslonCondensed-Italic';
  src: url('fonts/LibreCaslonCondensed-Italic.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

.browse-main {
  --gap: clamp(10px, 1.1vw, 24px);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 18px 24px 90px;
}

.browse-headline {
  font-family: 'LibreCaslonCondensed-BoldItalic', serif;
  font-size: 45px;
  line-height: 1.12;
  color: #102A71;
  margin-bottom: 24px;
}

/* Same width as the profile page's collection previews: four ~220px
   cards per row. */
.browse-grid {
  width: 100%;
  max-width: calc(4 * 220px + 3 * var(--gap));
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--gap);
  margin-top: 34px;
}

.browse-grid--all {
  margin-top: 48px;
}

.browse-card {
  position: relative;
  display: block;
  aspect-ratio: 4 / 5;
  border-radius: var(--card-radius);
  overflow: hidden;
  background: #F2F1EC;
  text-decoration: none;
}

.browse-card-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The search page's card overlay colour, from 50% at the bottom to clear
   at the middle. */
.browse-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(20, 29, 41, 0.5) 0%, rgba(20, 29, 41, 0) 50%);
  pointer-events: none;
}

.browse-card-label {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 12px;
  z-index: 1;
  font-family: 'LibreCaslonCondensed-Italic', serif;
  font-size: 24px;
  line-height: 1.1;
  color: #FCFDFF;
}

/* ══════════════════════════════════════════════════════
   RESULTS PAGE — HEADER
══════════════════════════════════════════════════════ */
/* ── Top navigation bar ─────────────────────────────── */
.results-header {
  background: var(--cream);
  border-bottom: 1px solid #F2F1EC;
  position: relative;
  z-index: 10;
  margin-bottom: clamp(40px, calc(3dvh + 10px), 60px);
}

/* Less space above the collection title than the shared default above --
   the room it gives up is added below the tabs divider instead (see
   #allProductsPanel .products-section / .outfits-scroll). */
body.collection-page .results-header {
  margin-bottom: clamp(20px, calc(3dvh - 10px), 40px);
}

.header-nav-row {
  display: flex;
  align-items: center;
  height: 56px;
  padding: 0 clamp(40px, 2.08vw, 60px);
  /* .nav-title below is position:absolute + left:50% -- without this, its
     containing block falls through to .results-header (the nearest
     positioned ancestor), which is full header width and knows nothing
     about this row's own side padding. That let the centered title sit
     past where the padding puts the search bar / profile icon, reading as
     the title getting cut off/overlapped near the edges on narrower
     windows. Anchoring it to this row instead centers it against the same
     padded box the other header controls live in. */
  position: relative;
}

.nav-title {
  font-family: 'LibreCaslonCondensed-Regular', serif;
  font-size: 32px;
  color: #102A71;
  white-space: nowrap;
  letter-spacing: -0.03em;
  cursor: pointer;
  position: absolute;
  left: 50%;
  top: 50%;
  /* top/left made explicit on both axes (was horizontal-only, relying on
     "static position" fallback for vertical) now that this centers against
     .header-nav-row instead of .results-header -- keeps it correctly
     vertically centered regardless of any height difference between the
     two, rather than depending on that fallback behavior lining up. */
  transform: translate(-50%, -50%);
}

.nav-left {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1vw, 16px);
  flex: 1;
}

.nav-right {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1vw, 16px);
  flex: 1;
  justify-content: flex-end;
}

.nav-link {
  position: relative;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: #102A71;
  text-decoration: none;
  padding: 3px 9px;
  border-radius: 7px;
  cursor: pointer;
  background: none;
  border: none;
  white-space: nowrap;
  letter-spacing: 0;
}

/* ── Mobile hamburger menu ───────────────────────────── */
.mobile-menu-btn {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
  margin-right: 4px;
  position: relative;
  z-index: 1001;
}

@media (max-width: 640px) {
  .mobile-menu-btn {
    display: flex;
    align-items: center;
    justify-content: center;
  }
}

/* Drawer: 85% wide, sliding in from the left over a dimmed page. Closed,
   it's hidden once the slide finishes and casts no shadow -- parked
   off-screen, its shadow otherwise bleeds onto the page's left edge. */
.mobile-nav-overlay {
  position: fixed;
  inset: 0 auto 0 0;
  width: 85%;
  background: #FFFDF8;
  z-index: 1000;
  transform: translateX(-100%);
  visibility: hidden;
  transition: transform 0.3s ease, box-shadow 0.3s ease, visibility 0s linear 0.3s;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  padding: 62px 24px 24px;
}

.mobile-nav-overlay.is-open {
  transform: translateX(0);
  visibility: visible;
  box-shadow: 4px 0 24px rgba(20, 29, 41, 0.12);
  transition: transform 0.3s ease, box-shadow 0.3s ease, visibility 0s;
}

@media (min-width: 641px) {
  .mobile-nav-overlay,
  .mobile-nav-backdrop {
    display: none;
  }
}

.mobile-nav-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 29, 41, 0.45);
  z-index: 999;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.mobile-nav-backdrop.is-open {
  opacity: 1;
  pointer-events: auto;
}

/* Same type as the home page's category chips (.category-chip). */
.mobile-nav-section-label {
  margin: 22px 0 2px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: #6F747D;
}

/* Same as a category row, bolded; first in the shop list. */
.mobile-nav-browse {
  display: block;
  padding: 10px 0;
  border-bottom: 1px solid #F2F1EC;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 600;
  font-size: 18px;
  letter-spacing: 0;
  color: #102A71;
  text-decoration: none;
}

.mobile-nav-search-row {
  position: relative;
  margin-bottom: 10px;
}

.mobile-nav-cam-btn {
  position: absolute;
  top: 50%;
  right: 12px;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  background: none;
  border: none;
  color: #102A71;
  opacity: 0.75;
  cursor: pointer;
}

.mobile-nav-search-input {
  width: 100%;
  height: 34px;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  border-radius: 999px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  letter-spacing: 0;
  color: #102A71;
  padding: 0 44px 0 18px;
  outline: none;
}

.mobile-nav-search-input::placeholder {
  color: #6F747D;
}

.mobile-nav-links {
  display: flex;
  flex-direction: column;
}

.mobile-nav-link {
  display: block;
  width: 100%;
  text-align: left;
  text-decoration: none;
  background: none;
  border: none;
  border-bottom: 1px solid #F2F1EC;
  padding: 13px 0;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0;
  color: #102A71;
  cursor: pointer;
}

.mobile-nav-link[hidden] {
  display: none;
}

.mobile-nav-links .mobile-nav-link {
  font-size: 18px;
  padding: 10px 0;
}

.mobile-nav-links .mobile-nav-link:last-child {
  border-bottom: none;
}

/* Pinned to the bottom of the drawer. */
.mobile-nav-account {
  margin-top: auto;
  display: flex;
  flex-direction: column;
}

.mobile-nav-account .mobile-nav-link {
  border-bottom: none;
  font-size: 13px;
}

/* My Profile and Log out sit close to the divider between them. */
#mobileNavAccount {
  padding-bottom: 7px;
}

.mobile-nav-account .mobile-nav-link + .mobile-nav-link {
  border-top: 1px solid #F2F1EC;
  padding-top: 7px;
}

/* ── Expandable search bar ──────────────────────────── */
.header-search-row {
  position: absolute;
  left: 0;
  width: 100%;
  background: var(--cream);
  border-bottom: 1px solid #F2F1EC;
  z-index: 9;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.35s ease, box-shadow 0.35s ease;
  display: flex;
  flex-direction: column;
}

/* Same shadow as the login card, so the open row reads as over the page;
   clipped at the top edge so it only falls below the row. */
.header-search-row.is-open {
  max-height: 110px;
  box-shadow: 0 2px 18px rgba(87, 127, 188, 0.08);
  clip-path: inset(0 -40px -40px -40px);
}

.header-search-inner {
  height: 110px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 clamp(40px, 2.08vw, 60px);
  position: relative;
  opacity: 0;
  transition: opacity 0.2s ease 0.08s;
}

.header-search-row.is-open .header-search-inner {
  opacity: 1;
}

/* Same width as the home page's search bar; centred on its own, with the
   close button hanging off its right edge. */
.header-search-field {
  position: relative;
  width: min(700px, 100%);
}

.header-search-input {
  width: 100%;
  height: 38px;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  border-radius: 999px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0;
  color: #102A71;
  padding: 0 48px 0 18px;
  outline: none;
}

.header-search-input::placeholder {
  color: #6F747D;
  font-weight: 400;
}

.header-search-close {
  position: absolute;
  top: 50%;
  left: calc(100% + 4px);
  transform: translateY(-50%);
  background: none;
  border: none;
  cursor: pointer;
  color: #6F747D;
  display: flex;
  align-items: center;
  padding: 4px;
  transition: color 0.2s;
}

.header-search-close:hover {
  color: #102A71;
}

/* ── Page title (search query / outfit name) ────────── */
.page-title {
  /* A real italic face, not a synthetic skew -- LibreCaslonCondensed-Bold
     has no italic variant of its own; -BoldItalic is a separate font
     file (see the @font-face block up top), same pattern already used
     for the roll button. */
  font-family: 'LibreCaslonCondensed-BoldItalic', serif;
  font-size: 40px;
  color: #102A71;
  letter-spacing: 0;
  white-space: nowrap;
  flex-shrink: 0;
}

.page-title-quote {
  font-family: 'LibreCaslonCondensed-Bold', serif;
  color: #FED861;
  font-size: 40px;
}


/* ══════════════════════════════════════════════════════
   RESULTS PAGE — FILTERS ROW
   Shares the exact same box model as .outfit-detail-header (see OUTFIT
   DETAIL PAGE section below) so the title and the products grid beneath
   it land in the same place on every page — search, generator, explore,
   outfit summary, wishlist summary.
══════════════════════════════════════════════════════ */
.filters-row,
.outfit-detail-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  min-height: 80px;
  padding: 8px clamp(40px, 2.08vw, 60px) clamp(23px, calc(1.5dvh + 15px), 33px);
}

.filters-row > .page-title,
.outfit-detail-header > .edit-name-container { align-self: flex-start; }

/* "Showing results for ..." after a typo correction. */
.query-correction {
  margin: -12px 0 16px;
  padding: 0 clamp(40px, 2.08vw, 60px);
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 14px;
  color: var(--muted);
}
.query-correction strong { color: var(--dark); font-weight: 600; }
.query-correction button {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--dark);
  text-decoration: underline;
  cursor: pointer;
}
@media (max-width: 640px) {
  .query-correction { margin: -4px 0 12px; padding: 0 16px; }
}

.filters-row > .price-gender-group,
.outfit-detail-header > .coll-header-actions,
.outfit-detail-header > .price-gender-group { align-self: center; }

/* The applied-to-all attribute pills sit right after the outfit title, even
   when the filters wrap onto their own line (space-between would push the
   pills to the far right). The filters stay right via their margin-left: auto.
   Desktop only: on mobile the filters drawer is position: fixed and relies on
   space-between to sit off-screen. */
@media (min-width: 641px) {
  .outfit-detail-header.cg-title-row { justify-content: flex-start; }
}

.price-gender-group {
  display: flex;
  align-items: flex-end;
  gap: 20px;
  margin-left: auto;
  /* Price/gender/brand must never wrap or get covered -- if the row is
     tight, .locked-attrs-summary is what gives way (see below), not this. */
  flex-shrink: 0;
  flex-wrap: nowrap;
}

/* Locked/typed attribute pills (e.g. from clicking an attribute dot) --
   same row as price/gender/brand, not forced onto a separate line below
   them. Wraps naturally via the parent's own flex-wrap if it genuinely
   doesn't fit, same as any other filter-row content. */
.locked-attrs-summary {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.filter-col {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.filter-col-fields {
  display: flex;
  align-items: center;
  gap: 4px;
}

.price-input {
  width: 46px;
  height: 26px;
  padding: 0 7px;
  border: 1px solid #F2F1EC;
  border-radius: 13px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  letter-spacing: 0;
  color: #102A71;
  text-align: center;
  background: #FCFDFF;
  outline: none;
  -moz-appearance: textfield;
}
.price-input::placeholder {
  color: #6F747D;
}
.price-input::-webkit-outer-spin-button,
.price-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
/* The "$" wrapper only exists for the mobile drawer (see media query
   below); on desktop it drops out of layout so the row is unchanged. */
.price-input-wrap {
  display: contents;
}
.price-input-prefix {
  display: none;
}

.price-search-btn {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  display: flex;
  align-items: center;
  color: var(--muted);
  transition: color 0.2s;
  margin-left: 2px;
}

.price-search-btn:hover {
  color: var(--dark);
}

/* ── Profile icon, far right of the top bar (replaces Purchases) ──
   Sits outside .nav-right so it stays visible even on mobile, where
   .nav-left/.nav-right are hidden in favor of the hamburger drawer. */
.profile-nav-link {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #102A71;
  opacity: 0.85;
  transition: opacity 0.15s;
  flex-shrink: 0;
  margin-left: clamp(12px, 1vw, 16px);
}

.profile-nav-link:hover,
.profile-nav-link.is-active {
  opacity: 1;
}

/* Logged-in state: a small circle with the user's initial, same footprint
   as the icon so the header doesn't shift on sign-in. */
.profile-nav-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #2C56A0;
  color: #FCFDFF;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 500;
  font-size: 10px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.profile-nav-avatar-img {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  object-fit: cover;
  display: inline-block;
  vertical-align: middle;
}

/* ── "Cart" link (header.js), visible at every width ── */
.bag-nav-link {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: clamp(12px, 1vw, 16px);
}

/* ── Account dropdown (caret next to the profile avatar) ──
   Only shown logged in -- logged out, the avatar slot is just the plain
   login link with no caret/menu, since there's nothing to open yet. */
.account-nav-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

/* Logged-out account links, in place of the avatar. */
.auth-nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: clamp(12px, 1vw, 16px);
}

.account-caret-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  padding: 4px;
  margin-left: 6px;
  color: #102A71;
  cursor: pointer;
  transition: opacity 0.15s;
}

.account-caret-btn:hover { opacity: 1; }

/* `.account-caret-btn { display: flex }` above has the same specificity as
   the browser's default `[hidden] { display: none }` rule, and author CSS
   always wins over the UA stylesheet regardless of source order -- so
   without this, header.js's `caret.hidden = !user` had no visible effect
   and the caret showed even logged out. This rule (attribute + class, one
   level more specific) restores hidden actually meaning hidden. */
.account-caret-btn[hidden] { display: none; }

.kebab-divider {
  height: 1px;
  background: #F2F1EC;
  margin: 6px 6px;
  flex-shrink: 0;
}

/* ── Sync/status toast (store.js) ─────────────────── */
.tca-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translate(-50%, 12px);
  background: var(--dark);
  color: var(--cream);
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  padding: 11px 18px;
  border-radius: 50px;
  opacity: 0;
  transition: opacity 0.25s, transform 0.25s;
  z-index: 2000;
  pointer-events: none;
  max-width: calc(100vw - 32px);
}

.tca-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* ── Camera button inside the header search bar ─────── */
.header-cam-btn {
  position: absolute;
  top: 50%;
  right: 12px;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  color: #102A71;
  opacity: 0.75;
  transition: opacity 0.15s;
}
.header-cam-btn:hover { opacity: 1; }

.header-search-close {
  color: #102A71;
}

/* Search-bar and product-card icons are drawn with 1px lines at any size. */
.header-cam-btn svg *,
.header-search-close svg *,
.home-search-icon *,
.home-cam-btn svg *,
.save-btn svg *,
.bag-btn svg *,
.img-search-btn svg *,
.search-icon-btn svg *,
.add-icon-btn svg *,
.remove-icon-btn svg * {
  vector-effect: non-scaling-stroke;
  stroke-width: 1px;
}

/* ── Upload popup ────────────────────────────────────── */
/* Above the mobile nav drawer (1000), which can also open it. */
.upload-popup {
  position: fixed;
  z-index: 1100;
  background: #FDFBF7;
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.13);
  padding: 20px 25px 25px;
  width: 360px;
  max-width: 90vw;
  overflow-y: auto;
}

.upload-popup-title {
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 15px;
  color: #102A71;
  margin: 0 0 12px;
  font-weight: normal;
}

/* ── Drop zone ───────────────────────────────────────── */
.upload-drop-zone {
  position: relative;
  width: 100%;
  min-height: 200px;
  background: #FDFBF7;
  border: 2px dashed #7E838C;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
  overflow: hidden;
}
.upload-drop-zone.is-drag-over {
  border-color: #2C56A0;
  background: #e8ecf5;
}

/* With a photo in it, the dashed outline wraps the photo itself. */
.upload-drop-zone.has-image {
  display: block;
  width: fit-content;
  max-width: 100%;
  min-height: 0;
  margin: 0 auto;
  cursor: default;
}

.upload-drop-zone.has-image .upload-preview {
  display: block;
  border-radius: 0;
}

.upload-drop-hint {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 14px;
  color: #7E838C;
  pointer-events: none;
  text-align: center;
  padding: 20px;
}

.upload-preview {
  max-height: 260px;
  max-width: 100%;
  object-fit: contain;
  border-radius: 10px;
  pointer-events: none;
}
.upload-preview[hidden] { display: none; }

/* ── Crop drawing layer ──────────────────────────────── */
.upload-dots-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* ── Hint below drop zone ────────────────────────────── */
.upload-select-hint {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 14px;
  color: #102A71;
  text-align: center;
  margin: 10px 0 0;
}

/* ── Action buttons ──────────────────────────────────── */
/* Same pills as the save modal's buttons, both in its Save blue. */
.upload-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 20px;
}
.upload-actions[hidden] { display: none; }

.upload-action-btn {
  flex: 1;
  height: 44px;
  background: #2C56A0;
  border: none;
  border-radius: 22px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: #FFFFFF;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  letter-spacing: 0;
}
.upload-action-btn:hover { background: #102A71; color: #FFFFFF; }
.upload-action-btn:disabled { opacity: 0.5; pointer-events: none; }

/* ── Mobile FILTERS trigger + accordion popup ─────────
   Hidden on desktop by default; shown/restyled by the mobile media
   query at the end of this file. Desktop keeps the classic always-
   visible price/gender/style row untouched. */
.filters-trigger {
  display: none;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 300;
  font-size: 13px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #102A71;
  text-decoration: underline;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 0;
}

/* Close X, top-right of the fullscreen filters popup. Hidden on
   desktop (base rule) — the popup itself is a normal always-visible
   row there, not something that needs closing. */
.filters-popup-close {
  display: none;
  position: absolute;
  top: 18px;
  right: clamp(20px, 5vw, 32px);
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px;
  z-index: 501;
}

/* Reset so this <button> is visually identical to the plain
   .filter-label span it replaces, until mobile styles kick in. */
.filter-toggle-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 0;
  cursor: default;
  font: inherit;
  text-align: left;
}

.filter-arrow {
  display: none;
}

/* Mobile-only (see the FILTERS drawer media query). */
.filters-backdrop {
  display: none;
}

/* Desktop keeps the plain min/max text inputs; the slider only
   renders on mobile (see media query below). */
.price-range-slider {
  display: none;
}

/* ── New filter bar ────────────────────────────────── */
.filter-label {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 300;
  font-size: 12px;
  letter-spacing: 0;
  color: #102A71;
  white-space: nowrap;
  flex-shrink: 0;
}

.gender-btn {
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  border-radius: 13px;
  height: 26px;
  padding: 0 7px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  letter-spacing: 0;
  color: #102A71;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  transition: background 0.15s, border-color 0.15s;
}

.gender-btn.active {
  background: #FED861;
  /* Same 1px width as the unselected state, just recoloured to match the
     fill instead of removed -- .gender-btn is auto-width (shrink-to-fit),
     so box-sizing: border-box doesn't protect it the way it would an
     explicitly-sized element: dropping the border outright shrunk the
     button by 2px (1px each side) the instant it was selected. */
  border: 1px solid #FED861;
  color: #102A71;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 600;
}

.style-search-input {
  width: clamp(142px, 12vw, 182px);
  height: 30px;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  border-radius: 15px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0;
  color: #102A71;
  padding: 0 10px;
  outline: none;
}

.style-search-input::placeholder {
  color: #6F747D;
}

/* ══════════════════════════════════════════════════════
   BRAND FILTER (search.html + outfit-generator.html)
   Same height/fill/outline as .gender-btn/.price-input so it reads as
   one filter row, not a bolted-on extra. Rightmost column always --
   see .brand-filter-col below.
══════════════════════════════════════════════════════ */
.brand-filter-col {
  /* Never the thing that wraps/shrinks when the row is tight -- price,
     gender, and brand together are meant to stay put; see .filters-row
     restructuring for what gives way instead. */
  flex-shrink: 0;
}

.brand-filter-fields {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
}

.brand-chips {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
}

/* Flex `gap` on .brand-filter-fields still reserves space before this
   even when it has no chips in it, pushing the field/circle button
   further right than PRICE/GENDER's own content -- collapse it out of
   the layout entirely when empty instead. */
.brand-chips:empty {
  display: none;
}

.brand-search-wrap {
  position: relative;
  flex-shrink: 0;
}

/* Fits the "Filter by brand" placeholder (91.6px in this font), its
   padding, border and the caret. */
.brand-search-input {
  width: 120px;
  height: 26px;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  border-radius: 13px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  letter-spacing: 0;
  color: #102A71;
  padding: 0 10px;
  outline: none;
}

.brand-search-input::placeholder {
  color: #6F747D;
}

/* Reuses .prefs-suggestions/.prefs-suggestion's exact look (same
   scrollable dropdown already used by the Favorite Brands field on the
   preferences page) rather than a second copy of the same pattern. Now a
   sibling of both the search field and the collapsed circle button (see
   .brand-filter-fields), since either one can open it. */
.brand-filter-fields .prefs-suggestions {
  left: auto;
  right: 0;
  width: clamp(180px, 15vw, 240px);
  /* Overrides the base .prefs-suggestions' own overflow-y/max-height --
     this box now also holds the collapsed-state typing field (see
     #brandDropdownSearchInput below) as a fixed header above the actual
     scrollable list, rather than being the scrollable element itself.
     display stays `none` by default and only switches to `flex` while
     NOT [hidden] -- a bare `display: flex` here has higher specificity
     (two classes) than the UA stylesheet's `[hidden] { display: none }`
     (one attribute selector), so without this split the dropdown rendered
     as a permanently-visible empty box even before the user ever touched
     the brand filter. */
  display: none;
  flex-direction: column;
  overflow: hidden;
  max-height: none;
  /* Scrollbar sits closer to the box's own edge than the base
     .prefs-suggestions' 6px padding would put it -- only the right side
     needs less inset, the list's own content keeps its usual left/top/
     bottom padding via the base rule. */
  padding-right: 2px;
}

.brand-filter-fields .prefs-suggestions:not([hidden]) {
  display: flex;
}

/* Plain underline field at the top of the brand dropdown, shown only while
   the brand-filter row is collapsed to its circle icon (see
   syncBrandFieldMode) -- there's no room for the full pill-shaped
   .brand-search-input in that state, but typing to filter the list
   shouldn't disappear along with it. */
.brand-dropdown-search-input {
  flex-shrink: 0;
  width: 100%;
  background: none;
  border: none;
  border-bottom: 1px solid #F2F1EC;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  color: var(--dark);
  padding: 4px 2px 8px 8px;
  margin-bottom: 4px;
  outline: none;
}

.brand-dropdown-search-input::placeholder {
  color: var(--muted);
}

.prefs-suggestions-list {
  overflow-y: auto;
  max-height: 190px;
}

/* Moved onto the base .prefs-suggestions class (was scoped to
   .brand-filter-fields .prefs-suggestions only) so every dropdown that
   reuses this component gets the same treatment -- login.html's
   onboarding brand picker, outfit-generator.html's two brand filters, and
   preferences.html's own brand field were all still showing the browser's
   plain default scrollbar. Same width/thumb as .card-attrs-scroll (the
   product overlay's own scroll region) -- that one uses a translucent
   near-white thumb since it sits on a dark overlay; this one uses the
   muted gray at the same opacity instead, since it sits on a light
   background, but both are the same actual width. */
.prefs-suggestions,
.prefs-suggestions-list {
  scrollbar-width: thin;
  scrollbar-color: rgba(126, 131, 140, 0.45) transparent;
}
.prefs-suggestions::-webkit-scrollbar,
.prefs-suggestions-list::-webkit-scrollbar {
  width: 6px;
}
.prefs-suggestions::-webkit-scrollbar-track,
.prefs-suggestions-list::-webkit-scrollbar-track {
  background: transparent;
}
.prefs-suggestions::-webkit-scrollbar-thumb,
.prefs-suggestions-list::-webkit-scrollbar-thumb {
  background: rgba(126, 131, 140, 0.45);
  border-radius: 3px;
}

/* Collapsed state once the first brand is picked -- the search field is
   replaced by this circular icon (same fill/border as the field it
   replaces) while >=1 brand is picked; clicking it opens the same
   dropdown directly (browsing, not typing -- there's no field left to
   type into while collapsed).
   `display: flex` here was silently overriding the browser's own
   `[hidden] { display: none }` rule -- equal specificity, and this
   author rule loads later in the cascade, so it won -- which meant the
   circle rendered even while its `hidden` attribute was set, showing
   alongside the search field instead of replacing it. The `:not([hidden])`
   qualifier keeps `display: flex` from applying whenever `hidden` is set. */
.brand-collapsed-btn:not([hidden]) {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  color: #6F747D;
}

/* ══════════════════════════════════════════════════════
   PRODUCT CARDS SCROLL
══════════════════════════════════════════════════════ */
.products-section {
  padding: 0;
  width: 100%;
  overflow: visible;
  position: relative;
}

body.search-page .products-section {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* overflow-y:auto still triggers the browser's native trackpad
     rubber-band/bounce feel even when content doesn't actually overflow
     (there's already a 120px buffer below the cards -- see
     .products-scroll-wrapper -- specifically so this almost never has to
     scroll for real). overscroll-behavior only affects that boundary
     bounce, not genuine scrolling when a card's content truly is taller
     than the available space, so real spillover still scrolls normally. */
  overscroll-behavior-y: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* .roll-section is position:fixed, so this box's own height runs past
     it, all the way to the viewport edge -- reserve its footprint (bottom
     offset + button height, same values as .roll-section/.roll-btn) so
     genuine overflow-scroll content never ends up hidden behind it. */
  padding-bottom: calc(clamp(23px, calc(4.5dvh - 5px), 47px) + 52px);
}

body.search-page .products-section::-webkit-scrollbar {
  display: none;
}

/* Same reservation as search-page above, minus the scroll-container
   properties -- the generator's product row doesn't scroll vertically
   the way search's multi-row grid does, but it sits above the exact same
   position:fixed .roll-section, so on a short viewport a tall product
   card can run past the fixed bar and get visually covered by it
   without this. Confirmed directly: at 1440x500 with a populated slot,
   the roll bar's top edge (420px) sat well above the card's bottom edge
   (560px) with no reservation here. */
body.generator-page .products-section {
  padding-bottom: calc(clamp(23px, calc(4.5dvh - 5px), 47px) + 52px);
}

.cg-status-text {
  display: none;
}

.cg-status-text.is-visible {
  display: block;
  position: absolute;
  left: 40px;
  right: auto;
  top: 50%;
  transform: translateY(-50%);
  text-align: left;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: var(--muted);
  pointer-events: none;
  z-index: 5;
}

/* Full-width scroll band — only this element scrolls horizontally. */
.products-scroll-wrapper {
  width: 100vw;
  overflow-x: auto;
  /* Small anti-bounce cushion only -- the roll button's own footprint is
     already reserved by .products-section's padding-bottom above, so this
     doesn't also need to be big enough to clear the button itself. */
  padding-bottom: 24px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.products-scroll-wrapper::-webkit-scrollbar {
  display: none;
}

.products-track {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: flex-start;
  width: max-content;
  padding: 0 clamp(40px, 2.08vw, 60px);
  gap: clamp(10px, 1.1vw, 24px);
  transition: opacity 0.3s ease;
}

.product-slot-wrapper {
  flex: 0 0 clamp(220px, 18vw, 350px);
  display: flex;
  flex-direction: column;
}

/* Desktop: redundant with the trailing gap-insert button after the
   last card (see .cg-slot-gap) — mobile shows this instead, below. */
.cg-append-tile {
  display: none;
}

/* ══════════════════════════════════════════════════════
   MOBILE — collection summary: 2 per row, rows grow
   as products are added (.is-summary-grid, set in
   collection.html only — search.html and
   outfit-generator.html keep their horizontal scroll track)
══════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  .products-scroll-wrapper.is-summary-grid {
    width: 100%;
    overflow-x: hidden;
  }

  .products-scroll-wrapper.is-summary-grid .products-track {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    width: 100%;
    padding: 0 16px;
    gap: 18px 10px;
  }

  .products-scroll-wrapper.is-summary-grid .product-slot-wrapper {
    flex: none;
    width: 100%;
  }
}

/* Collection cards match search's grid on mobile: photo only, with a
   swipeable carousel (dots below); details live in the panel. Desktop
   keeps the text below and has no carousel or panel-footer icons. */
body.collection-page .carousel-nav-below,
body.collection-page .coll-panel-icons {
  display: none;
}

@media (max-width: 640px) {
  .products-scroll-wrapper.is-summary-grid .product-card {
    max-height: none;
    touch-action: pan-y pinch-zoom;
  }

  .products-scroll-wrapper.is-summary-grid .product-below-info .product-name-below,
  .products-scroll-wrapper.is-summary-grid .product-below-info .product-meta-below {
    display: none;
  }

  body.collection-page .carousel-nav-below {
    display: flex;
    justify-content: center;
    margin-top: 6px;
    min-height: 6px;
  }

  body.collection-page .carousel-nav-below .carousel-dots {
    gap: 4px;
  }

  body.collection-page .carousel-nav-below .carousel-dot {
    width: 4px;
    height: 4px;
  }

  body.collection-page .carousel-nav-below .carousel-dot.is-current {
    width: 5px;
    height: 5px;
  }

  .product-card.is-overlay-active .coll-panel-icons {
    display: flex;
  }

  .product-card.is-overlay-active .save-btn-corner {
    display: none;
  }

  /* Outfits scroll as one normal page (no one-per-screen snapping), with
     the same card sizes as everywhere else. */
  body.collection-page .outfits-scroll {
    height: auto;
    overflow: visible;
    scroll-snap-type: none;
  }

  body.collection-page .outfit-section {
    height: auto;
    margin-bottom: 40px;
  }

  body.collection-page .outfit-section .products-section,
  body.collection-page .outfit-section .products-scroll-wrapper {
    overflow: visible;
  }

  body.collection-page .outfit-section-header {
    padding-top: 16px;
    padding-bottom: 16px;
  }

  body.collection-page #allProductsPanel .products-section {
    padding-top: 16px;
  }

  body.collection-page #allProductsPanel .products-scroll-wrapper {
    padding-bottom: 16px;
  }

  /* Up to four products fit without scrolling: cards are sized so both
     rows fit the screen, the same way search sizes its grid. 225px = the
     space above the grid (163) + dots and row gap (42) + bottom margin
     (20); the divisor is 2 rows x the card's 1.5 ratio. */
  body.collection-page #allProductsPanel .products-track {
    grid-template-columns: repeat(2, min(calc((100% - 10px) / 2), calc((100dvh - 225px) / 3)));
    justify-content: center;
  }

  body.collection-page .outfit-section .outfit-section-heading {
    font-size: 22px;
  }

  /* Empty states: one line, centered on the screen. */
  body.collection-page .products-track > .no-results {
    grid-column: 1 / -1;
    white-space: nowrap;
  }

  body.collection-page #allProductsPanel .no-results,
  body.collection-page #noOutfits {
    min-height: calc(100dvh - 260px);
    white-space: nowrap;
  }

  body.collection-page .outfit-section .no-results {
    min-height: 160px;
  }

  /* Header, tabs and outfit controls at search's sizes. */
  body.collection-page .outfit-detail-heading {
    font-size: 30px;
    line-height: 1.2;
  }

  body.collection-page .kebab-btn {
    width: 36px;
    height: 36px;
    margin-top: 0;
  }

  /* Outfit title on the left, its three buttons on the right of the same
     row; a long title gives way first. */
  body.collection-page .outfit-section-header {
    flex-wrap: nowrap;
    align-items: center;
  }

  body.collection-page .outfit-section-header > .edit-name-container {
    align-self: center;
    flex-shrink: 1;
    min-width: 0;
  }

  body.collection-page .outfit-section .outfit-section-heading {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  body.collection-page .outfit-section-header > .coll-header-actions {
    flex-shrink: 0;
    gap: 8px;
    margin-left: 12px;
  }

  /* Shorter than "Open in generator" beside them, 2px apart. */
  body.collection-page .outfit-caret-btn {
    width: 26px;
    height: 26px;
  }

  body.collection-page .collection-tabs {
    padding: 0 24px;
  }

  body.collection-page .collection-tab {
    font-size: 13px;
  }

  /* LOCK's height, as a pill with ROLL's shadow. */
  body.collection-page .open-in-generator-btn {
    height: 30px;
    padding: 0 14px;
    font-size: 13px;
    border-radius: 15px;
    box-shadow: 0 2px 0 0 #2C56A0;
  }

  body.collection-page .open-in-generator-btn:active {
    transform: translateY(2px);
    box-shadow: 0 0 0 0 #2C56A0;
  }
}

/* ══════════════════════════════════════════════════════
   MOBILE — search results / outfit-generator: fixed 2x2
   grid, capped at 4 products by JS (SLOT_COUNT). No paging
   — a new search or ROLL just replaces the same 4 slots.
══════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  .products-scroll-wrapper.is-results-grid {
    width: 100%;
    overflow-x: hidden;
    padding-bottom: 40px; /* prevents forced overflow-y:auto from clipping — kept just tall enough to clear the ROLL button now that it sits closer to the bottom */
  }

  .products-scroll-wrapper.is-results-grid .products-track {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    width: 100%;
    padding: 0 16px;
    gap: 18px 10px;
  }

  /* Each card fills its grid column. */
  .products-scroll-wrapper.is-results-grid .product-slot-wrapper,
  .products-scroll-wrapper.is-results-grid .picker-slot {
    flex: none;
    width: 100%;
    height: auto;
    margin: 0 auto;
  }

  /* Columns are sized to the cards (fixed 10px gap), so extra width goes
     to the side margins. Card width is also capped by the visible screen
     height so both rows, with their dots, stay above the ROLL controls:
     225px = the space above the grid (117) + dots and row gap (42) + ROLL
     controls, their background strip and 4px of air (66); the divisor is
     2 rows x the card's 1.5 ratio. */
  body.search-page .products-scroll-wrapper.is-results-grid .products-track {
    grid-template-columns: repeat(2, min(calc((100% - 10px) / 2), calc((100dvh - 225px) / 3)));
    justify-content: center;
  }

  /* Horizontal drags go to the photo swipe handler; vertical ones still
     scroll the page. */
  .products-scroll-wrapper.is-results-grid .product-card {
    max-height: none;
    touch-action: pan-y pinch-zoom;
  }

  /* Only the photos show on the grid; name/vendor/price appear in the
     attributes panel instead. */
  .products-scroll-wrapper.is-results-grid .product-below-info .product-name-below,
  .products-scroll-wrapper.is-results-grid .product-below-info .product-meta-below {
    display: none;
  }

  /* Swiping replaces the arrows; only the dots show. */
  .products-scroll-wrapper.is-results-grid .carousel-arrow-left,
  .products-scroll-wrapper.is-results-grid .carousel-arrow-right {
    display: none;
  }

  .products-scroll-wrapper.is-results-grid .carousel-nav-below {
    margin-top: 6px;
    min-height: 6px;
  }

  .products-scroll-wrapper.is-results-grid .carousel-dots {
    gap: 4px;
  }

  .products-scroll-wrapper.is-results-grid .carousel-dot {
    width: 4px;
    height: 4px;
  }

  .products-scroll-wrapper.is-results-grid .carousel-dot.is-current {
    width: 5px;
    height: 5px;
  }

  .products-scroll-wrapper.is-results-grid .persistent-lock {
    top: 6px;
    left: 6px;
    height: 18px;
    padding: 0 7px;
    font-size: 9px;
    border-radius: 5px;
    box-shadow: 0 1.5px 0 0 #2C56A0;
  }

  /* Touch: the 18px badge gets a 44px-tall tap target (tapping it unlocks
     the slot -- header.js lets it through instead of opening the panel).
     Invisible; the card's overflow:hidden clips it at the card edge. */
  @media (hover: none) {
    .products-scroll-wrapper.is-results-grid .persistent-lock::before {
      content: '';
      position: absolute;
      top: -6px;
      left: -6px;
      right: -8px;
      bottom: -20px;
    }
  }

  /* The desktop "insert a slot here" gap buttons don't fit a 2-col
     grid (each would claim its own cell) — mobile only supports
     appending at the end, via .cg-append-tile below. */
  .products-scroll-wrapper.is-results-grid .cg-slot-gap {
    display: none;
  }

  .cg-append-tile {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    aspect-ratio: 1 / 1.5;
    border-radius: var(--card-radius);
    background: #F2F1EC;
    margin: 0 auto;
  }

  .cg-append-tile .cg-slot-insert-btn {
    position: static;
    transform: none;
    opacity: 1;
    pointer-events: auto;
    width: 40px;
    height: 40px;
  }

  /* Same box as a product photo. */
  .products-scroll-wrapper.is-results-grid .picker-slot {
    aspect-ratio: 1 / 1.5;
  }
}

.product-card {
  width: 100%;
  height: auto;
  /* Every card is portrait, height = 1.5x width (CSS aspect-ratio is
     width / height). The photo fills it with object-fit: cover. */
  aspect-ratio: 1 / 1.5;
  /* Ratio 1.5 (height:width), scaled with the same clamp() as the
     card's own width (clamp(220px, 18vw, 350px) * 1.5). On shorter
     screens this drops to 1.35 below — see the max-height media query
     after this rule, which now also sets aspect-ratio to match. */
  max-height: clamp(330px, 27vw, 525px);
  position: relative;
  border-radius: var(--card-radius);
  overflow: hidden;
  cursor: default;
  background: #FCFDFF;
  /* When max-height clips the image, center it on the vertical axis
     so the crop is symmetric (equal amount lost off the top and
     bottom) instead of just cutting off the bottom. */
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Same fit-both-rows cap as the mobile rule, for the 1.35 ratio below. */
@media (max-width: 640px) and (max-height: 700px) {
  body.search-page .products-scroll-wrapper.is-results-grid .products-track,
  body.collection-page #allProductsPanel .products-track {
    grid-template-columns: repeat(2, min(calc((100% - 10px) / 2), calc((100dvh - 225px) / 2.7)));
  }
}

/* Shorter screens get a tighter ratio (1.35) so cards don't eat too
   much of the limited vertical space. */
@media (max-height: 700px) {
  .product-card {
    aspect-ratio: 1 / 1.35;
    max-height: clamp(297px, 24.3vw, 472.5px);
  }

  .cg-slot-gap {
    height: clamp(297px, 24.3vw, 472.5px);
  }

  .picker-slot {
    height: clamp(297px, 24.3vw, 472.5px);
  }
}

/* Levi's cards already fit their own fixed 4:5 box without cropping
   (see below) — exempt them from this cap. */
.product-card.is-levis {
  max-height: none;
}

.product-card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--card-radius);
  /* Drop-shadow-style outline: thin on top/left, thicker on bottom/
     right. A separate absolutely-positioned overlay (not a real border
     on the image itself), so it never affects image sizing/spacing. */
  border-style: solid;
  border-color: #F2F1EC;
  border-width: 1px 1px 3px 1px;
  pointer-events: none;
  z-index: 3;
}

/* ── Carousel nav — below the photo mat, before the product name (not on
   the photo), so it's always visible with no hover-gate. Same system as
   search.html's, ported here for outfit-generator.html's main grid and
   explore section. */
.carousel-nav-below {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 9px;
  min-height: 22px; /* reserved even with no carousel, so .product-name-below always starts at the same distance from the photo */
}
.carousel-arrow-left,
.carousel-arrow-right {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #F2F1EC;
  border: none;
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #6F747D;
  flex-shrink: 0;
}
.carousel-dots {
  display: flex;
  align-items: center;
  gap: 5px;
}

/* At most 7 dots show; with more photos the row scrolls to keep the
   current one in the middle (product-tags.js). On a photo in between, an
   end with more dots beyond it fades; on the first and last photos every
   dot stays clear. The padding keeps the dots' hover rings from being
   clipped. */
.carousel-dots {
  position: relative;
  max-width: 75px;
  overflow: hidden;
  padding: 6px 0;
  margin: -6px 0;
  scrollbar-width: none;
}
.carousel-dots:has(.carousel-dot:nth-child(8)) {
  justify-content: flex-start;
}
.carousel-dots.dots-more-after {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent);
}
.carousel-dots.dots-more-before {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 14px);
          mask-image: linear-gradient(to right, transparent, #000 14px);
}
.carousel-dots.dots-more-before.dots-more-after {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 14px, #000 calc(100% - 14px), transparent);
          mask-image: linear-gradient(to right, transparent, #000 14px, #000 calc(100% - 14px), transparent);
}
.carousel-dot { flex-shrink: 0; }

.carousel-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(126, 131, 140, .22);
  border: none;
  padding: 0;
  cursor: pointer;
  position: relative;
  transition: background 0.1s ease, width 0.1s ease, height 0.1s ease;
}
.carousel-dot::before {
  content: '';
  position: absolute;
  inset: -5.5px;
  border-radius: 50%;
  background: transparent;
  transition: background 0.1s ease;
}
.carousel-dot:hover { background: rgba(126, 131, 140, .5); }
.carousel-dot:hover::before { background: rgba(126, 131, 140, .08); }
.carousel-dot:active { background: #102A71; }
.carousel-dot:active::before { background: rgba(126, 131, 140, .16); }
.carousel-dot.is-current {
  width: 6px;
  height: 6px;
  background: #102A71;
}

/* On touch screens photos change by swiping; the dots only show where
   you are. */
@media (hover: none) {
  .carousel-dot {
    pointer-events: none;
  }
}

.product-name-below {
  font-family: 'LibreCaslonCondensed-SemiBold', serif;
  font-size: 19px;
  color: #102A71;
  margin-top: clamp(6px, 1.2dvh, 18px);
  line-height: 1.2;
  word-break: break-word;
  overflow-wrap: break-word;
}

.product-meta-below {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 14px;
  color: #102A71;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  margin-top: 5px;
}

.product-meta-below > span:first-child {
  text-transform: uppercase;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 300;
  /* Was inheriting .product-meta-below's 14px, same as the price/rating
     sibling (.meta-pr) -- given its own explicit size here so it can move
     independently without touching the price, which must stay untouched. */
  font-size: 13px;
}

.meta-pr {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 14px;
}

.product-img {
  /* Fills the card's fixed aspect-ratio box (1:1 by default, or whatever
     ratio _applyCropToCard sets) without leaving any gap — object-fit:cover
     center-crops instead of letterboxing. JS overrides position/transform
     inline once a card ratio is computed, same as this rule intends. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  flex-shrink: 0;
}

/* Levi's listings are full-body photos where the
   garment itself is a small fraction of a very tall image — reshaping the
   slot to the detected box like every other card would make these cards
   absurdly narrow. Instead: the crop is fit tight to the
   garment (.levis-crop-window gets sized/positioned in JS via
   garment, but fit into a FIXED 4:5
   frame via letterbox/pillarbox rather than reshaping the frame itself —
   whichever dimension comes up short is filled with white, not stretched. */
.product-card.is-levis {
  aspect-ratio: 4 / 5;
  height: auto;
  background: #FFFFFF;
  overflow: hidden;
  position: relative;
}

.product-card.is-levis .levis-crop-window {
  position: absolute;
  overflow: hidden;
}

.product-card.is-levis .levis-crop-window .product-img {
  position: absolute;
  inset: auto;
  width: 100%;
  height: auto;
  object-fit: unset;
}

/* ── Hover overlay ─────────────────────────────────── */
.card-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(20, 29, 41, 0.45) 0%,
    rgba(20, 29, 41, 0.85) 100%
  );
  border-radius: var(--card-radius);
  opacity: 0;
  transition: opacity 0.3s ease;
  padding: 4px 8px 10px;
  color: #F2F1EC;
}

/* Real pointers only — on touch, :hover sticks after tap instead of toggling */
@media (hover: hover) {
  .product-card:hover .card-overlay,
  .product-card:has(:focus-visible) .card-overlay {
    opacity: 1;
  }

  .product-card:hover .product-img,
  .product-card:has(:focus-visible) .product-img {
    filter: blur(3px) brightness(0.9);
  }
}

/* Touch equivalent — toggled by header.js on tap */
.product-card.is-overlay-active .card-overlay {
  opacity: 1;
}

.product-card.is-overlay-active .product-img {
  filter: blur(3px) brightness(0.9);
}

/* Top controls row: ×, dots, lock — each positioned independently, absolute
   to .card-overlay, so values never shift between hover / non-hover states */
.overlay-top {
  /* layout no longer used — kept only as a semantic wrapper */
}

/* Sheet-only elements, shown by the ATTRIBUTES PANEL block below. */
.panel-photo,
.carousel-dots.panel-dots,
.panel-info > .product-name-below,
.panel-info > .product-meta-below {
  display: none;
}

/* Layout boxes only in the mobile sheet. On desktop they add no box, so
   the lock button and drag dots inside them still position against
   .card-overlay. */
.panel-header-row,
.panel-photo-col,
.panel-photo-frame,
.panel-info {
  display: contents;
}

/* ══════════════════════════════════════════════════════
   ATTRIBUTES PANEL — .card-overlay pops from a card-sized
   hover reveal into a full-screen panel on touch (a drawer on
   phones held sideways, see the end of this file). Same DOM
   node either way (not a separate component), so every
   existing lock/save/visit-site click handler -- all
   delegated from #productsGrid -- keeps working unchanged.
══════════════════════════════════════════════════════ */
@media (max-width: 640px), (orientation: landscape) and (max-height: 500px) {
  /* A transform on any ancestor makes it the containing block for the
     position:fixed sheet, trapping the sheet inside the grid cell -- so no
     animation leftover is allowed to stay on while it's open. */
  .product-slot-wrapper:has(> .product-card.is-overlay-active),
  .product-card.is-overlay-active {
    transform: none !important;
  }

  /* The grid photo stays sharp behind the sheet (the hover-overlay blur
     is for the small in-card overlay only). */
  .product-card.is-overlay-active .product-img {
    filter: none;
  }

  /* Full screen; the whole panel scrolls, with the footer pinned to the
     bottom. */
  .product-card.is-overlay-active .card-overlay {
    position: fixed;
    inset: 0;
    width: 100%;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: 0;
    background: var(--cream);
    color: var(--dark);
    display: flex;
    flex-direction: column;
    padding: 56px 20px 0;
    z-index: 2050;
  }

  /* Frozen top row: a cream band behind LOCK and the X, which stay put
     while the panel scrolls under them. */
  .product-card.is-overlay-active .card-overlay::before {
    content: '';
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 52px;
    background: var(--cream);
    transition: box-shadow 0.15s ease;
    z-index: 2;
  }

  /* Once the panel scrolls, the same shadow as the expanded search bar. */
  .product-card.is-overlay-active .card-overlay.is-scrolled::before {
    box-shadow: 0 2px 18px rgba(87, 127, 188, 0.08);
  }

  /* Top to bottom: LOCK (the X sits level with it, top-right), the photo
     with its dots, then name, brand and price. .panel-info adds no box
     here, so its lock button and text can be ordered around the photo. */
  /* Fills the screen above the footer (56px top padding + the footer's
     68px), so the attributes only start below the fold. */
  .product-card.is-overlay-active .panel-header-row {
    display: flex;
    flex-direction: column;
    min-height: calc(100dvh - 124px - env(safe-area-inset-bottom));
    margin: 0 0 14px;
    flex-shrink: 0;
  }

  .product-card.is-overlay-active .panel-info {
    display: contents;
  }

  .product-card.is-overlay-active .panel-info .slot-lock-text-btn {
    position: fixed;
    top: 12px;
    left: 20px;
    z-index: 3;
    height: 30px;
    padding: 0 14px;
    font-size: 13px;
    border-radius: 8px;
  }

  /* The photo takes whatever height the lock row and text leave, up to
     the card's 1:1.5 shape. */
  .product-card.is-overlay-active .panel-photo-col {
    order: 1;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    width: 100%;
  }

  .product-card.is-overlay-active .panel-photo-frame {
    position: relative;
    display: block;
    flex: 1 1 auto;
    width: 100%;
    min-height: 260px;
    max-height: calc((100vw - 40px) * 1.5);
    overflow: hidden;
    border-radius: 10px;
  }

  .product-card.is-overlay-active .carousel-dots.panel-dots {
    display: flex;
  }

  .product-card.is-overlay-active .panel-info .product-name-below {
    order: 2;
    display: block;
    font-size: 22px;
    margin-top: 14px;
  }

  .product-card.is-overlay-active .panel-info .product-meta-below {
    order: 3;
    display: flex;
    gap: 4px;
    margin-top: 8px;
  }

  .product-card.is-overlay-active .panel-info .product-meta-below > span:first-child,
  .product-card.is-overlay-active .panel-info .meta-pr {
    font-size: 14px;
  }

  .product-card.is-overlay-active .panel-photo {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* Only holds the grid's drag-handle dots, which mean nothing in the
     sheet. */
  .product-card.is-overlay-active .overlay-top {
    display: none;
  }

  /* Top-right corner, level with LOCK, above the photo. Sized to match
     the hamburger's close X: a 12px cross, 1.5px stroke, 28px tap area. */
  .product-card.is-overlay-active .close-btn {
    position: fixed;
    z-index: 3;
    top: 13px;
    right: 10px;
    width: 28px;
    height: 28px;
    color: var(--dark);
  }

  .product-card.is-overlay-active .close-btn svg {
    width: 14.4px;
    height: 14.4px;
  }

  .product-card.is-overlay-active .close-btn svg line {
    stroke-width: 1.25;
  }

  .product-card.is-overlay-active .card-info {
    position: static;
    display: block;
    flex: none;
    overflow: visible;
  }

  .product-card.is-overlay-active .card-attrs-scroll {
    overflow: visible;
    padding-right: 4px;
  }

  .product-card.is-overlay-active .attr-row,
  .product-card.is-overlay-active .attr-row-static {
    padding: 6px 4px 6px 10px;
  }

  /* The row's tap highlight -- also what :hover sticks to after a tap on
     touch, per the sticking-hover note further up -- reads as a light
     rectangle around the row itself. */
  .product-card.is-overlay-active .attr-row:hover {
    background: #F2F1EC;
  }

  /* Label smaller than value -- a category name shouldn't read more
     prominent than the actual attribute. */
  .product-card.is-overlay-active .attr-label {
    font-size: 12px;
    color: var(--muted);
  }

  .product-card.is-overlay-active .attr-value {
    font-size: 14px;
    color: var(--dark);
  }

  .product-card.is-overlay-active .product-disclosure {
    font-size: 11px;
  }

  .product-card.is-overlay-active .tag-lock-circle {
    border-color: var(--muted);
  }

  /* Pinned to the bottom of the screen while the panel scrolls under it. */
  .product-card.is-overlay-active .card-footer {
    position: sticky;
    bottom: 0;
    z-index: 1;
    margin-top: auto;
    padding: 10px 0 calc(14px + env(safe-area-inset-bottom));
    background: var(--cream);
    flex-shrink: 0;
  }

  /* Sized to its text rather than stretching to the icon buttons. */
  .product-card.is-overlay-active .product-webpage-btn {
    flex: 0 0 auto;
    height: 44px;
    max-width: none;
    padding: 0 32px;
    border: none;
    border-radius: 22px;
    background: #FED861;
    color: #102A71;
    font-size: 15px;
    font-weight: 500;
  }

  .product-card.is-overlay-active .card-footer-icons {
    gap: 8px;
  }

  .product-card.is-overlay-active .save-btn,
  .product-card.is-overlay-active .bag-btn,
  .product-card.is-overlay-active .img-search-btn,
  .product-card.is-overlay-active .search-icon-btn,
  .product-card.is-overlay-active .add-icon-btn,
  .product-card.is-overlay-active .remove-icon-btn {
    width: 44px;
    height: 44px;
    border-radius: 22px;
    border: 1px solid #F2F1EC;
    background: #FCFDFF;
    color: #102A71;
  }

  .product-card.is-overlay-active .save-btn svg,
  .product-card.is-overlay-active .bag-btn svg {
    width: 25px;
    height: 25px;
  }

  .product-card.is-overlay-active .img-search-btn svg,
  .product-card.is-overlay-active .search-icon-btn svg,
  .product-card.is-overlay-active .add-icon-btn svg,
  .product-card.is-overlay-active .remove-icon-btn svg {
    width: 23px;
    height: 23px;
  }
}

/* On touch phones a tap opens the sheet above, so the in-card overlay has
   no use -- keeping it out of the layout otherwise also stops it fading
   over the photo for a moment as the sheet closes. */
@media (max-width: 640px) and (hover: none), (orientation: landscape) and (max-height: 500px) {
  .product-card:not(.is-overlay-active) .card-overlay {
    display: none;
  }

  /* A transition can't run out of display:none, so the panel's fade-in is
     a keyframe animation instead, which plays each time it appears. */
  .product-card.is-overlay-active .card-overlay {
    animation: sheet-fade-in 0.3s ease;
  }
}

@keyframes sheet-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Pinch zoom on the panel photo (panel.js): while pinching, the photo can
   grow past its frame over the rest of the panel, like Instagram. */
.product-card.is-overlay-active .panel-photo-frame.is-pinching {
  overflow: visible;
  z-index: 5;
}

.product-card.is-overlay-active .panel-photo-frame.is-pinching .panel-photo {
  will-change: transform;
}

.close-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #F2F1EC;
}

/* LOCK text button (slot-level lock) */
.slot-lock-text-btn {
  position: absolute;
  top: 10px;
  left: 10px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: normal;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: #2C56A0;
  background: #FED861;
  border: none;
  /* box-shadow instead of a border -- it follows border-radius exactly,
     so the shadow can't bulge past the pill's rounded corners the way an
     asymmetric border-width does. Offset-only, no blur, so it reads as a
     crisp 2px ledge, not a soft glow. */
  box-shadow: 0 2px 0 0 #2C56A0;
  border-radius: 7px;
  padding: 0 10px;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}

.slot-lock-text-btn:active {
  /* Shadow shrinks by exactly the distance the button moves down, so its
     bottom edge stays put -- only the yellow face (and its text) visibly
     moves, instead of the whole button (shadow included) shifting
     together. */
  transform: translateY(2px);
  box-shadow: 0 0 0 0 #2C56A0;
}

.slot-lock-text-btn.is-locked {
  background: #FED861;
}

/* 6-dot grid (3 columns × 2 rows) */
.dots-grid {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  display: grid;
  grid-template-columns: repeat(3, 3px);
  grid-template-rows: repeat(2, 3px);
  gap: 2px;
  cursor: grab;
}

.dot {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #F2F1EC;
  opacity: 1;
}

.icon-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: #F2F1EC;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.85;
  transition: opacity 0.15s;
  margin-right: 0;
}

.icon-btn:hover { opacity: 1; }

/* Product info */
.card-info {
  position: absolute;
  top: 40px;
  left: 10px;
  right: 0;
  bottom: 48px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 4px;
}


/* Card footer — pinned to bottom of overlay */
.card-footer {
  position: absolute;
  bottom: 10px;
  left: 10px;
  right: 10px;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Groups the icon buttons (image-search / explore-more / save) so they
   keep one fixed gap between themselves; margin-left:auto pushes the
   whole cluster to the right, instead of justify-content:space-between
   spreading extra space evenly into EVERY gap (which visually centered
   the middle button between Visit Site and the others). */
.card-footer-icons {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}

/* "Visit site" button */
.product-webpage-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  flex: 1;
  height: 28px;
  max-width: 86px;
  padding: 0 6px;
  border: 1px solid rgba(242, 241, 236, 0.35);
  border-radius: 7px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0;
  color: #FFFFFF;
  text-decoration: none;
  background: none;
  cursor: pointer;
  white-space: nowrap;
}

.save-btn,
.bag-btn {
  width: 28px;
  height: 28px;
  border-radius: 7px;
  border: 1px solid rgba(242, 241, 236, 0.35);
  background: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #FFFFFF;
  padding: 0;
  flex-shrink: 0;
}

/* Hover labels on the product popup's footer buttons ("Search image",
   "Add to cart", "Save product"); mouse devices only. The last button's
   label is right-aligned so it stays inside the card. */
@media (hover: hover) {
  .card-footer-icons [data-tip] { position: relative; }
  .card-footer-icons [data-tip]::after {
    content: attr(data-tip);
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translate(-50%, 4px);
    padding: 4px 10px;
    border: 1px solid #F2F1EC;
    border-radius: 12px;
    background: #FCFDFF;
    color: #102A71;
    font-family: 'SpaceGrotesk', sans-serif;
    font-weight: 400;
    font-size: 12px;
    line-height: 1.3;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s ease, transform 0.15s ease;
    z-index: 5;
  }
  .card-footer-icons [data-tip]:last-child::after {
    left: auto;
    right: 0;
    transform: translateY(4px);
  }
  .card-footer-icons [data-tip]:hover::after,
  .card-footer-icons [data-tip]:focus-visible::after {
    opacity: 1;
    transform: translate(-50%, 0);
  }
  .card-footer-icons [data-tip]:last-child:hover::after,
  .card-footer-icons [data-tip]:last-child:focus-visible::after {
    transform: translateY(0);
  }
}

/* Outfit/wishlist summary — save sits in its own bottom-right corner,
   not inline with Visit Site in .card-footer. */
.save-btn-corner {
  position: absolute;
  bottom: 10px;
  right: 10px;
}

/* ══════════════════════════════════════════════════════
   NAVIGATION ARROWS + ROLL BUTTON
══════════════════════════════════════════════════════ */

/* Same solid-navy/white "commit this action" treatment as
   .save-modal-confirm-btn -- the previous pale outline pill read as
   just another inert chip on the cream background, easy to miss
   entirely against everything else on the page. Anchored inside
   .roll-section (itself position:fixed) rather than flowing after the
   product grid -- it used to sit in a plain .nav-row well above the
   fixed Roll bar, nowhere near the bottom-right corner it's supposed
   to occupy, which made it read as missing entirely. */
.save-changes-btn {
  position: absolute;
  right: clamp(20px, 2.08vw, 60px);
  top: 50%;
  transform: translateY(-50%);
  height: 42px;
  padding: 0 22px;
  background: #2C56A0;
  color: #FFFFFF;
  border: none;
  border-radius: 21px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 14px;
  letter-spacing: 0;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}

.save-changes-btn:hover {
  background: #102A71;
  color: #FFFFFF;
}

/* Confirm removal modal (collection detail page) */
.confirm-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 2060;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(38, 26, 18, 0.55);
}

.confirm-modal-overlay.is-open {
  display: flex;
}

.confirm-modal-box {
  background: #FFFDF8;
  border-radius: 18px;
  padding: 32px 28px 24px;
  width: 340px;
  position: relative;
  text-align: center;
}

.confirm-modal-title {
  font-family: 'LibreCaslonCondensed-SemiBold', serif;
  font-size: 25px;
  font-weight: normal;
  letter-spacing: 0;
  color: var(--dark);
  margin-bottom: 26px;
  line-height: 1.2;
}

.confirm-modal-sub {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  letter-spacing: 0;
  color: var(--muted);
  margin-bottom: 28px;
  line-height: 1.4;
}

.confirm-modal-actions {
  display: flex;
  gap: 12px;
  justify-content: center;
}

/* Same pills as the save modal's buttons. */
.confirm-modal-cancel,
.confirm-modal-confirm {
  flex: 1;
  height: 44px;
  padding: 0 24px;
  border-radius: 22px;
  border: none;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.confirm-modal-cancel {
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  color: var(--dark);
}

.confirm-modal-confirm {
  background: #2C56A0;
  color: #FFFFFF;
}

.confirm-modal-cancel:hover {
  background: #F2F1EC;
}

.confirm-modal-confirm:hover {
  background: #102A71;
  color: #FFFFFF;
}

.nav-arrow-btn {
  width: 45px;
  height: 45px;
  border-radius: 50%;
  border: 1px solid #F2F1EC;
  background: #FCFDFF;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #102A71;
  transition: background 0.15s, color 0.15s;
}

.nav-arrow-btn:hover:not(:disabled),
.nav-arrow-btn:active:not(:disabled) {
  background: #FED861;
  color: #102A71;
}

/* Never dimmed: with nothing to undo/redo the arrow just does nothing. */
.nav-arrow-btn:disabled {
  cursor: default;
  pointer-events: none;
}

/* Redo (#nextBtn on search.html, #cgNextBtn/#exploreNextBtn on the
   generator) starts `disabled` in the HTML itself -- before any roll has
   happened there's nothing to redo yet. Undo dimming when disabled is
   still correct/expected; redo should never look dimmed/broken, even in
   that starting state -- it stays functionally inert via pointer-events:
   none above, just not visually flagged as such. */
#nextBtn:disabled,
#cgNextBtn:disabled,
#exploreNextBtn:disabled {
  opacity: 1;
}

.product-slot-wrapper {
  transition: opacity 0.3s cubic-bezier(0.33, 1, 0.68, 1);
}

.roll-section {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 15px;
  position: fixed;
  /* 5px lower than the plain clamp(28px, 4.5dvh, 52px) -- shifted down
     so the product slot above has that much more room before it reaches
     the fixed bar. */
  bottom: clamp(23px, calc(4.5dvh - 5px), 47px);
  left: 0;
  right: 0;
  z-index: 20;
}

/* Standardized "ran out of results" notification -- sits directly above
   the roll button (centered on .roll-section's own box, which is
   basically centered on the button since prev/next sit symmetrically
   either side of it), clear of the button itself, rather than a floating
   pill at an arbitrary point on the page. No container shape at all --
   the pill version read as too distinct an element from everything else
   on the page. Just the text, on a soft navy blurred glow (--dark, not
   black) instead of a background, so there's still enough contrast to
   stay legible over any page content behind it. */
/* Short messages ("Added to bag", "End of results") in the ROLL button's
   exact shape, laid over it in inverted colours (bag.js rollMessage sets the position
   and size from the button). .is-floating: bottom-centre on pages without
   a ROLL button. */
.roll-message {
  position: fixed;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #2C56A0;
  color: #FCFDFF;
  border-radius: 26px;
  box-shadow: 0 3px 0 0 #2C56A0;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
  z-index: 40;
}
.roll-message.is-visible { opacity: 1; }
.roll-message.is-floating {
  left: 50%;
  transform: translateX(-50%);
  bottom: clamp(23px, calc(4.5dvh - 5px), 47px);
  width: 135px;
  height: 52px;
}
@media (max-width: 640px) {
  .roll-message { box-shadow: 0 2px 0 0 #2C56A0; }
  .roll-message.is-floating { width: 116px; height: 42px; border-radius: 21px; }
}

/* outfit-generator.html has TWO of these -- the main grid's (fixed,
   above) and explore's own. Both position:fixed at the same bottom-of-
   viewport spot meant they permanently overlapped whenever explore was
   open, and since explore's comes later in the DOM, it stacked on top --
   visually swallowing the main grid's Save changes button, which explore's
   own copy doesn't have. Sticky instead of fixed here: it still tracks
   the viewport bottom while the (very tall, 100vh) explore section is
   passing through it, but scrolls away normally once you're back on the
   main grid, so there's only ever one truly-fixed bar on screen. */
.explore-roll-section {
  position: sticky;
}

/* Generator's main bar: undo/ROLL/redo centred, Save on the right page
   margin (same as the title row above). On mobile, undo/ROLL/redo sit on
   the left margin instead, with Save pushed to the right. */
body.generator-page .roll-section:not(.explore-roll-section) .save-changes-btn {
  right: clamp(40px, 2.08vw, 60px);
}

@media (max-width: 640px) {
  body.generator-page .roll-section:not(.explore-roll-section) {
    justify-content: flex-start;
    padding: 0 24px;
  }

  body.generator-page .roll-section:not(.explore-roll-section) .save-changes-btn {
    margin-left: auto;
  }
}

.roll-section::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -18px;
  bottom: -200px;
  background: #FDFBF7;
  z-index: -1;
}

/* Line weights on screen, at every size: undo/redo arrows 1.5px; the top
   bar's account caret and the photo carousel arrows 1px. */
.nav-arrow-btn svg polyline {
  stroke-width: 1.5px;
  vector-effect: non-scaling-stroke;
}
.account-caret-btn svg polyline,
.carousel-arrow-left svg polyline,
.carousel-arrow-right svg polyline {
  stroke-width: 1px;
  vector-effect: non-scaling-stroke;
}

.roll-btn {
  background: #FED861;
  color: #2C56A0;
  border: none;
  /* box-shadow instead of a border -- it follows border-radius exactly,
     so the shadow can't bulge past the pill's rounded corners the way an
     asymmetric border-width does. Offset-only, no blur, capped at 3px. */
  box-shadow: 0 3px 0 0 #2C56A0;
  border-radius: 26px;
  width: 135px;
  height: 52px;
  font-family: 'LibreCaslonCondensed-BoldItalic', serif;
  font-size: 19px;
  font-weight: normal;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}

.roll-btn:active {
  /* Shadow shrinks by exactly the distance the button moves down, so its
     bottom edge stays put -- only the yellow face visibly moves. */
  transform: translateY(3px);
  box-shadow: 0 0 0 0 #2C56A0;
}

/* ══════════════════════════════════════════════════════
   NO RESULTS / HIDDEN CARDS
══════════════════════════════════════════════════════ */
.product-card.hidden {
  display: none;
}

.no-results {
  text-align: center;
  padding: 60px 20px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  white-space: nowrap;
  font-size: 16px;
  color: var(--muted);
  white-space: nowrap;
}

.price-input::-webkit-inner-spin-button,
.price-input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* ══════════════════════════════════════════════════════
   PRODUCT CARD — SCROLLABLE ATTRS
══════════════════════════════════════════════════════ */
.card-attrs-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  /* "safe" falls back to flex-start when content overflows — plain
     flex-end would push overflow off the top of the scroll container,
     making it inaccessible (scrollTop can't go negative). */
  justify-content: safe flex-end;
  scrollbar-width: thin;
  scrollbar-color: rgba(252, 253, 255, 0.45) transparent;
  padding-right: 10px;
}

.card-attrs-scroll::-webkit-scrollbar {
  width: 6px;
}

.card-attrs-scroll::-webkit-scrollbar-track {
  background: transparent;
}

.card-attrs-scroll::-webkit-scrollbar-thumb {
  background: rgba(252, 253, 255, 0.45);
  border-radius: 3px;
}

/* ── Attribute rows ─────────────────────────────────── */
.attr-divider {
  height: 1px;
  background: rgba(170, 170, 170, 0.3);
  flex-shrink: 0;
  /* Inset from both edges so the line reads shorter than the row's own
     rectangle, without changing the row's own width. */
  margin: 0 10px;
}

.attr-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
  cursor: pointer;
  border-radius: 7px;
  padding: 8px 3px 8px 10px;
  transition: background 0.1s;
}

.attr-row:hover {
  background: rgba(170, 170, 170, 0.3);
}

/* Read-only attr row (no lock circle, no click target) — wishlist/outfit summary */
.attr-row-static {
  cursor: default;
  padding-left: 13px;   /* + card-info's 10px inset = 23px from card edge */
}

.attr-row-static:hover {
  background: none;
}

/* Desktop overlay: the attribute area's height is shared evenly by 7 rows,
   or by 6 when 7 would leave under 3px above and below the text. 28.6px is
   a row's text (label 13px + value 15.6px); rows are 1px dividers apart and
   fill 1px past the area, so the divider after the last visible row is hidden. */
@media (min-width: 641px) {
  .card-attrs-scroll {
    container-type: size;
  }
}

@container (min-height: 1px) {
  .attr-row {
    padding-top: max(0px, calc(((100cqh - 4px) / 6 - 28.6px) / 2));
    padding-bottom: max(0px, calc(((100cqh - 4px) / 6 - 28.6px) / 2));
  }
}

@container (min-height: 251px) {
  .attr-row {
    padding-top: calc(((100cqh - 5px) / 7 - 28.6px) / 2);
    padding-bottom: calc(((100cqh - 5px) / 7 - 28.6px) / 2);
  }
}

.attr-row-empty {
  display: none;
}

/* Circle lock indicator — left of each attribute */
.tag-lock-circle {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  border: 1px solid #FFFFFF;
  background: none;
  flex-shrink: 0;
  box-sizing: border-box;
}

.tag-lock-circle.is-locked {
  background: #FED861;
  border-color: #FED861;
}

/* ══════════════════════════════════════════════════════
   ATTRIBUTE LOCK PILLS (outfit-generator.html) -- the shared
   main filter row (.locked-attrs-summary, reused from
   search.html) and brand chips. No per-product mini row
   anymore -- a card-level lock shows nothing beyond its own
   dot; only the overlay's promote arrow (below) reaches here.
══════════════════════════════════════════════════════ */
/* Label above its row of buttons, same shape as .filter-col
   (PRICE/GENDER/BRAND) rather than sitting side by side with them. */
.locked-attr-group {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  flex-shrink: 0;
}

.locked-attr-btns {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
  flex-wrap: wrap;
}

/* Plain text, no background/border/icons -- the whole thing is one click
   target that deactivates it (see cgDeactivateAttrValue/
   exploreRemoveAttrValue/the brand-chip handlers). Same filled-pill look
   as before the arrow/x/divider removal -- just a plain click target now,
   no internal zones. */
.attr-lock-pill {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 7px;
  background: #FED861;
  border-radius: 13px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 600;
  font-size: 13px;
  color: #102A71;
  white-space: nowrap;
  cursor: pointer;
}

/* "Apply to all" button, in the card overlay next to a promotable
   attribute (Color/Material/Occasion/Style -- see
   ProductTags.isPromotableField) -- pressing it is the only way a value
   reaches the shared row above. Always visible, not a hover-reveal. */
.attr-promote-btn {
  background: #2C56A0;
  border: none;
  border-radius: 11px;
  height: 22px;
  padding: 0 8px;
  margin-left: auto;
  cursor: pointer;
  color: #FCFDFF;
  font-family: 'SpaceGrotesk', sans-serif;
  /* Matches .gender-btn's unselected-state weight (.gender-btn.active is 600). */
  font-weight: 400;
  font-size: 10px;
  white-space: nowrap;
  flex-shrink: 0;
  transition: background 0.15s;
}

.attr-promote-btn:hover {
  background: #102A71;
}

.attr-text {
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* Colour variants in the Color row: each square is that colour's own photo
   (product-tags.js variantSwatchesHTML); clicking one swaps the card. */
.variant-swatches {
  display: flex;
  gap: 4px;
  margin-left: auto;
  padding: 2px;
  max-width: 55%;
  overflow-x: auto;
  scrollbar-width: none;
  flex-shrink: 1;
}
.variant-swatches::-webkit-scrollbar { display: none; }
.variant-swatches + .attr-promote-btn { margin-left: 0; }

.variant-swatch {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1px solid #6F747D;
  border-radius: 4px;
  background-color: #F2F1EC;
  background-size: 240%;
  background-position: 50% 42%;
  cursor: pointer;
}
.variant-swatch:hover { border-color: #FCFDFF; }

/* Hover label over a colour square (product-tags.js); same look as the
   popup footer buttons' labels. */
.swatch-tip {
  position: fixed;
  transform: translate(-50%, calc(-100% - 4px));
  padding: 4px 10px;
  border: 1px solid #F2F1EC;
  border-radius: 12px;
  background: #FCFDFF;
  color: #102A71;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  line-height: 1.3;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 1000;
}
.swatch-tip.is-visible {
  opacity: 1;
  transform: translate(-50%, calc(-100% - 8px));
}
.variant-swatch.is-current {
  border-color: #FCFDFF;
  cursor: default;
}

.attr-label {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 10px;
  letter-spacing: 0.06em;
  color: rgba(242, 241, 236, 0.75);
  text-transform: uppercase;
  line-height: 1.3;
}

.attr-row-empty .attr-label {
  visibility: hidden;
}

.attr-value {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0;
  color: #FFFFFF;
  line-height: 1.3;
}

/* Outfit-generator cards have a single-row footer — bottom clearance for 34px row + padding */
#cgProductsGrid .card-info,
#exploreGrid .card-info {
  bottom: 48px;
}

/* ══════════════════════════════════════════════════════
   PERSISTENT LOCK BADGE
   LOCK text visible outside the overlay when slot is locked.
══════════════════════════════════════════════════════ */
.persistent-lock {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 10;
  display: none;
  height: 24px;
  align-items: center;
  justify-content: center;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: #2C56A0;
  background: #FED861;
  border: none;
  box-shadow: 0 2px 0 0 #2C56A0;
  border-radius: 7px;
  padding: 0 10px;
  cursor: pointer;
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}

.persistent-lock:active {
  transform: translateY(2px);
  box-shadow: 0 0 0 0 #2C56A0;
}

.product-card.is-locked .persistent-lock {
  display: flex;
}

/* Hidden under the hover overlay, which has its own LOCKED button. Real
   pointers only: on touch, :hover sticks to the last-tapped card and
   would hide the badge until something else is tapped. */
@media (hover: hover) {
  .product-card:hover .persistent-lock {
    display: none;
  }
}


/* ══════════════════════════════════════════════════════
   SAVE TO COLLECTION MODAL
══════════════════════════════════════════════════════ */
.save-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 2060;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(38, 26, 18, 0.55);
}

.save-modal-overlay.is-open {
  display: flex;
}

.save-modal-box {
  background: #FFFDF8;
  border-radius: 18px;
  padding: 25px 25px 25px;
  width: 360px;
  max-width: 90vw;
  color: var(--dark);
  position: relative;
}

.save-modal-title {
  font-family: 'LibreCaslonCondensed-SemiBold', serif;
  font-size: 25px;
  font-weight: normal;
  letter-spacing: 0;
  color: #102A71;
  margin-bottom: 4px;
  line-height: 1.2;
  text-align: center;
}

.save-modal-input-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 18px;
  margin-bottom: 18px;
}

.save-modal-input {
  flex: 1;
  border: 1px solid #F2F1EC;
  border-radius: 51px;
  background: #FCFDFF;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  letter-spacing: 0;
  color: var(--dark);
  padding: 0 16px;
  height: 44px;
  outline: none;
}

/* Chrome/Edge autofill paints its own yellow background + focus ring on
   a field it has a suggestion for (email/password here), ignoring this
   input's own background -- overriding via the -webkit-autofill
   pseudo-class is the only way to stop it. The huge transition-delay is
   the standard trick to stop that yellow from flashing in the instant
   before the override applies. */
.save-modal-input:-webkit-autofill,
.save-modal-input:-webkit-autofill:hover,
.save-modal-input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--dark);
  -webkit-box-shadow: 0 0 0 1000px #FCFDFF inset;
  box-shadow: 0 0 0 1000px #FCFDFF inset;
  transition: background-color 9999s ease-in-out 0s;
}

.save-modal-input:focus {
  outline: none;
}

/* Chips keep the navy of their hover/selected states rather than the yellow
   ring the inputs use, so keyboard focus matches what a pointer would show. */
.prefs-chip:focus-visible {
  outline: none;
  border-color: #2d5290;
  color: #2d5290;
  box-shadow: 0 0 0 3px rgba(45, 82, 144, 0.25);
}

.save-modal-input::placeholder {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  letter-spacing: 0;
  color: var(--muted);
}

.save-modal-add-btn {
  width: 35px;
  height: 35px;
  border-radius: 50%;
  border: none;
  background: #2C56A0;
  cursor: pointer;
  color: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 0;
  transition: background 0.15s, color 0.15s;
}

.save-modal-add-btn:hover {
  background: #102A71;
  color: #FFFFFF;
}

.save-modal-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 192px;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(126, 131, 140, 0.45) transparent;
}

/* Same scrollbar as .card-attrs-scroll / .prefs-suggestions. */
.save-modal-list::-webkit-scrollbar {
  width: 6px;
}
.save-modal-list::-webkit-scrollbar-track {
  background: transparent;
}
.save-modal-list::-webkit-scrollbar-thumb {
  background: rgba(126, 131, 140, 0.45);
  border-radius: 3px;
}

.save-outfit-item {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid #F2F1EC;
  border-radius: 10px;
  padding: 2px 14px 2px 2px;
}

.save-outfit-thumb {
  width: 40px;
  height: 40px;
  background: var(--muted);
  border-radius: 6px;
  flex-shrink: 0;
}

/* The footer row in the save modal's outfit screen -- same rectangle as an
   outfit row, but no image and nothing to select other than the row itself. */
.save-outfit-item--all-products {
  margin-top: 8px;
  padding: 10px 14px;
}

/* The "New collection/outfit name" field specifically -- same height as the
   circular add button beside it (35px), rather than .save-modal-input's own
   44px default (shared with login/prefs pages, left untouched there). */
#saveModal .save-modal-input {
  height: 35px;
}

.save-outfit-name {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  letter-spacing: 0;
  color: var(--dark);
  line-height: 1.2;
  flex: 1;
}

.save-outfit-item {
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

.save-outfit-item:hover {
  background: rgba(16, 42, 113, 0.05);
  border-color: var(--dark);
}

.save-outfit-item.is-saved {
  border-color: var(--dark);
}

.save-outfit-check {
  display: flex;
  align-items: center;
  justify-content: center;
}

.save-outfit-item.is-pending {
  background: rgba(16, 42, 113, 0.05);
  border-color: var(--dark);
}

.save-modal-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 20px;
}

.save-modal-cancel-btn {
  flex: 1;
  height: 44px;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  border-radius: 22px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: var(--dark);
  cursor: pointer;
}

.save-modal-confirm-btn {
  flex: 1;
  height: 44px;
  background: #2C56A0;
  border: none;
  border-radius: 22px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: #FFFFFF;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.save-modal-confirm-btn:hover {
  background: #102A71;
  color: #FFFFFF;
}

/* ══════════════════════════════════════════════════════
   LOGIN GATE (login-gate.js) — centered popup by default,
   bottom sheet under 600px
══════════════════════════════════════════════════════ */
.auth-gate-backdrop {
  position: fixed;
  inset: 0;
  z-index: 2100;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(38, 26, 18, 0.55);
}

.auth-gate-backdrop.is-open {
  display: flex;
}

.auth-gate-card {
  background: #FFFDF8;
  border-radius: 18px;
  padding: 25px;
  width: 360px;
  max-width: 90vw;
  color: var(--dark);
}

.auth-gate-handle {
  display: none;
}

.auth-gate-title {
  font-family: 'LibreCaslonCondensed-SemiBold', serif;
  font-size: 25px;
  font-weight: normal;
  color: var(--dark);
  line-height: 1.2;
  text-align: center;
}

.auth-gate-sub {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: var(--muted);
  text-align: center;
  margin-top: 6px;
}

.auth-gate-error {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  color: #B3261E;
  text-align: center;
  margin-top: 10px;
}

.auth-gate-input {
  width: 100%;
  margin-top: 12px;
}

.auth-gate-submit-btn {
  width: 100%;
  height: 44px;
  margin-top: 16px;
  background: #2C56A0;
  border: none;
  border-radius: 22px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: #FFFFFF;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, opacity 0.15s;
}

.auth-gate-submit-btn:hover:not(:disabled) {
  background: #102A71;
  color: #FFFFFF;
}

.auth-gate-submit-btn:disabled {
  opacity: 0.6;
  cursor: default;
}

.auth-gate-swap {
  display: block;
  text-align: center;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: var(--dark);
  text-decoration: underline;
  margin-top: 16px;
  cursor: pointer;
}

@media (max-width: 640px) {
  .auth-gate-backdrop {
    align-items: flex-end;
    justify-content: stretch;
  }

  .auth-gate-card {
    width: 100%;
    max-width: none;
    border-radius: 22px 22px 0 0;
    padding: 12px 20px 26px;
  }

  .auth-gate-handle {
    display: block;
    width: 38px;
    height: 4px;
    border-radius: 2px;
    background: rgba(16, 42, 113, 0.18);
    margin: 0 auto 16px;
  }
}

/* ── Save modal — tabs ──────────────────────────────── */
.save-tabs {
  display: flex;
  position: relative;
  border-bottom: 1px solid #F2F1EC;
}

.save-tab {
  flex: 1;
  background: none;
  border: none;
  padding: 4px 0 11px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: var(--muted);
  cursor: pointer;
  text-align: center;
  transition: color 0.18s;
}

.save-tab.is-active {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 500;
  color: var(--dark);
}

.save-tab-indicator {
  position: absolute;
  bottom: -1px;
  left: 0;
  width: 50%;
  height: 2px;
  background: var(--dark);
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}

.save-tab-panel {
  display: none;
}

.save-tab-panel.is-active {
  display: block;
}

/* .prefs-form's own `display: flex` (see the "Preferences form" section
   below) has the SAME specificity as .save-tab-panel's `display: none`
   above, and comes later in this file, so without this rule it would win
   and make the onboarding preferences step (login.html, which reuses
   .prefs-form inside the tab-panel system) show up regardless of which
   tab/step is actually active. Two classes here beats .prefs-form's one,
   forcing it back to hidden by default; the .is-active variant right
   after this then re-enables it, at three classes, only when active. */
.save-tab-panel.prefs-form {
  display: none;
}

.save-tab-panel.is-active.prefs-form {
  display: flex;
}

.save-modal-empty {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: var(--muted);
  padding: 8px 0;
}

/* ── Save modal — two screens (collections → outfits/all products) ─── */
.save-modal-screen {
  display: none;
}

.save-modal-screen.is-active {
  display: block;
}

.save-modal-back-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-bottom: 6px;
  background: none;
  border: none;
  color: var(--dark);
  cursor: pointer;
  padding: 0;
}

/* Mobile only -- the full-page modal below has no backdrop to tap. */
.save-modal-close {
  display: none;
}

/* Mobile: the save modal fills the screen. Same two screens and controls,
   resized: bigger title and rows, lists grow to the page, and the
   Cancel/Save pair sits at the bottom. */
@media (max-width: 640px) {
  .save-modal-overlay {
    align-items: stretch;
    background: #FFFDF8;
  }

  .save-modal-box {
    width: 100%;
    max-width: none;
    height: 100%;
    border-radius: 0;
    padding: 56px 24px calc(20px + env(safe-area-inset-bottom));
    display: flex;
    flex-direction: column;
    overflow-y: auto;
  }

  .save-modal-close {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 10px;
    right: 10px;
    width: 28px;
    height: 28px;
    padding: 0;
    background: none;
    border: none;
    color: var(--dark);
    cursor: pointer;
  }

  .save-modal-title {
    font-size: 30px;
  }

  .save-modal-screen.is-active {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
  }

  .save-modal-back-btn {
    position: absolute;
    top: 10px;
    left: 10px;
    width: 28px;
    height: 28px;
    margin: 0;
  }

  .save-modal-back-btn svg {
    width: 14px;
    height: 14px;
  }

  /* Same field as the hamburger search bar and the brand filter search. */
  #saveModal .save-modal-input {
    height: 34px;
    font-size: 13px;
    padding: 0 14px;
    border-radius: 17px;
  }

  #saveModal .save-modal-input::placeholder {
    font-size: 13px;
  }

  #saveModal .save-modal-add-btn {
    width: 34px;
    height: 34px;
  }

  .save-modal-list {
    max-height: none;
    gap: 10px;
  }

  .save-outfit-item {
    padding: 4px 16px 4px 4px;
  }

  .save-outfit-item img,
  .save-outfit-thumb {
    width: 52px !important;
    height: 52px !important;
  }

  .save-outfit-item--all-products {
    padding: 18px 16px;
  }

  .save-modal-actions {
    margin-top: auto;
    padding-top: 20px;
  }

  .save-modal-cancel-btn,
  .save-modal-confirm-btn {
    height: 48px;
    border-radius: 24px;
  }
}

/* ══════════════════════════════════════════════════════
   COLLECTIONS GRID (profile page)
══════════════════════════════════════════════════════ */
.outfits-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 30px var(--gap, clamp(10px, 1.1vw, 24px));
}

.outfit-card {
  text-decoration: none;
  display: block;
  cursor: pointer;
}

.outfit-preview {
  width: 100%;
  aspect-ratio: 3 / 2;
  border-radius: var(--card-radius);
  overflow: hidden;
  background: #FCFDFF;
  display: flex;
  border: 1px solid #F2F1EC;
  box-sizing: border-box;
}

.outfit-preview img {
  height: 100%;
  object-fit: cover;
  flex-shrink: 0;
  display: block;
}

.outfit-card-name {
  display: block;
  font-family: 'LibreCaslonCondensed-SemiBold', serif;
  font-size: 20px;
  color: #102A71;
  margin-top: 13px;
}

.outfit-card-name.is-untitled {
  opacity: 0.3;
}

.outfits-empty {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 16px;
  color: var(--muted);
  text-align: center;
  margin-top: 40px;
}

/* ══════════════════════════════════════════════════════
   OUTFIT DETAIL PAGE
   (.outfit-detail-header box model is defined once, shared with
   .filters-row, up in the RESULTS PAGE — FILTERS ROW section above)
══════════════════════════════════════════════════════ */

.outfit-detail-heading {
  font-family: 'LibreCaslonCondensed-Bold', serif;
  font-size: 40px;
  font-weight: normal;
  color: #102A71;
  line-height: 1.2;
}

.outfit-detail-heading.is-untitled {
  opacity: 0.3;
}

/* Always-editable title input in the generator -- 36px to match
   search.html's own #pageTitle (body.search-page override) exactly, not
   the base .page-title's 40px. A mismatched title height was throwing
   off the whole row's height and, with it, the filter row/button
   positioning relative to search.html's. */
.cg-name-input {
  font-family: 'LibreCaslonCondensed-BoldItalic', serif;
  font-size: 36px;
  font-weight: normal;
  color: #102A71;
  line-height: 1.2;
  height: 1.2em;
  background: transparent;
  border: none;
  outline: none;
  padding: 0;
  letter-spacing: 0;
  cursor: text;
  min-width: 100px;
}

.cg-name-input:focus {
  outline: none;
  text-decoration: none;
}

.cg-name-input::placeholder {
  color: #102A71;
  opacity: 0.35;
}

.open-in-generator-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 46px;
  padding: 0 18px;
  background: #FED861;
  color: #2C56A0;
  border: none;
  /* Same box-shadow-as-shadow technique as .roll-btn -- offset-only,
     bottom edge only, so it follows the pill's border-radius exactly. */
  box-shadow: 0 3px 0 0 #2C56A0;
  border-radius: 26px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  letter-spacing: 0;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.08s ease, box-shadow 0.08s ease;
  flex-shrink: 0;
  white-space: nowrap;
}

.open-in-generator-btn:active {
  /* Shadow shrinks by exactly the distance the button moves down, so its
     bottom edge stays put -- only the yellow face visibly moves. */
  transform: translateY(3px);
  box-shadow: 0 0 0 0 #2C56A0;
}

.coll-header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.kebab-wrap {
  position: relative;
}

.kebab-btn {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: opacity 0.15s;
  margin-top: 4px;
}

.kebab-btn:hover { opacity: 0.85; }

.kebab-dropdown {
  display: none;
  flex-direction: column;
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  background: #FDFBF7;
  border-radius: 12px;
  padding: 6px;
  min-width: 190px;
  z-index: 200;
  box-shadow: 0 4px 18px rgba(87, 127, 188, 0.13);
}

.kebab-dropdown.is-open { display: flex; }

.kebab-item {
  display: block;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 14px;
  color: #102A71;
  background: none;
  border: none;
  border-radius: 8px;
  padding: 10px 14px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.12s;
  width: 100%;
  box-sizing: border-box;
}

.kebab-item:hover { background: #F7F4ED; }

.kebab-item-danger { color: #102A71; }

/* ══════════════════════════════════════════════════════
   COLLECTION SUMMARY PAGE — tabs + outfits scroll-snap
══════════════════════════════════════════════════════ */
.collection-tabs {
  display: flex;
  gap: 28px;
  padding: 0 clamp(40px, 2.08vw, 60px);
  border-bottom: 1px solid #F2F1EC;
  position: relative;
}

.collection-tab {
  background: none;
  border: none;
  padding: 4px 0 8px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 16px;
  color: var(--dark);
  opacity: 0.45;
  cursor: pointer;
  transition: opacity 0.18s;
}

.collection-tab.is-active {
  font-weight: 500;
  opacity: 1;
}

/* Sits exactly on top of .collection-tabs' own border-bottom (same -1px
   offset trick as .save-tab-indicator) and slides between tabs -- position
   and width are set from JS, measured off the active tab's own layout,
   since (unlike the save modal's fixed 50/50 tabs) these aren't equal width. */
.collection-tab-indicator {
  position: absolute;
  bottom: -1px;
  height: 2px;
  background: var(--dark);
  transition: left 0.25s cubic-bezier(0.4, 0, 0.2, 1), width 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}

.collection-tab-panel {
  display: none;
}

.collection-tab-panel.is-active {
  display: block;
}

/* Distance from the tabs divider to the product cards -- .products-section
   itself has no padding of its own (shared with search.html/generator, who
   each handle their own top spacing differently), so it's added here,
   scoped to just this panel. Larger than it used to be -- the space it
   takes is given up above the title instead (body.collection-page
   .results-header, up in RESULTS PAGE — HEADER). */
#allProductsPanel .products-section {
  padding-top: 36px;
}

.outfits-scroll {
  height: calc(100dvh - 220px);
  overflow-y: auto;
  overflow-x: hidden;
  scroll-snap-type: y mandatory;
  /* No padding here -- each .outfit-section already fills exactly this
     box's own height (see below), so any top padding on the box itself
     would only be reachable by scrolling PAST the first section's snap
     point, and some browsers snap it away before it's ever seen. The
     "distance from the tabs divider" gap lives on .outfit-section-header
     instead, which puts it at the top of every section, not just the
     first -- and since only one section is ever on screen at a time,
     that reads identically. */
  /* No visible scrollbar (still scrolls -- wheel, drag, keyboard -- the
     scroll-snap sections are the only "scrollbar" this page shows), same
     pattern as body.search-page .products-section. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.outfits-scroll::-webkit-scrollbar {
  display: none;
}

/* Fixed to the container's height, not min-height -- a section taller
   than the viewport used to push past it, so the NEXT section's title and
   "Open in generator" button peeked in at the bottom while scroll-snap
   still treated this one as "current". Any overflow inside (many products
   in one outfit) scrolls within .outfit-section .products-section instead
   of growing the section itself. Percentage height needs .outfits-scroll
   to have no padding of its own (see above) -- padding there would shrink
   this below the box's real visible height, reopening the same peek. */
.outfit-section {
  height: 100%;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
}

/* Overrides .outfit-detail-header's shared 8px top padding -- this is the
   "distance from the tabs divider" gap (see .outfits-scroll above), and
   the same increase/reason as #allProductsPanel .products-section's
   padding-top. */
.outfit-section-header {
  min-height: 60px;
  flex-shrink: 0;
  padding-top: 36px;
}

/* An outfit's own product row: capped at ~5 products (see netlify/CLAUDE.md),
   so it wraps onto extra rows within the section rather than scrolling
   sideways -- .products-scroll-wrapper's shared default (used by
   search.html/the generator) is a horizontal scroll track, which isn't
   wanted here.
   No overflow-y here on purpose -- an inner scroll region used to catch the
   wheel/trackpad gesture first (scrolling the products before the section
   itself would snap to the next one), which read as the snap "not working"
   until the cards were fully scrolled. A section is exactly the viewport's
   height with nothing scrollable inside it, so every wheel/swipe goes
   straight to .outfits-scroll's own snap-scroll. Any overflow (more
   products than the section's own height allows) is clipped rather than
   scrolled -- outfits are small enough that this shouldn't come up in
   practice. */
.outfit-section .products-section {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.outfit-section .products-scroll-wrapper {
  overflow: hidden;
}

.outfit-section .products-track {
  flex-wrap: wrap;
  width: 100%;
}

.outfit-section-heading {
  font-size: 32px;
}

.outfit-caret-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  color: var(--dark);
  cursor: pointer;
  flex-shrink: 0;
  transition: opacity 0.15s;
}

.outfit-caret-btn:hover { opacity: 0.85; }

/* The up/down pair sits closer together than the rest of the row. */
.outfit-caret-btn + .outfit-caret-btn {
  margin-left: -6px;
}

.outfit-caret-btn:disabled {
  cursor: default;
}
.outfit-caret-btn:disabled:hover { opacity: 1; }

.back-to-top-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: #6F747D;
  padding: 0 0 10px 0;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 15px;
  transition: color 0.15s;
}

.back-to-top-btn:hover { color: var(--dark); }

/* Mobile replaces the text link above with this circle beside FILTERS. */
.back-to-top-icon {
  display: none;
}

@media (max-width: 640px) {
  .back-to-top-btn {
    display: none;
  }

  .back-to-top-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    margin-left: 12px;
    padding: 0;
    border: 1px solid #F2F1EC;
    border-radius: 50%;
    background: #FCFDFF;
    color: #102A71;
    align-self: center;
    cursor: pointer;
  }

  body.explore-in-view .save-changes-btn {
    display: none;
  }
}

/* ══════════════════════════════════════════════════════
   COLLECTION GENERATOR — PICKER SLOT
══════════════════════════════════════════════════════ */
.picker-slot {
  flex: 0 0 clamp(220px, 18vw, 350px);
  /* Matches .product-card's own height exactly (same clamp expression) so
     the category-picker card is the same size as a populated product card. */
  height: clamp(330px, 27vw, 525px);
  position: relative;
  border: 1px solid #F2F1EC;
  border-radius: var(--card-radius);
  background: #F2F1EC;
  cursor: default;
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex-shrink: 0;
}

.no-results-slot {
  align-items: center;
  gap: 12px;
}

.no-results-text {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: var(--muted);
  text-align: center;
  padding: 0 24px;
}

.no-results-retry-btn {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  color: #102A71;
  background: none;
  border: none;
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
}

.picker-categories {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 0 18px;
}

/* Left-aligned dot + label, same shape as the product overlay's .attr-row
   (dot + text), just one line instead of a label/value stack -- shorter
   rows and no extra vertical padding make this read as more condensed. */
.picker-cat-row {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0;
  color: #6F747D;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: clamp(24px, 2.5vw, 30px);
  padding: 0 6px 0 12px;
  box-sizing: border-box;
  border-radius: 7px;
  cursor: pointer;
  user-select: none;
  transition: background 0.12s;
}

.picker-cat-row:hover {
  background: #F7F4ED;
}

.picker-cat-row.is-selected {
  color: var(--dark);
  /* A lit dot alone read as too subtle against the card's own beige fill
     -- this rectangle is the clearer "this one's selected" signal. */
  background: #FCFDFF;
}

/* Same concept as .tag-lock-circle in the product overlay (hollow ring,
   fills marigold when locked) -- adapted for this card's light background,
   where .tag-lock-circle's white ring would be invisible. */
.picker-cat-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  border: 1px solid #6F747D;
  background: none;
  flex-shrink: 0;
  box-sizing: border-box;
}

.picker-cat-row.is-selected .picker-cat-dot {
  background: #FED861;
  border-color: #FED861;
}

/* Typed category ("Other"): the row's own text style, as an input. */
.picker-cat-row--custom {
  cursor: text;
}

.picker-cat-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--dark);
  outline: none;
}

.picker-cat-input::placeholder {
  color: #6F747D;
  opacity: 1;
}

/* iOS zooms into inputs under 16px: render at 16px, scaled to the row's 12px. */
@media (max-width: 640px) {
  .picker-cat-input {
    font-size: 16px;
    transform: scale(0.75);
    transform-origin: left center;
    margin-right: -33%;
  }
}

.picker-divider {
  width: 100%;
  height: 1px;
  /* Was the same gray as .attr-divider in the product overlay -- now
     swapped with .picker-slot's old white fill, see above. */
  background: #FCFDFF;
  /* Inset from both edges, same treatment as .attr-divider, so the line
     reads shorter than the row's own rectangle. */
  margin: 0 10px;
  width: calc(100% - 20px);
}


/* ══════════════════════════════════════════════════════
   EDITABLE COLLECTION NAME
══════════════════════════════════════════════════════ */
.edit-name-container {
  display: flex;
  align-items: center;
  gap: 10px;
}

.edit-name-pencil {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--muted);
  padding: 4px;
  opacity: 0;
  transition: opacity 0.15s;
  flex-shrink: 0;
  display: flex;
  align-items: center;
}

.edit-name-container:hover .edit-name-pencil {
  opacity: 0.55;
}

.edit-name-pencil:hover {
  opacity: 1 !important;
}

.edit-name-input {
  font-family: 'LibreCaslonCondensed-Bold', serif;
  font-size: 40px;
  font-weight: normal;
  color: #102A71;
  border: none;
  background: transparent;
  outline: none;
  padding: 0;
  letter-spacing: 0;
  line-height: 1.2;
  height: 1.2em;
  min-width: 120px;
  text-decoration: underline solid var(--dark);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 6px;
}

/* ══════════════════════════════════════════════════════
   COLLECTION GENERATOR — SLOT GAP INSERT ZONES
══════════════════════════════════════════════════════ */
.cg-track {
  gap: 0;
}

.cg-slot-gap {
  flex-shrink: 0;
  align-self: flex-start;
  /* Matches .product-card's own height exactly (same clamp expression,
     scaled by the same 1.5 ratio) so the insert button centers on the
     image, not the taller slot (image + attrs + name/vendor/price below
     it). Without an explicit height here, the button's only content is
     absolutely positioned and contributes nothing to this element's own
     size, so it collapses to 0 height and pins to the row's top edge. */
  height: clamp(330px, 27vw, 525px);
  width: clamp(10px, 1.1vw, 24px);
  position: relative;
  display: block;
  align-items: center;
  justify-content: center;
  overflow: visible;
  z-index: 10;
}

.cg-slot-gap::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -5px;
  right: -5px;
  z-index: 1;
}

.cg-slot-insert-btn {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0);
  width: clamp(28px, 2.5vw, 40px);
  height: clamp(28px, 2.5vw, 40px);
  border-radius: 50%;
  border: none;
  background: #2C56A0;
  color: #FFFFFF;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  z-index: 10;
  opacity: 0;
  pointer-events: none;
  /* Pop in from 0 -> 100% scale on hover-enter, with a slight overshoot
     (the curve's y > 1 partway through) instead of a flat linear grow, so
     it reads as a deliberate pop rather than the flat opacity fade-in it
     replaced. Kept subtle -- y capped just above 1 instead of the earlier
     1.56, which read as too dramatic a bounce. */
  transition: transform 0.22s cubic-bezier(0.34, 1.15, 0.64, 1), opacity 0.12s ease, background 0.15s;
}

.cg-slot-insert-btn:hover {
  background: #102A71;
}

.cg-slot-gap.show-btn .cg-slot-insert-btn {
  transform: translate(-50%, -50%) scale(1);
  opacity: 1;
  pointer-events: auto;
}

/* ══════════════════════════════════════════════════════
   CARD FADE-IN ANIMATION
══════════════════════════════════════════════════════ */


/* ══════════════════════════════════════════════════════
   SEARCH / ADD ICON BUTTONS (outfit generator & explore)
══════════════════════════════════════════════════════ */
.search-icon-btn,
.add-icon-btn,
.img-search-btn {
  width: 28px;
  height: 28px;
  border-radius: 7px;
  border: 1px solid rgba(242, 241, 236, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  padding: 0;
  background: none;
  color: #FFFFFF;
  margin-left: auto;
}

.save-btn svg,
.bag-btn svg {
  width: 16px;
  height: 16px;
}

.img-search-btn svg,
.search-icon-btn svg,
.add-icon-btn svg,
.remove-icon-btn svg {
  width: 15px;
  height: 15px;
}

/* ══════════════════════════════════════════════════════
   IMAGE CROP SEARCH
══════════════════════════════════════════════════════ */
.product-card.is-img-selecting {
  cursor: crosshair;
  user-select: none;
}

/* A finger drag draws the box instead of scrolling the grid (the results
   grid's own cards allow vertical scrolling). */
.products-scroll-wrapper .product-card.is-img-selecting,
.product-card.is-img-selecting * {
  touch-action: none;
}

/* Selecting: the photo is only dimmed (by .img-select-svg), never blurred,
   and the dragged-out area shows through clear. */
.product-card.is-img-selecting .product-img {
  filter: none;
}

.product-card.is-img-selecting .card-overlay {
  visibility: hidden;
}

.img-select-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 5;
}

.sel-too-small {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(20, 29, 41, 0.88);
  color: #F2F1EC;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  padding: 5px 12px;
  border-radius: 6px;
  white-space: nowrap;
  z-index: 10;
  pointer-events: none;
}

.img-search-loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(20, 29, 41, 0.4);
  z-index: 10;
}

.img-search-spinner {
  width: 28px;
  height: 28px;
  border: 2px solid rgba(242, 241, 236, 0.3);
  border-top-color: #F2F1EC;
  border-radius: 50%;
  animation: imgSearchSpin 0.7s linear infinite;
}

@keyframes imgSearchSpin {
  to { transform: rotate(360deg); }
}

/* ══════════════════════════════════════════════════════
   EXPLORE SECTION (bottom half of collection generator)
══════════════════════════════════════════════════════ */
/* Site header is 71px tall + .results-header's own margin-bottom:40px
   = 111px of space above .top-half — NOT 82px. That stale constant
   under-budgeted by 29px, which is exactly how much the page overflowed
   the viewport (forcing a scrollbar) even with explore-more closed and
   no element sizing changed. */
.top-half {
  min-height: calc(100vh - 111px);
}

.explore-section {
  display: none;
  border-top: 1px solid #F2F1EC;
  padding-top: 28px;
  position: relative;
}

/* Nominally 100vh, same as search.html's own body -- but height:100vh
   alone doesn't stop its children (label/title/filters-row/products grid/
   roll bar) spilling past it in normal block flow, which is what forced
   extra scrolling to reach the bottom of a section that was supposed to
   already be exactly one screen tall. display:flex column + the flex:1
   products-section below (mirroring body.search-page's own products-
   section) makes the single row of cards absorb whatever space is left
   after the fixed-height chrome above/below it, the same way search.html
   never needs to scroll for a real result set. */
.explore-section.is-visible {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}

.explore-title-group {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex-shrink: 0;
  align-self: flex-start;
}

.explore-section-label {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Mobile swaps the label + product name for one search-style title. */
.explore-mobile-title {
  display: none;
}

@media (max-width: 640px) {
  .explore-title-group {
    display: none;
  }

  .explore-section .filters-row .explore-mobile-title {
    display: block;
    align-self: flex-start;
    font-size: 30px;
    line-height: 1.2;
  }
}

/* Same box model as search.html's own #pageTitle -- folded into the
   filters-row itself (see markup) instead of sitting in a separate block
   above it, so the title lands in the same place on this page as it does
   on search.html, and the section doesn't pay for a second block's worth
   of margin/padding on top of the filters-row's own. */
body.generator-page .explore-section.is-visible .products-section {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-bottom: calc(clamp(23px, calc(4.5dvh - 5px), 47px) + 52px);
}

.explore-section .products-section::-webkit-scrollbar {
  display: none;
}

/* Mobile: title text 5px smaller across the board. Placed at the end of
   the file so it wins the cascade over every same-specificity base rule
   above, regardless of where each selector is defined. */
@media (max-width: 640px) {
  .nav-title {
    font-size: 23px;
  }

  .page-title {
    font-size: 20px;
  }

  /* Outranks search.html's own inline 36px page-title rule. The min-height
     reserves the line before the title text is filled in, so the row (and
     the FILTERS link beside it) doesn't shift down when it appears. */
  body.search-page .filters-row .page-title {
    font-size: 30px;
    line-height: 1.2;
    min-height: 1.2em;
  }

  .page-title-quote,
  .outfit-detail-heading {
    font-size: 24px;
  }

  /* Same size as search.html's title. */
  .cg-name-input {
    font-size: 30px;
  }
}

/* ══════════════════════════════════════════════════════
   AUTH PAGES (login.html, reset-password.html) — a standalone
   save-modal-style card, not overlaid.
══════════════════════════════════════════════════════ */
/* Centered between the header (57px + 40px margin) and the legal footer
   (48px), so the sign-up card fits a 720px-tall window without scrolling. */
.auth-main {
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: calc(100vh - 145px);
  min-height: calc(100dvh - 145px);
  padding: 12px 16px;
  transition: padding-top 0.35s ease;
}

/* .header-search-row is position:absolute, so opening it overlays the
   page instead of pushing it down -- and the auth card sits high enough
   that the open row lands 3px above it. Drop the card while the row is
   open, in step with the row's own 0.35s max-height transition. Scoped
   to the auth pages: elsewhere the row overlays scrolling content, which
   is what it's meant to do. */
body:has(.header-search-row.is-open) .auth-main {
  padding-top: 76px;
}

.auth-card {
  background: #FFFDF8;
  border-radius: 18px;
  padding: 32px;
  width: 560px;
  max-width: 100%;
  color: var(--dark);
  box-shadow: 0 2px 18px rgba(87, 127, 188, 0.08);
}

.auth-card .save-modal-input-row {
  margin-top: 14px;
  margin-bottom: 0;
}

/* Same pill/height as .save-modal-input so it reads as part of the same
   field group, not a differently-shaped button dropped in above them. */
.oauth-google-btn {
  width: 100%;
  height: 44px;
  margin-top: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  border: 1px solid #F2F1EC;
  border-radius: 22px;
  background: #FCFDFF;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: var(--dark);
  cursor: pointer;
  transition: background 0.15s;
}

.oauth-google-btn:hover {
  background: #F2F1EC;
}

.oauth-google-btn svg {
  flex-shrink: 0;
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 14px 0;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: #F2F1EC;
}

/* Same .save-modal-input pill as every other field (identical width to
   the email field above it) -- the eye button is absolutely positioned
   *over* it, inside its border, rather than sitting beside it as its own
   separate box. */
.password-input-wrap {
  position: relative;
  flex: 1;
  display: flex;
}

.password-input-wrap .save-modal-input {
  width: 100%;
  padding-right: 44px;
}

/* Safari's caps-lock arrow inside password boxes. */
input::-webkit-caps-lock-indicator {
  content: none;
}

.password-toggle-btn {
  position: absolute;
  right: 4px;
  top: 4px;
  bottom: 4px;
  width: 36px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--dark);
  opacity: 0.55;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.15s;
}

.password-toggle-btn:hover {
  opacity: 1;
}

/* ══════════════════════════════════════════════════════
   SETTINGS PAGES (edit-profile.html, account.html, preferences.html) --
   full-width, with a left-side nav between the three. Deliberately NOT the
   boxed .auth-card treatment above: that's reserved for the initial
   sign-up/log-in flow, where staying inside one small box (including its
   later multi-step onboarding) is the point.
══════════════════════════════════════════════════════ */
.settings-main {
  display: flex;
  align-items: flex-start;
  gap: clamp(32px, 4vw, 64px);
  max-width: 1320px;
  margin: 0 auto;
  padding: clamp(32px, 4vw, 48px) clamp(20px, 4vw, 60px) 72px;
}

.settings-sidebar {
  flex: 0 0 200px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: sticky;
  top: 24px;
}

.settings-nav-link {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  color: #102A71;
  text-decoration: none;
  /* Matches .nav-link's 9px so the LINK TEXT starts on the same x as "Search"
     in the header. The box already lined up at 14px, but with no fill behind
     the active link it's the text edge that reads as the alignment. */
  padding: 10px 9px;
  border-radius: 8px;
  transition: background 0.12s, color 0.12s;
}

.settings-nav-link:hover { background: #FCFDFF; }
.settings-nav-link.is-active {
  background: none;
  color: #102A71;
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* .save-modal-title is centered by default (right for the small popup
   modal it was built for) -- these page headings sit in a normal
   left-aligned page layout, not a centered box. */
.settings-content .save-modal-title {
  text-align: left;
}

.settings-content {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 760px;
}

@media (max-width: 720px) {
  .settings-main { flex-direction: column; }
  .settings-sidebar {
    flex-direction: row;
    position: static;
    width: 100%;
    overflow-x: auto;
    gap: 8px;
    /* Its 200px flex-basis is a WIDTH beside the content, but becomes a 200px
       HEIGHT once .settings-main stacks -- which left the nav row as a
       200px-tall band with a stretched active pill. Height comes from the
       links here. */
    flex: 0 0 auto;
  }
  .settings-nav-link { flex-shrink: 0; }

  .settings-sidebar {
    scrollbar-width: thin;
    scrollbar-color: rgba(126, 131, 140, 0.45) transparent;
  }
  .settings-sidebar::-webkit-scrollbar {
    height: 6px;
  }
  .settings-sidebar::-webkit-scrollbar-track {
    background: transparent;
  }
  .settings-sidebar::-webkit-scrollbar-thumb {
    background: rgba(126, 131, 140, 0.45);
    border-radius: 3px;
  }
}

/* ══════════════════════════════════════════════════════
   REFINE YOUR RECOMMENDATIONS -- shared between preferences.html (settings,
   full-size grid) and the onboarding refine step in login.html (boxed,
   compact grid). See refine-recommendations.js.
══════════════════════════════════════════════════════ */
.refine-section {
  margin-top: clamp(28px, 3vw, 40px);
  max-width: 720px;
}

.refine-title {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 500;
  font-size: 16px;
  color: var(--dark);
}

.refine-stage-label {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #102A71;
  text-align: center;
  margin: 0 0 2px;
}

.refine-stage-label:empty {
  display: none;
}

.refine-subtitle {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  color: #6F747D;
  margin-top: 4px;
  margin-bottom: 16px;
}

/* Centered only for the onboarding step (login.html) -- the settings-page
   refine section (preferences.html, "Uncheck the options...") sits in a
   normal left-aligned page layout, not the centered auth-card box. */
#onboardingRefinePanel .refine-subtitle {
  text-align: center;
  font-size: 15px;
}

#onboardingRefinePanel .refine-stage-label {
  font-size: 15px;
}

/* Same card shape as search.html's product cards. */
#onboardingRefinePanel .refine-item-img-wrap {
  aspect-ratio: 1 / 1.5;
  border-radius: var(--card-radius);
}

#onboardingRefinePanel .refine-item-name {
  font-size: 13px;
  margin-top: 8px;
}

.refine-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 12px;
}

.refine-grid.is-compact {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px 12px;
  padding: 2px;
  scrollbar-width: thin;
  scrollbar-color: rgba(126, 131, 140, 0.45) transparent;
}

.refine-grid.is-compact::-webkit-scrollbar {
  width: 6px;
}
.refine-grid.is-compact::-webkit-scrollbar-track {
  background: transparent;
}
.refine-grid.is-compact::-webkit-scrollbar-thumb {
  background: rgba(126, 131, 140, 0.45);
  border-radius: 3px;
}

.refine-item {
  position: relative;
  cursor: pointer;
}

.refine-item-img-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  border-radius: 10px;
  overflow: hidden;
  background: #FCFDFF;
  box-shadow: 0 0 0 0 #2C56A0;
  transition: box-shadow 0.15s;
}

.refine-item-img-wrap img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Selection reads via the checkbox + a highlight ring on the SELECTED
   item, not by dimming the unselected ones -- dimming looked wrong
   wherever most items start unchecked (the onboarding refine step, where
   the whole grid would otherwise render dim by default before anyone has
   picked anything). */
.refine-item.is-checked .refine-item-img-wrap {
  box-shadow: 0 0 0 1px #2C56A0;
}

.refine-item.is-checked:hover .refine-item-img-wrap {
  box-shadow: 0 0 0 1px #102A71;
}

.refine-item-check {
  position: absolute;
  top: 6px;
  left: 6px;
  width: 20px;
  height: 20px;
  border-radius: 6px;
  background: #FCFDFF;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #102A71;
  z-index: 2;
  transition: background 0.12s;
}
.refine-item:hover .refine-item-check { background: #F2F1EC; }

.refine-item-check svg { display: none; }
.refine-item.is-checked .refine-item-check svg { display: block; }

.refine-item-name {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  color: var(--dark);
  margin-top: 4px;
  /* Up to two lines, then an ellipsis. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-height: 1.25;
  overflow: hidden;
  overflow-wrap: anywhere;
}

.refine-empty {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  color: #6F747D;
}

#onboardingRefinePanel .refine-empty {
  grid-column: 1 / -1;
  font-size: 15px;
  text-align: center;
  margin: 40px 0;
}

#guestRow {
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid #F2F1EC;
}

.auth-card .save-modal-actions {
  margin-top: 22px;
}

.auth-msg {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  line-height: 1.4;
  margin-top: 14px;
  margin-bottom: 0;
  min-height: 1.4em;
}

/* No message to show -- collapse entirely rather than reserve its
   min-height/margins, so surrounding fields sit at their normal gap. */
.auth-msg:empty {
  display: none;
}

.auth-msg.is-error { color: #102A71; }
.auth-msg.is-info  { color: #102A71; }

.auth-pending .auth-card {
  visibility: hidden;
}

/* Stacked on phones so "Set preferences now" stays on one line. */
@media (max-width: 640px) {
  #askPrefsPanel .save-modal-actions {
    flex-direction: column;
    align-items: stretch;
  }

  #askPrefsPanel .save-modal-actions button {
    flex: none;
    width: 100%;
  }
}

.save-tab-panel .auth-subhead {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  line-height: 1.5;
  color: #102A71;
  text-align: center;
  margin: 0 0 22px;
  overflow-wrap: anywhere;
}

/* Message-only panels ("Check your email", "Continue in the new tab"): more
   room under the heading, taken from below so the card keeps its height. */
.save-tab-panel .auth-subhead.auth-subhead-message {
  margin: 16px 0 10px;
}

.auth-link-row {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  text-align: center;
  margin-top: 18px;
}

.auth-card .auth-link-row {
  font-size: 13px;
}

.auth-link-row button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: #102A71;
  text-decoration: underline;
  cursor: pointer;
}

/* ══════════════════════════════════════════════════════
   PROFILE PAGE
══════════════════════════════════════════════════════ */
/* Capped at four ~220px collection previews per row (plus the grid's
   column gaps and the side padding), centered, so wider windows get wider
   side margins instead of more, or bigger, previews. */
.profile-section {
  --gap: clamp(10px, 1.1vw, 24px);
  --side: clamp(40px, 2.08vw, 60px);
  max-width: calc(4 * 220px + 3 * var(--gap) + 2 * var(--side));
  margin: 0 auto;
  padding: clamp(32px, 4vw, 48px) var(--side) clamp(48px, 6vw, 72px);
}

.profile-top-row {
  display: flex;
  align-items: flex-start;
  gap: clamp(20px, 2vw, 28px);
}

.profile-avatar-circle {
  flex-shrink: 0;
  width: clamp(100px, 8vw, 135px);
  height: clamp(100px, 8vw, 135px);
  border: 1px solid #F2F1EC;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.profile-avatar-circle svg {
  width: 55%;
  height: 55%;
}

.profile-info {
  display: flex;
  flex-direction: column;
  padding-top: 6px;
}

.profile-name {
  font-family: 'LibreCaslonCondensed-Regular', serif;
  font-size: clamp(28px, 2.2vw, 34px);
  color: #102A71;
  line-height: 1.2;
}

.profile-username {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 14px;
  color: #102A71;
  margin-top: 4px;
}

.profile-actions-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: clamp(20px, 2.5vw, 28px);
}

/* Same outline + hover-fill as .nav-arrow-btn: a pill for Edit Profile,
   circles for the icon buttons. */
.profile-action-btn,
.profile-action-btn-square {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 15px;
  color: #102A71;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s;
}

.profile-action-btn:hover,
.profile-action-btn-square:hover {
  background: #FED861;
}

.profile-action-btn {
  height: 46px;
  padding: 0 26px;
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.profile-action-btn-square {
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* Default avatar before a photo is uploaded. line-height:1 keeps the
   letter centered instead of riding on the font's ascender space. */
.profile-avatar-circle.has-initial {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 500;
  font-size: clamp(40px, 3.5vw, 56px);
  line-height: 1;
  color: #FCFDFF;
  background: #2C56A0;
}

.profile-collections {
  margin-top: clamp(32px, 3vw, 44px);
  padding-top: clamp(28px, 3vw, 40px);
  border-top: 1px solid #F2F1EC;
}

/* Same look as search.html's section title (.page-title there). */
.profile-section-title {
  font-family: 'LibreCaslonCondensed-BoldItalic', serif;
  font-weight: normal;
  font-size: 36px;
  line-height: 1.12;
  color: #102A71;
  text-align: left;
  margin-bottom: 24px;
}

.profile-collections .outfits-empty {
  text-align: left;
  margin-top: 0;
}

/* Preferences form */
/* No max-width of its own: the form fills whatever column holds it --
   .settings-content on the settings pages, .auth-card (560px, so already
   narrower than the old 560px cap) in login.html's onboarding step. A cap
   here just left the form narrower than, and off-centre from, its heading. */
.prefs-form {
  margin-top: clamp(28px, 3vw, 40px);
  display: flex;
  flex-direction: column;
  gap: 22px;
}

/* The form's own gap already spaces the Next button from the last field. */
#onboardingPrefsForm .save-modal-actions {
  margin-top: 0;
  padding-top: 0;
}

/* Same small-caps section-marker treatment as .explore-section-label on the
   Explore panel -- minus that rule's own gutter padding, which isn't part of
   the type style. Weight stays Medium: at 13px uppercase in --muted, Regular
   goes too faint next to the chips. */
.prefs-field label,
.prefs-field .prefs-label {
  display: block;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 500;
  font-size: 13px;
  color: #102A71;
  margin-bottom: 8px;
}

.prefs-field .save-modal-input {
  width: 100%;
}

.prefs-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* Budget min/max -- not full row width, just enough to hold a dollar
   amount. */
.prefs-row .save-modal-input {
  flex: 0 0 auto;
  width: 120px;
  min-width: 90px;
  height: 30px;
  padding: 0 10px;
  font-size: 12px;
}

/* Brand typeahead -- also capped rather than stretching the full form
   width. flex:0 0 auto overrides the base .save-modal-input's
   flex:1 1 0%, which otherwise grows the field to fill its row
   regardless of the width set here (a flex item's flex-basis wins over
   width on the main axis). */
.prefs-typeahead .save-modal-input {
  flex: 0 0 auto;
  /* Matches Min (120px) + gap (10px) + Max (120px) exactly, so its right
     edge lines up with the budget row's right edge. */
  width: 250px;
  height: 30px;
  padding: 0 10px;
  font-size: 12px;
}

/* Placeholder text has its own rule (.save-modal-input::placeholder,
   15px) that doesn't follow the input's own font-size override above --
   without this, "Type a brand name" stayed at 15px while the typed text
   itself (and the Min/Max placeholders below) had already shrunk to
   12px. */
.prefs-row .save-modal-input::placeholder,
.prefs-typeahead .save-modal-input::placeholder {
  font-size: 12px;
}

/* .save-modal-input-row's own margin-top (18px, meant for the taller
   login/signup/save-modal fields) stacked on top of .prefs-field label's
   margin-bottom (8px) here, leaving "Favorite brands" sitting much
   further from its field than every other label/field pair in this form
   (which just have the label's own 8px). Zeroed so the gap matches. */
.prefs-typeahead .save-modal-input-row {
  margin-top: 0;
}

.prefs-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.prefs-chips:not(:empty) {
  margin-top: 10px;
}

/* Forces a wrap inside .prefs-chips. Letter sizing and numeric sizing are two
   different systems sharing one pick-list, and running them together reads as
   one continuous sequence (...XL, XXL, 000, 00...). A zero-height item at full
   basis breaks the row; the container's 8px gap lands on both sides of it, so
   the two groups also get a bit more air between them than chips within a run. */
.prefs-chips-break {
  flex-basis: 100%;
  height: 0;
}

/* Unselected chips sit back in --muted so navy reads as "selected" and
   nothing else. Only .is-active below keeps the navy fill. */
.prefs-chip {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 12px;
  color: #102A71;
  background: #FCFDFF;
  border: 1px solid #F2F1EC;
  border-radius: 13px;
  height: 26px;
  /* At least 1.25x as wide as tall. */
  min-width: calc(26px * 1.25);
  padding: 0 7px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}

/* Hover previews the selected state: the outline and text pick up the navy
   that .is-active fills with, so the chip reads as "clicking this selects it".
   No fill -- the fill is what distinguishes selected from merely hovered. */
.prefs-chip:not(.is-active):hover {
  border-color: #2d5290;
  color: #2d5290;
}

.prefs-chip.is-active {
  background: #FED861;
  border-color: #FED861;
  color: #102A71;
}

.prefs-chip .prefs-chip-x {
  font-size: 12px;
  opacity: 0.7;
}

.prefs-gender-row {
  display: flex;
  gap: 6px;
}

.prefs-gender-row .gender-btn {
  height: 26px;
}

.prefs-gender-row .gender-btn.active {
  font-weight: 400;
}

.prefs-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}

/* Same height and type size as the settings pages' text fields. */
.prefs-actions .save-modal-confirm-btn,
.prefs-actions .save-modal-cancel-btn,
#deleteAccountBtn {
  flex: 0 0 auto;
  height: 44px;
  border-radius: 22px;
  font-size: 15px;
  padding: 0 20px;
}

.prefs-actions .save-modal-cancel-btn,
#deleteAccountBtn {
  border: 1px solid #2C56A0;
  transition: border-color 0.15s;
}

.prefs-actions .save-modal-cancel-btn:hover,
#deleteAccountBtn:hover {
  border-color: #102A71;
}

/* ── Legal links footer (logged-out visitors, header.js) ────────────── */
.site-legal-footer {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px 24px;
  padding: 20px 16px 12px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 12px;
}
.site-legal-footer a {
  color: var(--muted);
  text-decoration: none;
}
.site-legal-footer a:hover { color: #102A71; text-decoration: underline; text-underline-offset: 3px; }
.site-legal-footer.is-floating {
  position: fixed;
  left: 16px;
  bottom: 6px;
  z-index: 21;
  padding: 0;
  gap: 16px;
  justify-content: flex-start;
  font-size: 11px;
}
@media (max-width: 640px) {
  .site-legal-footer.is-floating { display: none; }
}

.mobile-nav-legal {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  padding-top: 16px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 11px;
}
.mobile-nav-legal[hidden] { display: none; }
.mobile-nav-legal a { color: var(--muted); text-decoration: none; }

/* ── Cookie banner (analytics.js) ───────────────────────────────────── */
.cookie-banner {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: 6px;
  z-index: 2147483001;
  max-width: 700px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 18px;
  background: #FFFDF8;
  border-radius: 18px;
  box-shadow: 0 2px 18px rgba(87, 127, 188, 0.18);
  font-family: 'SpaceGrotesk', sans-serif;
  color: #102A71;
}
.cookie-banner-text {
  flex: 1 1 auto;
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
}
.cookie-banner-text a { color: #2C56A0; }
.cookie-banner-actions {
  flex: 0 0 auto;
  display: flex;
  gap: 8px;
}
.cookie-banner-btn {
  min-width: 84px;
  height: 36px;
  padding: 0 16px;
  border: 1px solid #2C56A0;
  border-radius: 999px;
  background: #FFFDF8;
  color: #2C56A0;
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 13px;
  cursor: pointer;
}
.cookie-banner-btn:hover { background: #2C56A0; color: #FFFDF8; }
@media (max-width: 560px) {
  .cookie-banner { flex-direction: column; align-items: stretch; }
  .cookie-banner-actions .cookie-banner-btn { flex: 1 1 0; }
}

/* Sign-up consent line (login.html). */
.auth-legal {
  margin: 12px 0 0;
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 13px;
  line-height: 1.5;
  text-align: center;
  color: var(--muted);
}
.auth-legal a { color: #2C56A0; }

/* ── Legal pages (cookie-policy.html) ───────────────────────────────── */
.legal-page {
  max-width: 820px;
  margin: 0 auto;
  padding: clamp(32px, 4vw, 48px) 16px 72px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 14px;
  line-height: 1.6;
  color: #102A71;
}
.legal-page .save-modal-title { text-align: left; }
.legal-page h2 {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 500;
  font-size: 18px;
  margin: 36px 0 10px;
}
.legal-page h3 {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 500;
  font-size: 15px;
  margin: 24px 0 8px;
}
.legal-page p { margin: 0 0 12px; }
.legal-page ul { margin: 0 0 12px; padding-left: 20px; }
.legal-page a { color: #2C56A0; word-break: break-word; }
.legal-page strong { font-weight: 500; }
.legal-updated { color: var(--muted); margin-top: 8px !important; }

.legal-table-wrap {
  overflow-x: auto;
  margin: 0 0 16px;
  border: 1px solid #F2F1EC;
  border-radius: 8px;
}
.legal-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.legal-table th,
.legal-table td {
  text-align: left;
  vertical-align: top;
  padding: 8px 10px;
  border-bottom: 1px solid #F2F1EC;
}
.legal-table th { font-weight: 500; white-space: nowrap; }
.legal-table tr:last-child td { border-bottom: none; }
.legal-table td:first-child { overflow-wrap: anywhere; min-width: 120px; }
.legal-page ul ul { margin: 6px 0 0; }
.legal-page li + li { margin-top: 4px; }
.legal-toc { margin: 0 0 12px; padding-left: 22px; }
.legal-short { color: var(--muted); }

/* Privacy page for logged-out visitors: same column as the policy pages. */
.settings-main.is-signed-out {
  display: block;
  max-width: 820px;
  padding: clamp(32px, 4vw, 48px) 16px 72px;
}
.settings-main.is-signed-out .settings-content { max-width: none; }

/* ── Privacy (privacy.html) ─────────────────────────── */
.settings-sidebar[hidden] { display: none; }
.privacy-intro,
.privacy-note {
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 14px;
  line-height: 1.55;
  color: #102A71;
  margin: 12px 0 0;
}
.privacy-note { margin-top: 24px; color: var(--muted); }
.privacy-section-title {
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 500;
  font-size: 15px;
  color: #102A71;
  margin: 40px 0 0;
}
.privacy-section-title + .privacy-note { margin-top: 8px; }
.privacy-choices .privacy-note { margin-top: 0; }
.privacy-note a { color: #2C56A0; }

.privacy-choices {
  margin-top: 28px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.privacy-choice {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  cursor: pointer;
}
.privacy-choice[hidden] { display: none; }
/* Same size and shape as the Refine grid's .refine-item-check. */
.privacy-choice input {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin: 0;
  border-radius: 6px;
  background: #FCFDFF center / 14.5px 14.5px no-repeat;
  border: 1px solid #2C56A0;
  cursor: pointer;
}
.privacy-choice input:hover { border-color: #102A71; }
.privacy-choice input:checked {
  background-color: #2C56A0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpolyline points='2,7 5,10.5 12,3' fill='none' stroke='%23FCFDFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.privacy-choice:hover input:not(:disabled) { border-color: #102A71; }
.privacy-choice:hover input:checked:not(:disabled) { background-color: #102A71; }
.privacy-choice input:focus-visible { outline: 2px solid #2C56A0; outline-offset: 2px; }
.privacy-choice input:disabled { cursor: default; opacity: 0.5; }

.privacy-choice-title {
  display: block;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 500;
  font-size: 15px;
  color: #102A71;
  margin-bottom: 4px;
}
.privacy-choice-text {
  display: block;
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
}

/* ── Profile avatar upload ─────────────────────────────────────────── */
/* Must shrink to the circle it wraps. As a full-width block it stretched to
   the whole .settings-content column, which made the parent's text-align:center
   a no-op on it and left .avatar-edit-btn (absolute, right/bottom: -2px)
   anchored to the column's right edge instead of the circle's corner.
   vertical-align keeps the inline-block off the text baseline, which would
   otherwise add a descender's worth of space under it. */
.profile-avatar-wrap {
  position: relative;
  display: inline-block;
  vertical-align: top;
  flex-shrink: 0;
}

.profile-avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

.avatar-edit-btn {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: #2C56A0;
  color: var(--cream);
  border: 1px solid #F2F1EC;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s;
}

.avatar-edit-btn:hover {
  background: #102A71;
  color: var(--cream);
}

/* ── Favorite-brands typeahead ─────────────────────────────────────── */
.prefs-typeahead {
  position: relative;
}

.prefs-suggestions {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: var(--cream);
  border: 1px solid #F2F1EC;
  border-radius: 12px;
  box-shadow: 0 6px 18px rgba(87, 127, 188, 0.12);
  z-index: 20;
  max-height: 220px;
  overflow-y: auto;
  padding: 6px;
}

.prefs-suggestion {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 8px 10px;
  border-radius: 8px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-weight: 400;
  font-size: 13px;
  color: var(--dark);
  cursor: pointer;
}

.prefs-suggestion:hover {
  background: #FCFDFF;
}

/* ══════════════════════════════════════════════════════
   MOBILE — FILTERS popup (search.html, outfit-generator.html
   main + explore). Placed last so it wins the cascade over every
   same-specificity desktop rule above regardless of file position.
══════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  .filters-trigger {
    display: inline-block;
    font-size: 12px;
  }

  /* Pull the title/header row closer to the top bar. */
  .results-header {
    margin-bottom: 18px;
  }

  /* Pull the title text closer to the header — and further left — on
     every page that uses this shared box model. collection.html's
     title row is included even though it has no
     filters button; on search.html / outfit-generator.html, the
     FILTERS trigger moves left along with the title for free since
     it's left-aligned in the same stacked column below it. */
  .filters-row,
  .outfit-detail-header {
    min-height: 0;
    padding-top: 10px;
    padding-bottom: 11px;
    padding-left: 24px;
    padding-right: 24px;
  }

  /* The generator's title row gets more room above its grid. */
  .outfit-detail-header.cg-title-row {
    padding-bottom: 21px;
  }

  /* Every filter row (search, generator, explore): title, filter pills and
     FILTERS on one line, FILTERS at the right edge. The pills sit level
     with the title, without labels, 5px apart, paged with "+N" (see
     TCAFilters.renderSummary). A long title gives way before the pills. */
  .filters-row,
  .cg-title-row {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
  }

  .filters-row .page-title,
  .cg-title-row .edit-name-container {
    flex-shrink: 1;
    min-width: 0;
  }

  /* Italic glyphs (the J's swash, descenders) draw outside the text box;
     the padding keeps them inside the overflow clip and the matching
     negative margin leaves the layout where it was. */
  .filters-row .page-title {
    overflow: hidden;
    text-overflow: ellipsis;
    padding: 0 0.1em 0.15em 0.2em;
    margin: 0 -0.1em -0.15em -0.2em;
  }

  .filters-row > .locked-attrs-summary,
  .cg-title-row > .locked-attrs-summary {
    display: flex;
    flex: 1 0 96px;
    min-width: 0;
    margin: 0 0 0 12px;
    flex-wrap: nowrap;
    align-items: center;
    align-self: center;
    gap: 5px;
    overflow: hidden;
  }

  .filters-row > .locked-attrs-summary:empty,
  .cg-title-row > .locked-attrs-summary:empty {
    display: none;
  }

  .filters-row .filters-trigger,
  .cg-title-row .filters-trigger {
    margin-left: auto;
    padding-left: 10px;
    flex-shrink: 0;
  }

  .summary-more {
    flex-shrink: 0;
    height: 21px;
    padding: 0 2px;
    background: none;
    border: none;
    font-family: 'SpaceGrotesk', sans-serif;
    font-weight: 600;
    font-size: 12px;
    color: #102A71;
    cursor: pointer;
  }

  /* Top-right of the drawer, its cross 18px from the top and right edges
     (button padding 6px + the icon's 2px inset) -- the hamburger's X,
     mirrored. */
  .filters-popup-close {
    display: flex;
    top: 10px;
    right: 10px;
  }

  /* Repurposed as a drawer from the right, the hamburger drawer's twin:
     85% wide over a dimmed page (.filters-backdrop), hidden with no
     shadow once closed. Desktop's flex row is overridden entirely. */
  .price-gender-group {
    position: fixed;
    inset: 0 0 0 auto;
    width: 85%;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin-left: 0;
    background: #FDFBF7;
    padding: 50px 24px 24px;
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 0.3s ease, box-shadow 0.3s ease, visibility 0s linear 0.3s;
    z-index: 1000;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: rgba(126, 131, 140, 0.45) transparent;
  }

  .filters-row > .price-gender-group,
  .outfit-detail-header > .price-gender-group {
    align-self: stretch;
  }

  .filters-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(20, 29, 41, 0.45);
    z-index: 999;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
  }

  .filters-backdrop.is-open {
    opacity: 1;
    pointer-events: auto;
  }

  .price-gender-group::-webkit-scrollbar {
    width: 6px;
  }
  .price-gender-group::-webkit-scrollbar-track {
    background: transparent;
  }
  .price-gender-group::-webkit-scrollbar-thumb {
    background: rgba(126, 131, 140, 0.45);
    border-radius: 3px;
  }

  .price-gender-group.is-open {
    transform: translateX(0);
    visibility: visible;
    box-shadow: -4px 0 24px rgba(20, 29, 41, 0.12);
    transition: transform 0.3s ease, box-shadow 0.3s ease, visibility 0s;
  }

  /* Every section is laid out open -- a heading, then its controls. */
  .filter-col {
    gap: 0;
    border-bottom: 1px solid #F2F1EC;
  }

  .filter-col:last-child {
    border-bottom: none;
  }

  .filter-toggle-btn {
    width: 100%;
    padding: 18px 2px 12px;
  }

  .filter-toggle-btn .filter-label {
    font-size: 15px;
  }

  .filter-col-fields {
    padding: 0 2px 18px;
  }

  /* PRICE: typed Min/Max boxes with a "$" prefix, styled like the
     drawer's brand field. The drag slider stays hidden (base rule above);
     its rules below are unused but kept alongside initPriceSlider. */
  .price-gender-group .price-input-wrap {
    display: flex;
    align-items: center;
    flex: 1 1 0;
    min-width: 0;
    height: 34px;
    box-sizing: border-box;
    padding: 0 14px;
    border: 1px solid #F2F1EC;
    border-radius: 17px;
    background: #FCFDFF;
    cursor: text;
  }

  .price-gender-group .price-input-prefix {
    display: block;
    font-family: 'SpaceGrotesk', sans-serif;
    font-size: 16px;
    color: #6F747D;
  }

  /* 16px so iOS Safari doesn't zoom the page on focus. */
  .price-gender-group .price-input {
    width: 100%;
    min-width: 0;
    height: 100%;
    padding: 0 0 0 4px;
    border: none;
    border-radius: 0;
    background: transparent;
    font-size: 16px;
    text-align: left;
  }

  .price-range-labels {
    display: flex;
    justify-content: space-between;
    font-family: 'SpaceGrotesk', sans-serif;
    font-weight: 400;
    font-size: 13px;
    color: #102A71;
    margin-bottom: 16px;
  }

  .price-range-track {
    position: relative;
    height: 4px;
    background: #F2F1EC;
    border-radius: 2px;
    margin: 0 9px;
  }

  .price-range-fill {
    position: absolute;
    top: 0;
    bottom: 0;
    background: #102A71;
    border-radius: 2px;
  }

  .price-range-handle {
    position: absolute;
    top: 50%;
    width: 18px;
    height: 18px;
    background: #102A71;
    border: 2px solid #FFFFFF;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    cursor: grab;
    touch-action: none;
  }

  .price-range-handle:active {
    cursor: grabbing;
  }

  .gender-btn {
    height: 21px;
    font-size: 12px;
    padding: 0 5px;
  }

  .attr-lock-pill {
    height: 21px;
    padding: 0 5px;
  }

  /* Inside the filters drawer: bigger buttons, 11px text. Gender copies
     the home page's .category-chip sizing. */
  .price-gender-group .gender-btn {
    height: 30px;
    box-sizing: border-box;
    font-size: 13px;
    padding: 0 14px;
    border-radius: 40px;
  }

  .price-gender-group .attr-lock-pill {
    height: 30px;
    font-size: 11px;
    padding: 0 14px;
    border-radius: 15px;
  }

  .price-gender-group .filter-col-fields {
    gap: 8px;
  }

  .price-gender-group .filter-col-fields:has(> .gender-btn) {
    gap: 5px;
  }

  /* BRAND: chips, a full-width field, and the full brand list inline
     below it (kept open by search.html's brand JS on mobile). */
  .price-gender-group .brand-filter-fields {
    flex-direction: column;
    align-items: stretch;
  }

  .price-gender-group .brand-chips {
    flex-wrap: wrap;
  }

  .price-gender-group .brand-search-input {
    width: 100%;
    height: 34px;
    font-size: 13px;
    border-radius: 17px;
    padding: 0 14px;
  }

  .price-gender-group .brand-filter-fields .prefs-suggestions {
    position: static;
    width: auto;
    box-shadow: none;
    background: #FCFDFF;
  }

  .price-gender-group .prefs-suggestions-list,
  #cgBrandSuggestions,
  #exploreBrandSuggestions {
    max-height: 280px;
    overflow-y: auto;
  }

  .style-search-input {
    width: 100%;
    height: 26px;
    font-size: 13px;
  }

  /* Price under each product slot — slightly smaller than the product
     name (14px). .meta-pr has its own unscoped 14px rule elsewhere in
     this file that would otherwise win over the parent's mobile size. */
  .meta-pr {
    font-size: 11px;
  }

  /* Hamburger — smaller, pulled toward the screen edge (away from the
     centered "The Choice Architect" title). */
  .header-nav-row {
    height: 40px;
    padding: 0 16px;
  }

  .mobile-menu-btn {
    padding: 4px;
    margin-right: 0;
  }

  .mobile-menu-btn svg {
    width: 13px;
    height: 11px;
  }

  /* Close X at the menu button's spot, its cross 18px from both the top
     and left edges (button padding 6px + the icon's 2px inset). */
  .mobile-nav-close {
    position: absolute;
    top: 10px;
    left: 10px;
    background: none;
    border: none;
    cursor: pointer;
    padding: 6px;
    display: flex;
  }

  /* Standardized empty-state text ("No products found", "No products
     saved", "Could not reach the search server…", explore's "Loading…")
     — one look everywhere, centered in the space between the filters
     row and the roll button. */
  .no-results {
    color: #6F747D;
    font-family: 'SpaceGrotesk', sans-serif;
    font-weight: 400;
    font-size: 14px;
    white-space: normal;
    padding: 0 24px;
    min-height: 420px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
  }

  /* The hamburger drawer replaces the nav-left/nav-right link rows. */
  .nav-left,
  .nav-right {
    display: none;
  }

  /* The account entry lives only in the hamburger drawer on mobile. */
  .account-nav-wrap {
    display: none;
  }

  .bag-nav-link {
    margin-left: auto;
  }

  /* ROLL button + undo/redo arrows — scaled down to match the
     shorter 40px mobile top bar and tighter vertical rhythm. */
  .roll-btn {
    width: 116px;
    height: 42px;
    font-size: 18px;
    box-shadow: 0 2px 0 0 #2C56A0;
  }

  .roll-btn:active {
    transform: translateY(2px);
  }

  .nav-arrow-btn {
    width: 36px;
    height: 36px;
  }

  .nav-arrow-btn svg {
    width: 18px;
    height: 18px;
  }

  /* Home — headline centered in the space above the controls (toggle,
     search bar, chips), which sit at the bottom. 59px = the header's 41px
     height + its 18px bottom margin, so the page fills the screen without
     scrolling. */
  .home-main {
    min-height: calc(100dvh - 59px);
    align-items: stretch;
    padding: 24px 24px calc(28px + env(safe-area-inset-bottom));
  }

  /* With the legal links below it (signed-out visitors), the page and
     the links together fill the screen. */
  body.home:has(#siteLegalFooter) {
    display: flex;
    flex-direction: column;
    min-height: 100dvh;
  }

  body.home:has(#siteLegalFooter) .home-main {
    flex: 1 0 auto;
    min-height: 0;
    padding-bottom: 8px;
  }

  body.home #siteLegalFooter {
    padding-bottom: calc(12px + env(safe-area-inset-bottom));
  }

  .home-headline {
    margin: auto 0;
    font-size: 50px;
    line-height: 1.15;
  }

  .home-controls {
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .home-main .gender-toggle {
    margin-bottom: 16px;
  }

  .gender-toggle-seg {
    font-size: 11px;
    padding: 6px 14px;
  }

  .home-main .category-chip-row {
    margin-top: 16px;
  }

  /* Shared by the home page and the Browse page, so both match. */
  .category-chip-row {
    gap: 5px;
  }

  .category-chip {
    font-size: 11px;
    height: 30px;
    box-sizing: border-box;
    padding: 0 8px;
  }

  .home-search-bar {
    padding: 12px 13px 12px 13px;
    gap: 8px;
    border-radius: 14px;
    border-width: 1px;
    box-shadow: 0 1px 0 #2C56A0;
  }

  .home-search-input {
    font-size: 14px;
  }

  .upload-popup {
    width: auto;
  }

  .browse-main {
    padding: 32px 16px 60px;
  }

  .browse-headline {
    font-size: 40px;
    margin-bottom: 18px;
  }

  .browse-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
    margin-top: 24px;
  }

  .browse-grid--all {
    margin-top: 32px;
  }

  .browse-card-label {
    left: 10px;
    bottom: 9px;
    font-size: 20px;
  }

  .outfits-grid {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  }

  /* Comes after the max-height:700px tier, so this cap wins on short
     phones too (that tier's 1.35 ratio still applies). Levi's cards stay
     exempt via .product-card.is-levis. */
  .product-card {
    max-height: 210px;
  }

  /* Corner rounding, 2px less than desktop's --card-radius: 10px. */
  :root {
    --card-radius: 8px;
  }

  /* Reclaim the unused buffer between .card-info's insets and where the
     top controls / footer actually end (top controls end at 30px, footer
     buttons end at 38px from the bottom) — desktop's 45px/60px insets
     leave far more room than a 175px-tall mobile card can spare, which
     starved the attr-row scroll area down to ~1 row before it had to
     scroll. Cuts the buffer to a few px instead of 15-22px. */
  .card-info {
    top: 34px;
    bottom: 42px;
  }

  /* #cgProductsGrid/#exploreGrid .card-info's bottom:54px (desktop, single-row
     footer) outranks the rule above by ID specificity — needs its own
     mobile override for the same reason. */
  #cgProductsGrid .card-info,
  #exploreGrid .card-info {
    bottom: 42px;
  }

  /* Tighter line-spacing in the text block under each product photo. */
  .product-meta-below {
    gap: 1px;
  }

  /* Thinner drop-shadow outline around each product photo. */
  .product-card::after {
    border-width: 0.5px 0.5px 1.5px 0.5px;
  }

  /* Pull the ROLL button closer to the bottom edge, lengthening the
     scrollable product area above it. */
  .roll-section {
    bottom: 14px;
    gap: 9px;
  }

  /* Background strip behind the ROLL controls reaches only 6px above
     them, leaving more room for the product grid. */
  .roll-section::before {
    top: -6px;
  }

  /* In line after the redo arrow, so the centered row shifts ROLL a
     little left to make room. */
  .save-changes-btn {
    position: static;
    transform: none;
    height: 36px;
    padding: 0 16px;
    font-size: 13px;
  }

  /* Desktop's .products-section padding-bottom formula is tuned against
     the desktop roll button's size/position -- mobile's own
     .products-scroll-wrapper.is-results-grid padding-bottom (below)
     already reserves its own, differently-sized button's footprint. */
  body.search-page .products-section {
    padding-bottom: 0;
  }

  /* ---- Profile page ---- */
  .profile-section {
    padding: 32px 24px 60px;
  }

  .profile-top-row {
    gap: 16px;
  }

  .profile-avatar-circle {
    width: 100px;
    height: 100px;
  }

  .profile-avatar-circle svg {
    width: 44px;
    height: 44px;
  }

  .profile-name {
    font-size: 26px;
  }

  .profile-actions-row {
    margin-top: 20px;
  }

  /* Same size as the attributes panel's footer buttons. */
  .profile-action-btn {
    height: 36px;
    padding: 0 28px;
    font-size: 13px;
  }

  .profile-action-btn-square {
    width: 36px;
    height: 36px;
  }

  .profile-action-btn-square svg {
    width: 16px;
    height: 16px;
  }

  .profile-section-title {
    font-size: 30px;
  }
}
/* ══════════════════════════════════════════════════════
   BAG PAGE (bag.html)
══════════════════════════════════════════════════════ */
.bag-main {
  padding: 24px clamp(40px, 2.08vw, 60px) 80px;
  max-width: 1200px;
  margin: 0 auto;
  font-family: 'SpaceGrotesk', sans-serif;
  color: #102A71;
}
.bag-title {
  font-family: 'LibreCaslonCondensed-BoldItalic', serif;
  font-weight: normal;
  font-size: 40px;
  margin: 0 0 24px;
}
.bag-empty { color: var(--muted); font-size: 16px; }
.bag-empty a { color: #102A71; }
.bag-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 40px;
  align-items: start;
}
.bag-layout[hidden] { display: none; }
.bag-brand { border-top: 1px solid #F2F1EC; padding: 16px 0 8px; }
.bag-brand-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 12px;
}
.bag-brand-head h2 { font-size: 13px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; margin: 0; }
.bag-brand-head span { font-size: 14px; }
.bag-item { display: flex; gap: 16px; padding: 8px 0 16px; }
.bag-item.is-unavailable { opacity: 0.55; }
.bag-item-photo img {
  width: 96px;
  height: 128px;
  object-fit: cover;
  border-radius: var(--card-radius);
  background: #F2F1EC;
  display: block;
}
.bag-item-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.bag-item-name { font-size: 15px; color: #102A71; text-decoration: none; }
.bag-item-name:hover { text-decoration: underline; }
.bag-item-price { font-size: 14px; color: var(--muted); }
.bag-item-warning { font-size: 13px; color: #B3261E; }
.bag-item-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: auto; }
.bag-size {
  height: 32px;
  padding: 0 24px 0 11px;
  border: 1px solid #F2F1EC;
  border-radius: 16px;
  background: #FCFDFF url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none' stroke='%23102A71' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='1,1 5,5 9,1'/%3E%3C/svg%3E") no-repeat right 10px center;
  -webkit-appearance: none;
  appearance: none;
  font: inherit;
  font-size: 14px;
  color: #102A71;
  cursor: pointer;
}
.bag-qty { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.bag-qty-btn,
.bag-remove {
  background: none;
  border: 1px solid #F2F1EC;
  border-radius: 16px;
  height: 32px;
  min-width: 32px;
  padding: 0 10px;
  font: inherit;
  font-size: 14px;
  color: #102A71;
  cursor: pointer;
}
.bag-qty-btn { background: #FCFDFF; }
.bag-remove { border: none; text-decoration: underline; padding: 0; }
.bag-summary {
  position: sticky;
  top: 24px;
  border: 1px solid #F2F1EC;
  border-radius: var(--card-radius);
  padding: 20px;
  background: #FCFDFF;
}
.bag-summary-row { display: flex; justify-content: space-between; font-size: 15px; padding: 4px 0; }
.bag-summary-total { font-weight: 600; border-top: 1px solid #F2F1EC; margin-top: 8px; padding-top: 12px; }
.bag-msg { font-size: 13px; color: var(--muted); min-height: 1em; margin: 12px 0; }
.bag-note { font-size: 12px; line-height: 1.5; color: var(--muted); margin: 14px 0 0; }
.bag-note a { color: #2C56A0; }

/* End of every product popup's attribute list (ProductTags.DISCLOSURE_HTML). */
.product-disclosure {
  margin: 6px 0 2px;
  padding: 0 4px 0 10px;
  font-family: 'SpaceGrotesk', sans-serif;
  font-size: 9px;
  line-height: 1.45;
  color: inherit;
  opacity: 0.7;
}
.bag-login-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  border-radius: 22px;
  background: #2C56A0;
  color: #FFFFFF;
  font-size: 15px;
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
}
.bag-login-btn:hover { background: #102A71; color: #FFFFFF; }
.bag-confirm { max-width: 640px; font-size: 15px; }
.bag-confirm .bag-login-btn { margin-top: 24px; max-width: 240px; }
.bag-confirm-line { font-size: 14px; padding: 2px 0 6px; }

@media (max-width: 640px) {
  .bag-main { padding: 16px 16px 60px; }
  .bag-title { font-size: 30px; margin-bottom: 16px; }
  .bag-layout { grid-template-columns: 1fr; gap: 24px; }
  .bag-summary { position: static; }
  .bag-item-photo img { width: 80px; height: 106px; }
}

/* Settings pages: body text 15px; Preferences chips, fields, refine text
   and product names 13px; field headings 17px. Last in the file so it wins
   over the shared components' own sizes. */
.settings-main .settings-nav-link,
.settings-main .refine-empty,
.settings-main .privacy-intro,
.settings-main .privacy-note,
.settings-main .privacy-choice-text,
.settings-main a {
  font-size: 15px;
}

.settings-main .gender-btn,
.settings-main .prefs-chip,
.settings-main .prefs-chip .prefs-chip-x,
.settings-main .prefs-suggestion,
#prefsForm .save-modal-input,
.settings-main .refine-subtitle,
.settings-main .refine-item-name {
  font-size: 13px;
}

#prefsForm .save-modal-input::placeholder {
  font-size: 13px;
}

.settings-main label,
.settings-main .prefs-label,
.settings-main .refine-title,
.settings-main .privacy-section-title {
  font-size: 17px;
}

/* Settings: favourite colours read as names; the account page's small link. */
#prefColorChips .prefs-chip {
  text-transform: capitalize;
}

.settings-main .auth-link-row {
  font-size: 13px;
}

/* ══════════════════════════════════════════════════════
   PHONES HELD SIDEWAYS — and other short, wide windows.
   Tapping or clicking a card opens the attributes panel as a
   drawer over the right third of the screen (header.js,
   panel.js). On search, five cards fit between the filter row
   and a compact ROLL bar without scrolling.
══════════════════════════════════════════════════════ */
@media (orientation: landscape) and (max-height: 500px) {
  :root {
    --drawer-w: max(50vw, 300px);
  }

  .product-card.is-overlay-active .card-overlay {
    left: auto;
    width: var(--drawer-w);
    padding: 52px 16px 0;
    box-shadow: -6px 0 24px rgba(16, 42, 113, 0.12);
  }

  .product-card.is-overlay-active .card-overlay::before {
    left: auto;
    width: var(--drawer-w);
  }

  .product-card.is-overlay-active .panel-info .slot-lock-text-btn {
    left: calc(100vw - var(--drawer-w) + 16px);
  }

  /* The photo on the left half; name, brand and price beside it. */
  .product-card.is-overlay-active .panel-header-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto auto 1fr;
    column-gap: 14px;
    align-items: start;
    min-height: 0;
  }

  .product-card.is-overlay-active .panel-photo-col {
    grid-column: 1;
    grid-row: 1 / 4;
  }

  .product-card.is-overlay-active .panel-photo-frame {
    flex: none;
    width: 100%;
    height: auto;
    min-height: 0;
    max-height: calc(100dvh - 124px);
    aspect-ratio: 2 / 3;
  }

  .product-card.is-overlay-active .panel-info .product-name-below {
    grid-column: 2;
    grid-row: 1;
    margin-top: 0;
    font-size: 20px;
  }

  .product-card.is-overlay-active .panel-info .product-meta-below {
    grid-column: 2;
    grid-row: 2;
  }

  .product-card.is-overlay-active .card-footer {
    padding: 8px 0 calc(6px + env(safe-area-inset-bottom));
  }

  .product-card.is-overlay-active .coll-panel-icons {
    display: flex;
  }

  .product-card.is-overlay-active .save-btn-corner {
    display: none;
  }

  /* The desktop overlay's even row spacing sizes the list to its box;
     in the drawer the list sets its own height. */
  .product-card.is-overlay-active .card-attrs-scroll {
    container-type: normal;
  }

  .product-card:not(.is-overlay-active):hover .product-img {
    filter: none;
  }

  /* No room beside the ROLL bar; phones held upright don't show them
     either. */
  .site-legal-footer.is-floating {
    display: none;
  }

  .header-nav-row {
    height: 44px;
    padding: 0 16px;
  }

  .results-header {
    margin-bottom: 16px;
  }

  body.search-page .filters-row {
    min-height: 0;
    padding: 0 16px 14px;
  }

  body.search-page .filters-row .page-title {
    font-size: 28px;
  }

  body.search-page .products-track {
    padding: 0 16px;
  }

  /* As tall as the space between the filter row and the ROLL bar allows
     (photo dots included), at the card's 1:1.35 shape. */
  body.search-page .products-track .product-slot-wrapper {
    flex: 0 1 min(260px, calc((100dvh - 208px) / 1.35));
  }

  /* Same dots as phones held upright. */
  .products-scroll-wrapper.is-results-grid .carousel-nav-below {
    margin-top: 6px;
    min-height: 6px;
  }

  .products-scroll-wrapper.is-results-grid .carousel-dots {
    gap: 4px;
  }

  .products-scroll-wrapper.is-results-grid .carousel-dot {
    width: 4px;
    height: 4px;
  }

  .products-scroll-wrapper.is-results-grid .carousel-dot.is-current {
    width: 5px;
    height: 5px;
  }

  body.search-page .roll-section::before {
    top: -6px;
  }

  body.home .home-main {
    padding: 14px 24px 8px;
  }

  body.home .home-headline {
    font-size: 34px;
    margin-bottom: 12px;
  }

  body.home .gender-toggle {
    margin-bottom: 10px;
  }

  body.home .gender-toggle-seg {
    padding: 5px 14px;
    font-size: 10px;
  }

  body.home .home-search-form {
    max-width: 520px;
  }

  body.home .home-search-bar {
    padding: 9px 16px;
  }

  body.home .home-search-input {
    font-size: 14px;
  }

  body.home .category-chip-row {
    margin-top: 10px;
    gap: 6px;
  }

  body.home .category-chip {
    padding: 6px 10px;
    font-size: 10px;
  }

  body.home #siteLegalFooter {
    padding: 8px 16px;
  }

  body.search-page .product-name-below,
  body.search-page .product-meta-below {
    display: none;
  }

  body.search-page .products-section {
    padding-bottom: 56px;
  }

  body.search-page .products-scroll-wrapper {
    padding-bottom: 0;
  }

  body.search-page .roll-section {
    bottom: 8px;
    gap: 10px;
  }

  body.search-page .roll-btn {
    width: 104px;
    height: 40px;
    border-radius: 20px;
    font-size: 16px;
  }

  body.search-page .nav-arrow-btn {
    width: 36px;
    height: 36px;
  }
}

/* Swiping replaces the photo arrows on touch screens; with a mouse they
   stay, so the cards leave room for them. */
@media (orientation: landscape) and (max-height: 500px) and (hover: none) {
  .products-scroll-wrapper.is-results-grid .carousel-arrow-left,
  .products-scroll-wrapper.is-results-grid .carousel-arrow-right {
    display: none;
  }

  body.search-page .products-track .product-slot-wrapper {
    flex-basis: min(260px, calc((100dvh - 192px) / 1.35));
  }
}
