/* _content/Odel.Web/Components/Account/AccountTabs.razor.rz.scp.css */
/* The account's own navigation (2026-09-27).

   A scrolling row rather than a wrapping one: five tabs do not fit across a
   390px phone, and a set that wraps to two lines stops reading as one control.
   Scrolling keeps it a single band at every width, and the scrollbar is hidden
   the same way the carousel tracks hide theirs. */
.account-tabs[b-dwwwh0bpr0] {
  margin-bottom: var(--space-24);
  border-bottom: 1px solid var(--gray-200);
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.account-tabs[b-dwwwh0bpr0]::-webkit-scrollbar {
  display: none;
}

.account-tabs ul[b-dwwwh0bpr0] {
  display: flex;
  gap: var(--space-4);
  list-style: none;
  margin: 0;
  padding: 0;
  /* The row is as wide as its contents, so the flex items keep their size
     instead of being squeezed to fit a phone. */
  width: max-content;
  min-width: 100%;
}

.account-tab[b-dwwwh0bpr0] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  padding: var(--space-12) var(--space-16);
  text-decoration: none;
  white-space: nowrap;
  color: var(--text-muted);
  font-weight: 500;
  font-size: var(--text-0-88);
  /* A transparent rule the same weight as the active one, so a tab does not
     move by two pixels when it becomes current. */
  border-bottom: 2px solid transparent;
  transition: var(--transition);
}
.account-tab:hover[b-dwwwh0bpr0] {
  color: var(--text-dark);
}

/* The page being read. Weight and an underline carry it, not colour alone —
   a tab told apart only by a hue is a tab some Shoppers cannot tell apart at
   all (WCAG 1.4.1), and aria-current says it in words besides. */
.account-tab.is-current[b-dwwwh0bpr0] {
  color: var(--primary-black);
  font-weight: 700;
  border-bottom-color: var(--primary-black);
  cursor: default;
}

/* The glyph is decoration beside a word that already names the tab, so it is
   held back and never the thing carrying the meaning. */
.account-tab i[b-dwwwh0bpr0] {
  font-size: var(--text-0-8);
  opacity: 0.75;
}
.account-tab.is-current i[b-dwwwh0bpr0] {
  opacity: 1;
}

/* On a phone the labels are what matter and the icons earn less of the room. */
@media (max-width: 575px) {
  .account-tab[b-dwwwh0bpr0] {
    padding: var(--space-10) var(--space-12);
    font-size: var(--text-0-8);
  }
}
/* _content/Odel.Web/Components/Account/AddressBookPanel.razor.rz.scp.css */
/* The book itself (Story 5.8), extracted into this component's own scoped
   stylesheet alongside AddressBookPanel.razor (Story 17.3). The card, the
   form groups, the labels, the inputs, the hint and the submit button stay
   the shared account ones (`account-card` in wwwroot/css/app.css); the
   `.account-card .card` width belongs to whichever page renders the card
   element itself (Addresses.razor.css, Account.razor.css) and is not
   repeated here.

   Blazor's CSS isolation rewrites a selector by requiring its SCOPE
   ATTRIBUTE only on the rightmost element, not on every ancestor named in
   it — so `.account-card .card h2` below still matches a heading this
   component renders, wherever the page that hosts it put the `.card` div.

   BOTH h2 AND h3 (Story 18.3 review, P5): the panel's own sub-headings
   ("Change this address" / "Add an address" / "Remove this address?") now
   render at a level the host picks via HeadingLevel — h2 by default
   (/addresses, where this panel's own h1 is the page's only other heading),
   h3 on /account (where the column already has its own h2, "My addresses"),
   so heading levels never skip downward on either host. Both selectors
   carry the same rule so the sizing does not depend on which host is
   rendering it. */

.account-card .card h2[b-qppy7iu4mj],
.account-card .card h3[b-qppy7iu4mj] {
  font-size: var(--text-1);
  margin: var(--space-24) 0 var(--space-16);
}

/* The "Add an address" accordion (Story 19.5, Product Owner: "get the add
   new address inside a accordion and put it above the list"). A native
   <details>/<summary> pair: no script, and the SSR form inside is untouched —
   only its container's open/closed state is new (see AddFormIsOpen in the
   component's own code). */
.address-add[b-qppy7iu4mj] {
  margin: 0 0 var(--space-16);
  border: 1px solid var(--neutral-200);
  border-radius: 8px;
  background: var(--off-white);
}

.address-add summary[b-qppy7iu4mj] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8);
  padding: var(--space-14);
  cursor: pointer;
  list-style: none;
  font-weight: 700;
  color: var(--text-dark);
  font-size: var(--text-0-9);
}

/* Chrome and Safari both still draw their own disclosure triangle beside the
   list-style:none marker unless this is removed explicitly. */
.address-add summary[b-qppy7iu4mj]::-webkit-details-marker {
  display: none;
}

.address-add summary:focus-visible[b-qppy7iu4mj] {
  outline: 2px solid var(--yellow-accent);
  outline-offset: 2px;
}

/* The one state this accordion signals beyond open/closed itself: which way
   the chevron is currently pointing. Driven by the browser's own [open]
   attribute, not by anything this component tracks in C#. */
.address-add summary .chevron[b-qppy7iu4mj] {
  transition: var(--transition);
}

.address-add[open] summary .chevron[b-qppy7iu4mj] {
  transform: rotate(180deg);
}

.address-add-body[b-qppy7iu4mj] {
  padding: 0 var(--space-14) var(--space-14);
}

/* The scroll region around the list (Story 19.5): a fixed height so the book
   stops growing the page once it is long enough to need one, on both hosts
   this component renders on — see the component's own remarks on why it is
   not host-specific. tabindex + role/aria-label (set in the markup) keep it
   reachable without a mouse wheel.

   No `overscroll-behavior: contain` (review, dropped): on a touch device at
   390px this box is usually the page's own only scrollable region worth the
   name, and `contain` stops a finger's scroll gesture dead at either end of
   it rather than letting it chain on to scroll the page — the opposite of
   what a shopper flicking down expects. The margin below gives the hint text
   underneath it room so the box's own bottom edge does not read as a clipped
   sentence. */
.book-scroll[b-qppy7iu4mj] {
  max-height: calc(var(--space-90) * 5);
  margin-bottom: var(--space-11);
  overflow-y: auto;
}

.book-scroll:focus-visible[b-qppy7iu4mj] {
  outline: 2px solid var(--yellow-accent);
  outline-offset: 2px;
}

/* The book. A list, because it is one — a shopper navigating by structure is
   told how many addresses they have before reading any of them. */
.book[b-qppy7iu4mj] {
  list-style: none;
  margin: 0 0 var(--space-8);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-11);
}

.entry[b-qppy7iu4mj] {
  padding: var(--space-14);
  border: 1px solid var(--neutral-200);
  border-radius: 8px;
  background: var(--off-white);
}

.entry-head[b-qppy7iu4mj] {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-8) var(--space-11);
  margin-bottom: var(--space-8);
}

.entry-label[b-qppy7iu4mj] {
  font-weight: 700;
  color: var(--text-dark);
  font-size: var(--text-0-9);
}

/* The consequence of the rule stated under the list, marked on the entry it
   applies to. Wording rather than a bare star: a symbol needs a key, and the key
   would be the sentence this replaces. */
.entry-default[b-qppy7iu4mj] {
  font-size: var(--text-0-75);
  font-weight: 600;
  color: var(--text-muted);
  border: 1px solid var(--neutral-400);
  border-radius: 999px;
  padding: var(--space-4) var(--space-8);
}

/* The entry a half-completed edit could not remove. Marked rather than merely
   labelled, because the row beside it carries the same label and the shopper is
   being asked to tell them apart. */
.entry.stale[b-qppy7iu4mj] {
  border-color: var(--yellow-accent);
}

.entry-stale[b-qppy7iu4mj] {
  font-size: var(--text-0-75);
  font-weight: 600;
  color: var(--text-dark);
  border: 1px solid var(--yellow-accent);
  border-radius: 999px;
  padding: var(--space-4) var(--space-8);
}

/* The gate before the only destructive action this panel offers. It replaces
   the add form rather than sitting beside it, so the question is the thing on
   screen. */
.confirm[b-qppy7iu4mj] {
  margin: var(--space-24) 0 var(--space-16);
  padding: var(--space-16);
  border: 1px solid var(--yellow-accent);
  border-radius: 8px;
  background: var(--yellow-light, var(--off-white));
}

.confirm h2[b-qppy7iu4mj],
.confirm h3[b-qppy7iu4mj] {
  margin-top: 0;
}

.confirm p[b-qppy7iu4mj] {
  margin-bottom: var(--space-11);
  font-size: var(--text-0-85);
}

/* Which address is about to go, spelled out inside the question. */
.confirm-what span[b-qppy7iu4mj] {
  display: block;
}

/* One address, one line each, so the three parts stack on a phone without the
   name and the street running together. */
.entry-lines[b-qppy7iu4mj] {
  margin: 0 0 var(--space-11);
  font-size: var(--text-0-85);
  color: var(--text-dark);
}

.entry-lines span[b-qppy7iu4mj] {
  display: block;
}

.entry-lines .entry-name[b-qppy7iu4mj] {
  font-weight: 600;
}

.entry-lines .entry-mobile[b-qppy7iu4mj],
.entry-lines .entry-address[b-qppy7iu4mj] {
  color: var(--text-muted);
}

.entry-actions[b-qppy7iu4mj] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-16);
  font-size: var(--text-0-85);
}

/* The remove control is a button because removing is not something a crawler, a
   prefetch or a pasted URL may do — but it reads as the link beside it, because
   to the shopper the two are the same kind of thing. */
.entry-actions .link-btn[b-qppy7iu4mj] {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
  font-weight: 600;
  color: var(--primary-black);
  text-decoration: underline;
}

.entry-actions a[b-qppy7iu4mj] {
  font-weight: 600;
  color: var(--primary-black);
  text-decoration: underline;
}

/* The empty book: an invitation, not a blank panel. */
.empty[b-qppy7iu4mj] {
  margin: 0 0 var(--space-8);
  padding: var(--space-16);
  border: 1px dashed var(--neutral-400);
  border-radius: 8px;
  font-size: var(--text-0-85);
  color: var(--text-muted);
}

/* The sentence that says a write worked, reached by the redirect that follows
   it. */
.notice[b-qppy7iu4mj] {
  margin-bottom: var(--space-16);
  padding: var(--space-11) var(--space-14);
  border-radius: 8px;
  border: 1px solid var(--neutral-200);
  background: var(--off-white);
  font-size: var(--text-0-85);
  color: var(--text-dark);
}

/* Why the default is what it is, and why the city list may be missing. Both are
   statements the shopper needs before they act, not after. */
.hint[b-qppy7iu4mj] {
  display: block;
  margin: 0 0 var(--space-16);
  font-size: var(--text-0-8);
  color: var(--text-muted);
}

/* The country: a value the shopper reads and cannot change, styled unlike the
   inputs beside it because an input-shaped thing that does not take input is a
   defect as far as anyone can tell. */
.readonly-value[b-qppy7iu4mj] {
  margin: 0;
  padding: var(--space-11) 0;
  font-size: var(--text-0-85);
  font-weight: 600;
  color: var(--text-dark);
}

.routes[b-qppy7iu4mj] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-16);
  margin-top: var(--space-16);
}
/* _content/Odel.Web/Components/Assurances/PurchaseAssurances.razor.rz.scp.css */
/* The assurance list, styled beside the markup it belongs to (Story 3.6).
   These rules lived in ItemDetails.razor.css while the list was authored on the
   page; scoped CSS is scoped to the component that renders the element, so they
   travelled with it. The design system's values are unchanged — this is the
   same list the page has always shown, with per-product rows in it.

   The region's rule-off and vertical padding are not here: they belong to the
   `.block` the page wraps this list and the returns note in, which is where
   they have always been and where they still separate the region when this
   component renders nothing at all. */
.assurance-list[b-ywtembpbi3] {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--space-9);
  font-size: var(--text-0-84);
  color: var(--text-dark);
}
.assurance-list i[b-ywtembpbi3] {
  color: var(--yellow-accent);
  margin-right: var(--space-8);
}
/* _content/Odel.Web/Components/Async/BlockerNotice.razor.rz.scp.css */
/* Louder than a degradation and quieter than a failed page. The bag rendered
   and the figures are real; what is wrong is the bag itself. So it takes the
   degradation notice's ground and adds two things it does not have: a solid
   left edge in the primary text colour, and body text at full contrast rather
   than muted. No danger colour — this is not a failed purchase path. */
.notice[b-wjv05p1mbn] {
  display: flex;
  align-items: flex-start;
  gap: var(--space-10);
  margin: var(--space-16) 0;
  padding: var(--space-12) var(--space-16);
  background: var(--neutral-75);
  border: 1px solid var(--neutral-300);
  border-left: 3px solid var(--text-primary);
  border-radius: var(--radius-sm);
}

.icon[b-wjv05p1mbn] {
  font-size: var(--text-0-9);
  line-height: var(--leading-1-4);
  color: var(--text-primary);
}

.lines[b-wjv05p1mbn] {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.line[b-wjv05p1mbn] {
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-82);
  line-height: var(--leading-1-4);
  color: var(--text-primary);
}
/* _content/Odel.Web/Components/Async/DegradationNotice.razor.rz.scp.css */
/* Quieter than an error state on purpose: the page worked, one region of it
   did not. Warm neutral rather than danger colour, so it never reads as a
   failed purchase path. */
.notice[b-lcpaph5yft] {
  display: flex;
  align-items: flex-start;
  gap: var(--space-10);
  margin: var(--space-16) 0;
  padding: var(--space-12) var(--space-16);
  background: var(--neutral-75);
  border: 1px solid var(--neutral-300);
  border-radius: var(--radius-sm);
}

.icon[b-lcpaph5yft] {
  font-size: var(--text-0-9);
  line-height: var(--leading-1-4);
  color: var(--text-muted);
}

.lines[b-lcpaph5yft] {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.line[b-lcpaph5yft] {
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-82);
  line-height: var(--leading-1-4);
  color: var(--text-muted);
}
/* _content/Odel.Web/Components/Async/ErrorState.razor.rz.scp.css */
.state[b-a1wzr582sl] {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-14);
  padding: var(--space-40) var(--space-20);
  text-align: center;
}

.state-text[b-a1wzr582sl] {
  font-size: var(--text-0-85);
  color: var(--text-muted);
  font-family: 'Poppins', sans-serif;
}

.error-icon[b-a1wzr582sl] {
  font-size: var(--text-2);
  color: var(--yellow-accent);
}

.retry-btn[b-a1wzr582sl] {
  background: var(--primary-black);
  color: var(--white);
  border: none;
  border-radius: 25px;
  padding: var(--space-9) var(--space-24);
  font-size: var(--text-0-82);
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition);
}
.retry-btn:hover[b-a1wzr582sl] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}
/* _content/Odel.Web/Components/Async/FailureSummary.razor.rz.scp.css */
/* A page-level failure: louder than a degradation, because the page did not
   do what it was asked. Danger colour, and it sits above the content it is
   about rather than replacing it. */
.summary[b-nzraml2lml] {
  display: flex;
  align-items: flex-start;
  gap: var(--space-10);
  margin: var(--space-16) 0;
  padding: var(--space-12) var(--space-16);
  background: var(--danger-bg);
  border: 1px solid var(--danger-a12);
  border-left: var(--space-3) solid var(--danger);
  border-radius: var(--radius-sm);
}

.icon[b-nzraml2lml] {
  font-size: var(--text-0-9);
  line-height: var(--leading-1-4);
  color: var(--danger-dark);
}

.lines[b-nzraml2lml] {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.line[b-nzraml2lml] {
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  line-height: var(--leading-1-4);
  color: var(--danger-dark);
}
/* _content/Odel.Web/Components/Async/FieldError.razor.rz.scp.css */
/* Sits directly under its input, close enough that the association is visual
   as well as programmatic. */
.field-error[b-20nsd4gg79] {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-6);
}

.line[b-20nsd4gg79] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-75);
  line-height: var(--leading-1-4);
  color: var(--danger-dark);
}
/* _content/Odel.Web/Components/Async/LineError.razor.rz.scp.css */
/* Inside the line it concerns, so which item is in trouble is never in
   question. */
.line-error[b-5jot03mr7n] {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-6);
  padding: var(--space-6) var(--space-8);
  background: var(--danger-bg);
  border-left: var(--space-3) solid var(--danger);
  border-radius: var(--radius-xs);
}

.line[b-5jot03mr7n] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-75);
  line-height: var(--leading-1-4);
  color: var(--danger-dark);
}
/* _content/Odel.Web/Components/Async/Loader.razor.rz.scp.css */
.state[b-tmm1v1k7wg] {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-14);
  padding: var(--space-40) var(--space-20);
  text-align: center;
}

/* Full-viewport splash: covers header, footer and bottom nav while the
   first data load is in flight so nothing peeks through underneath. */
.state.fullscreen[b-tmm1v1k7wg] {
  position: fixed;
  inset: 0;
  z-index: 3000;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--white);
}

.spinner[b-tmm1v1k7wg] {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 3px solid var(--ink-a12);
  border-top-color: var(--yellow-accent);
  animation: asyncSpin-b-tmm1v1k7wg var(--duration-0-8) linear infinite;
}

@keyframes asyncSpin-b-tmm1v1k7wg {
  to {
    transform: rotate(360deg);
  }
}

.state-text[b-tmm1v1k7wg] {
  font-size: var(--text-0-85);
  color: var(--text-muted);
  font-family: 'Poppins', sans-serif;
}
/* _content/Odel.Web/Components/Cards/CategoryCard.razor.rz.scp.css */
.card[b-hw8wtrby8b] {
  display: block;
  position: relative;
  overflow: hidden;
  /* The card, not the image, owns the tile's box: the slot's declared 1:1
     supply ratio is deliberately cropped to a landscape band here, and to a
     shorter one on small screens. The image fills whatever box it is given. */
  min-height: 150px;
  height: 150px;
  border-radius: var(--radius-minimal);
  text-decoration: none;
  color: var(--white);
  background: var(--off-white);
  transition: var(--transition);
}
.card[b-hw8wtrby8b]  img {
  transition: var(--transition);
  filter: saturate(0.92);
}
.card[b-hw8wtrby8b]::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--black-a05), var(--black-a58));
}
.card span[b-hw8wtrby8b] {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 12px;
  z-index: 1;
  font-size: var(--text-0-9);
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.card:hover[b-hw8wtrby8b] {
  transform: translateY(-3px);
  box-shadow: var(--shadow-md);
  color: var(--white);
}
.card:hover[b-hw8wtrby8b]  img {
  transform: scale(1.06);
}
/* A slot the live hierarchy does not carry: still a tile, never a link to a
   category that does not exist. It does not lift on hover, so it does not
   promise a destination it has not got. */
.card.is-unmatched[b-hw8wtrby8b] {
  cursor: default;
}
.card.is-unmatched:hover[b-hw8wtrby8b] {
  transform: none;
  box-shadow: none;
}
.card.is-unmatched:hover[b-hw8wtrby8b]  img {
  transform: none;
}
@media (max-width: 575px) {
  .card[b-hw8wtrby8b] {
    min-height: 118px;
    height: 118px;
  }
  .card span[b-hw8wtrby8b] {
    left: 7px;
    right: 7px;
    bottom: 7px;
    font-size: var(--text-0-6);
    letter-spacing: 0.2px;
    line-height: var(--leading-1-15);
  }
}

/* Icon shown only on mobile (stacked above the label). */
.icon[b-hw8wtrby8b] {
  display: none;
  font-size: var(--text-1-1);
  margin-bottom: var(--space-4);
  opacity: 0.9;
}
@media (max-width: 991px) {
  .icon[b-hw8wtrby8b] {
    display: block;
  }
}
@media (max-width: 575px) {
  .icon[b-hw8wtrby8b] {
    font-size: var(--text-0-95);
    margin-bottom: var(--space-2);
  }
}

@media (max-width: 991px) {
  .card span[b-hw8wtrby8b] {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
  }
}
/* _content/Odel.Web/Components/Cards/ProductCard.razor.rz.scp.css */
/* The one product card's stylesheet, merged from GridCard.razor.css and
   PromoCard.razor.css (Story 3.5).

   The card's contents are the same on both surfaces and its frame is not: the
   grid cell is sized by the flex grid's breakpoints, the carousel cell fills
   its slide, and the two have always carried different metrics — padding, type
   scale, tag, swatch and button sizes. So everything the two surfaces share is
   written once, unscoped, and only the values that genuinely differ are written
   twice, under `.item` (grid) and `.card` (rail). Both surfaces look exactly as
   they did before the merge. */

/* ---- the wrapper ------------------------------------------------------- */

/* A COLUMN, so the Add control can be pinned to the bottom of every card
   (Product Owner, 2026-09-27). The image sits at its fixed ratio and the body
   takes whatever height is left, which is what gives the auto margin on the
   button something to push against. */
.item[b-v2ayucldzk],
.card[b-v2ayucldzk] {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border-radius: var(--radius-minimal);
  overflow: hidden;
  transition: var(--transition);
  cursor: pointer;
  position: relative;
}

.item[b-v2ayucldzk] {
  flex: 0 0 calc(50% - 5px);
  border: 1px solid var(--gray-200);
}
@media (min-width: 768px) {
  .item[b-v2ayucldzk] {
    flex: 0 0 calc(25% - 8px);
  }
}
@media (min-width: 992px) {
  .item[b-v2ayucldzk] {
    flex: 0 0 calc(20% - 8px);
  }
}

.card[b-v2ayucldzk] {
  border: 1px solid var(--subtle-gray);
  /* A carousel slide sets the height; the card fills it, so a short name does
     not leave a card floating in its slide. */
  height: 100%;
}

.item:hover[b-v2ayucldzk],
.card:hover[b-v2ayucldzk] {
  box-shadow: var(--shadow-lg);
  transform: translateY(-4px);
  border-color: transparent;
}
.item:hover .img-wrap[b-v2ayucldzk]  img {
  transform: scale(1.05);
}
.card:hover .img-wrap[b-v2ayucldzk]  img {
  transform: scale(1.06);
}

.card-link[b-v2ayucldzk] {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
}

/* ---- the image --------------------------------------------------------- */

.img-wrap[b-v2ayucldzk] {
  position: relative;
  overflow: hidden;
  aspect-ratio: 2 / 3;
  background: var(--neutral-100);
}
/* The wrapper is the slot's box and matches the product card's locked 2:3,
   the shape of the CDN's product photos, so a correctly supplied 1000×1500
   asset is not cropped. Dimensions and
   cropping belong to the slot image; only the framing and the hover
   transition are the card's business. */
.img-wrap[b-v2ayucldzk]  img {
  /* Anchor nearer the top so the top of the product isn't cropped. */
  object-position: center 20%;
  transition: var(--transition);
}

/* ---- the tag ----------------------------------------------------------- */

.tag[b-v2ayucldzk] {
  position: absolute;
  background: var(--yellow-accent);
  color: var(--primary-black);
  font-weight: 700;
  z-index: 5;
  text-transform: uppercase;
}

.item .tag[b-v2ayucldzk] {
  top: var(--space-10);
  right: var(--space-10);
  font-size: var(--text-0-65);
  padding: var(--space-4) var(--space-10);
  border-radius: 15px;
  letter-spacing: 0.8px;
}

.card .tag[b-v2ayucldzk] {
  top: var(--space-12);
  right: var(--space-12);
  font-size: var(--text-0-7);
  padding: var(--space-5) var(--space-12);
  border-radius: var(--radius-pill);
  letter-spacing: 1px;
  box-shadow: 0 4px 14px var(--yellow-a40);
}

/* ---- the discount strip ------------------------------------------------- */
/* Product Owner, 2026-10-26: a yellow "NN% OFF" ticker slides in along the top
   edge of a discounted card's photo while a mouse hovers the card, and the
   pill steps aside so the discount is not shown twice. Hover-capable pointers
   only: a phone has no hover and keeps the pill. It takes no pointer events,
   so a click still lands on the card's link. Reduced motion needs no block of
   its own: app.css collapses every keyframe animation under
   prefers-reduced-motion. */
.promo-strip[b-v2ayucldzk] {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 4;
  background: var(--yellow-accent);
  color: var(--primary-black);
  overflow: hidden;
  white-space: nowrap;
  padding: var(--space-4) 0;
  pointer-events: none;
  transform: translateY(-100%);
  opacity: 0;
  transition: var(--transition);
}

.promo-strip-track[b-v2ayucldzk] {
  display: flex;
  width: max-content;
  animation: promo-strip-scroll-b-v2ayucldzk 15s linear infinite;
  animation-play-state: paused;
}

.promo-strip-track span[b-v2ayucldzk] {
  flex-shrink: 0;
  padding-right: var(--space-24);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
}

.item .promo-strip-track span[b-v2ayucldzk] {
  font-size: var(--text-0-7);
}

.card .promo-strip-track span[b-v2ayucldzk] {
  font-size: var(--text-0-75);
}

@keyframes promo-strip-scroll-b-v2ayucldzk {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

@media (hover: hover) {
  .item:hover .promo-strip[b-v2ayucldzk],
  .card:hover .promo-strip[b-v2ayucldzk] {
    transform: none;
    opacity: 1;
  }

  .item:hover .promo-strip-track[b-v2ayucldzk],
  .card:hover .promo-strip-track[b-v2ayucldzk] {
    animation-play-state: running;
  }

  .item:hover .tag.has-strip[b-v2ayucldzk],
  .card:hover .tag.has-strip[b-v2ayucldzk] {
    opacity: 0;
  }
}

/* "New" is the grid's treatment and the flash tag is the carousel's; neither
   surface has ever rendered the other's, so each stays where it was. */
.item .new[b-v2ayucldzk] {
  background: var(--primary-black);
  color: var(--white);
}

.card .flash-tag[b-v2ayucldzk] {
  background: var(--danger);
  color: var(--white);
  box-shadow: 0 4px 14px var(--danger-a60);
  font-weight: 800;
  letter-spacing: 1.2px;
  padding: var(--space-5) var(--space-14);
}

/* ---- the body ---------------------------------------------------------- */

/* THE BODY IS WHERE THE ALIGNMENT HAPPENS. A card with colour circles is
   taller than one without, so in a row of mixed cards the buttons used to sit
   at different heights — the ragged edge the Product Owner asked to fix. The
   body stretches to fill the card, and .add-btn takes an auto top margin, so
   every button in a row lands on the same line whatever the card above it
   carries. */
.item .body[b-v2ayucldzk],
.card .body[b-v2ayucldzk] {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
}

.item .body[b-v2ayucldzk] {
  padding: var(--space-12);
}

.card .body[b-v2ayucldzk] {
  padding: var(--space-14);
}

/* The two prices on one line — see the markup's own comment. The bottom
   margin matters on the TALLEST card in a row: there the auto margin below
   collapses to nothing, and without this the button would sit flush against
   the price on a card that has no colour circles to separate them. */
.price-line[b-v2ayucldzk] {
  margin-bottom: var(--space-6);
  /* A flex row that wraps WHOLE prices (2026-10-26): when the price and the
     cut price do not fit side by side, the cut price drops onto its own line
     under the price instead of breaking at the space inside "LKR 12,345.00".
     The gap replaces the cut price's old left margin, so a wrapped cut price
     starts flush with the price above it. */
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
}

.item .price-line[b-v2ayucldzk] {
  column-gap: var(--space-4);
}

.card .price-line[b-v2ayucldzk] {
  column-gap: var(--space-6);
}

.name[b-v2ayucldzk] {
  color: var(--text-dark);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.item .name[b-v2ayucldzk] {
  font-weight: 500;
  font-size: var(--text-0-82);
  margin-bottom: var(--space-3);
}

.card .name[b-v2ayucldzk] {
  font-weight: 600;
  font-size: var(--text-0-9);
  margin-bottom: var(--space-4);
}

.price[b-v2ayucldzk] {
  font-weight: 700;
  color: var(--primary-black);
  white-space: nowrap;
}

.item .price[b-v2ayucldzk] {
  font-size: var(--text-0-9);
}

.card .price[b-v2ayucldzk] {
  font-size: var(--text-1);
}

.old-price[b-v2ayucldzk] {
  text-decoration: line-through;
  /* WCAG 1.4.3: --gray-400 on white is 2.4:1. --text-muted is 4.8:1 and is
     already the token for de-emphasised text. */
  color: var(--text-muted);
  font-weight: 400;
  white-space: nowrap;
}

.item .old-price[b-v2ayucldzk] {
  font-size: var(--text-0-7);
}

.card .old-price[b-v2ayucldzk] {
  font-size: var(--text-0-78);
}

/* ---- the real colour swatches ------------------------------------------ */
/* One chip per colour the gateway actually states for this product — never a
   count keyed by grid position. A chip is not a focusable control (it is
   aria-hidden, the strip is one link's content), but since 2026-10-23 a click
   or tap on it selects that colour (product-card-colour-hover.js), so it shows
   a pointer and a ring on the selected one. There is still no :hover rule: the
   hover preview is the image's, not the dot's. */

.colors[b-v2ayucldzk] {
  display: flex;
  flex-wrap: wrap;
}

.item .colors[b-v2ayucldzk] {
  gap: var(--space-4);
  margin: var(--space-5) 0 var(--space-7);
}

.card .colors[b-v2ayucldzk] {
  gap: var(--space-5);
  margin: var(--space-7) 0 var(--space-9);
}

.color-dot[b-v2ayucldzk] {
  border-radius: 50%;
  border: 1px solid var(--black-a20);
  flex: none;
  cursor: pointer;
}

/* The colour the shopper clicked. A ring offset from the dot so it reads on
   any hue, including a white or a photo swatch. */
.color-dot.is-selected[b-v2ayucldzk] {
  outline: 2px solid var(--primary-black);
  outline-offset: 1px;
}

.item .color-dot[b-v2ayucldzk] {
  width: 20px;
  height: 20px;
}

.card .color-dot[b-v2ayucldzk] {
  width: 20px;
  height: 20px;
}

/* Story 29.2 (Product Owner, 2026-10-21: "item card colour circles make
   slightly big for mobile screens, another 2px"): a phone's dots are 22px, 2px
   over the 20px above, so a thumb can land on one. Phone only (max-width 767px,
   the storefront's mobile width); the desktop sizes above are untouched. The
   `.photo` and `.unresolved` dots are the same element with a different
   background, so they grow with it, and the strip's own `flex-wrap` and gap
   rules still lay the larger dots out tidily. */
@media (max-width: 767px) {
  .item .color-dot[b-v2ayucldzk] {
    width: 22px;
    height: 22px;
  }

  .card .color-dot[b-v2ayucldzk] {
    width: 22px;
    height: 22px;
  }
}

/* A colour the gateway named but did not resolve to a hex. No hue is guessed
   for it — the stripe is a "not drawn" texture, not a stand-in colour. */
.color-dot.unresolved[b-v2ayucldzk] {
  background-image: repeating-linear-gradient(
    45deg,
    var(--white) 0,
    var(--white) 2px,
    var(--gray-300) 2px,
    var(--gray-300) 4px
  );
}

/* Rung 2 of the ladder (hex, photo, hatch): the colour's OWN product
   photograph. `auto` rather than `cover` (Product Owner): cover filled the dot
   edge to edge by cropping into the photo, and on a swatch this small the crop
   routinely landed on a sleeve or a background rather than on the colour the
   dot is there to show. At `auto` the image keeps its own scale and the dot
   reads as the garment it came from. */
.color-dot.photo[b-v2ayucldzk] {
  background-image: var(--dot-image);
  background-size: auto;
  background-position: center;
}

/* ---- the add control --------------------------------------------------- */

/* Outlined text button. It renders and does nothing — see the component. */
/* An <a> since Story 12.12, not a <button>. These four declarations exist only
   because of that change: an anchor is inline, underlined and left-aligned by
   default, where the button it replaced was none of those. Without them the
   control reads as a stray underlined word rather than the framed action the
   card has always shown. */
.add-btn[b-v2ayucldzk] {
  position: relative;
  z-index: 3;
  background: transparent;
  /* An outlined button, 40% wide and pushed to the card's right edge
     (2026-10-26). The border takes the text colour, so it turns white with the
     text on hover. margin-left: auto works because the card body is a flex
     column. On a phone 40% is narrower than "+ Add" plus its padding, so
     max-content lets the button grow just enough to keep the label on one
     line. */
  border: 1px solid;
  color: var(--primary-black);
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition);
  width: 40%;
  min-width: max-content;
  white-space: nowrap;
  display: block;
  text-align: center;
  text-decoration: none;
  box-sizing: border-box;
  margin-left: auto;
}
.add-btn:hover[b-v2ayucldzk] {
  background: var(--primary-black);
  color: var(--white);
}

.item .add-btn[b-v2ayucldzk] {
  display: inline-block;
  padding: var(--space-5) var(--space-14);
  border-radius: var(--radius-pill);
  font-size: var(--text-0-7);
  /* auto, not a fixed gap: it absorbs whatever height the card has spare, so
     the button sits on the card's floor rather than a fixed distance below
     whatever happens to precede it. On the tallest card in a row it collapses
     to nothing, which is why the elements above carry their own bottom
     margins. */
  margin-top: auto;
  letter-spacing: 0.3px;
  text-align: center;
}

.card .add-btn[b-v2ayucldzk] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  padding: var(--space-6) var(--space-16);
  border-radius: 25px;
  font-size: var(--text-0-75);
  /* See .item .add-btn — same reasoning, the rail's own metrics. */
  margin-top: auto;
  letter-spacing: 0.5px;
  text-transform: lowercase;
}

/* ---- footer row, Size button and size panel (Story 32.18) --------------- */

/* The bottom controls on one line: Size at the left, the add link at the
   right. The row takes the auto top margin the add link used to carry, so it
   still sits on the card's floor; the add link keeps its own outlined style and
   its margin-left: auto, which is what holds it at the right. */
.card-actions[b-v2ayucldzk] {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  margin-top: auto;
}

.card-actions .add-btn[b-v2ayucldzk] {
  margin-top: 0;
}

/* A sibling of the add link: the same outline, height and weight. */
.size-btn[b-v2ayucldzk] {
  position: relative;
  z-index: 3;
  background: transparent;
  border: 1px solid;
  color: var(--primary-black);
  font-family: inherit;
  font-weight: 600;
  line-height: normal;
  cursor: pointer;
  transition: var(--transition);
  white-space: nowrap;
  text-align: center;
  box-sizing: border-box;
  /* The same box as the add link beside it (Product Owner, 2026-10-26: "size
     button get the same size as the add button"): 40% wide, growing only when
     its label needs it, and without the 44px floor the global button rules
     give every <button>, which the add link (an <a>) never had. */
  width: 40%;
  min-width: max-content;
  min-height: 0;
}
/* Story 32.22: once a size is chosen the button reads that size. It keeps the
   40% box: a long name is cut with an ellipsis rather than wrapping or growing. */
.size-btn.is-chosen[b-v2ayucldzk] {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.size-btn:hover[b-v2ayucldzk],
.size-btn[aria-expanded="true"][b-v2ayucldzk] {
  background: var(--primary-black);
  color: var(--white);
}
.item .size-btn[b-v2ayucldzk] {
  padding: var(--space-5) var(--space-14);
  border-radius: var(--radius-pill);
  font-size: var(--text-0-7);
  letter-spacing: 0.3px;
}
/* On a phone a grid card is narrow enough that 40% is less than "+ Add" plus
   its desktop padding, so the two buttons grew to different widths. Tighter
   side padding lets both labels fit inside the same 40%. */
@media (max-width: 767px) {
  .item .size-btn[b-v2ayucldzk],
  .item .add-btn[b-v2ayucldzk] {
    padding-left: var(--space-8);
    padding-right: var(--space-8);
  }
}
.card .size-btn[b-v2ayucldzk] {
  padding: var(--space-6) var(--space-16);
  border-radius: 25px;
  font-size: var(--text-0-75);
  letter-spacing: 0.5px;
}

/* The panel: an accordion sheet that rises out of the top edge of the card
   body over the lower part of the photo (Product Owner, 2026-10-26: "without
   replacing image area ... rise from the top of the item body, with smooth
   animated moving up, its height according to the sizes it contains"). It is
   anchored to the photo's bottom edge and is only as tall as its content,
   capped at the photo's height. Closed, it sits translated one full height
   down, below that edge, where .img-wrap's own overflow: hidden clips it; the
   script removes [hidden], then adds .is-open on the next frame so the slide
   runs, and on close waits for the slide before setting [hidden] again. Above
   the tag (5), the discount strip (4) and the card's full-cover link (2). The
   [hidden] rule is explicit because display: flex would otherwise beat it. */
.size-panel[b-v2ayucldzk] {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 100%;
  z-index: 6;
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
  padding: var(--space-12);
  overflow-y: auto;
  box-sizing: border-box;
  background: var(--white);
  color: var(--primary-black);
  border-radius: var(--radius-xs) var(--radius-xs) 0 0;
  box-shadow: 0 -8px 24px var(--black-a06);
  transform: translateY(100%);
  transition: transform var(--duration-0-35) var(--ease-standard);
}
.size-panel.is-open[b-v2ayucldzk] {
  transform: translateY(0);
}
.size-panel[hidden][b-v2ayucldzk] {
  display: none;
}
/* While the live stock loads (Product Owner, 2026-10-26): the sheet stays
   solid white, never faded, and a small spinning circle stands in for the
   chips, which stay out of sight so a size cannot flash as in stock before
   the answer crosses it out. A failed fetch clears aria-busy too, and the
   chips then show as plain links. The spin reuses app.css's own
   odel-loading-spin and its duration token, so reduced motion stops it. */
.size-panel-spinner[b-v2ayucldzk] {
  display: none;
}
.size-panel[aria-busy="true"] .size-panel-spinner[b-v2ayucldzk] {
  display: block;
  align-self: center;
  margin: auto 0;
  width: var(--space-24);
  height: var(--space-24);
  box-sizing: border-box;
  border-radius: 50%;
  border: 2px solid var(--gray-200);
  border-top-color: var(--primary-black);
  animation: odel-loading-spin var(--duration-0-8) linear infinite;
}
.size-panel[aria-busy="true"] .size-grid[b-v2ayucldzk] {
  display: none;
}

.size-panel-title[b-v2ayucldzk] {
  font-size: var(--text-0-8);
  font-weight: 700;
}

.size-grid[b-v2ayucldzk] {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
}

/* ::deep: the script re-renders the chips from the live endpoint, and those
   elements do not carry this component's scope attribute. */
.size-panel[b-v2ayucldzk]  .size-chip {
  display: inline-block;
  min-width: 36px;
  padding: var(--space-6) var(--space-10);
  border: 1px solid var(--primary-black);
  border-radius: var(--radius-xs);
  color: var(--primary-black);
  font-size: var(--text-0-8);
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  box-sizing: border-box;
  cursor: pointer;
}
.size-panel[b-v2ayucldzk]  .size-chip:hover {
  background: var(--primary-black);
  color: var(--white);
}
.size-panel[b-v2ayucldzk]  .size-chip.is-out {
  border-color: var(--text-muted);
  color: var(--text-muted);
  text-decoration: line-through;
}
.size-panel[b-v2ayucldzk]  .size-chip.is-out:hover {
  background: var(--white);
  color: var(--text-muted);
}

/* ---- add to bag from the card (Story 32.19) ------------------------------ */

/* The chosen size: filled black, white text. Beats :hover/.is-out by coming last. */
.size-panel[b-v2ayucldzk]  .size-chip.is-selected,
.size-panel[b-v2ayucldzk]  .size-chip.is-selected:hover {
  background: var(--primary-black);
  color: var(--white);
}

/* "Select a size" / "Selected: M". Empty means no line at all. */
.size-panel-prompt[b-v2ayucldzk] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
}
.size-panel-prompt:empty[b-v2ayucldzk] {
  display: none;
}

/* "Pick a colour", under the dots, when + Add is pressed with several colours
   and none chosen. */
.colour-hint[b-v2ayucldzk] {
  font-size: var(--text-0-75);
  font-weight: 600;
  color: var(--text-muted);
  margin: 0 0 var(--space-6);
}
.colour-hint[hidden][b-v2ayucldzk] {
  display: none;
}

/* A short ring pulse on the dots; reduced motion is covered globally. */
.colors.is-nudged .color-dot[b-v2ayucldzk] {
  animation: card-colour-nudge-b-v2ayucldzk var(--duration-0-45) var(--ease-standard) 2;
}
@keyframes card-colour-nudge-b-v2ayucldzk {
  0% { box-shadow: 0 0 0 0 var(--text-muted); }
  100% { box-shadow: 0 0 0 var(--space-5) transparent; }
}

/* The press is in flight (Product Owner, 2026-10-26): the label gives way to a
   small spinning circle inside the button, the same loader the size panel
   uses, at full opacity; the page skeleton is only for real navigation. The
   label stays in the button (only made transparent) so the button keeps its
   size. The duration is app.css's token, so reduced motion stops it. */
.add-btn.is-submitting[b-v2ayucldzk] {
  position: relative;
  color: transparent;
  cursor: progress;
}
.add-btn.is-submitting[b-v2ayucldzk]::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--space-14);
  height: var(--space-14);
  box-sizing: border-box;
  border-radius: 50%;
  border: 2px solid var(--gray-200);
  border-top-color: var(--primary-black);
  animation: add-btn-spin-b-v2ayucldzk var(--duration-0-8) linear infinite;
}
/* Under the pointer the button is filled black, so the ring turns light. */
.add-btn.is-submitting:hover[b-v2ayucldzk]::after {
  border-color: var(--white-a12);
  border-top-color: var(--white);
}
/* Its own keyframes, because the spinner is centred with a translate the
   shared odel-loading-spin (rotate only) would overwrite. */
@keyframes add-btn-spin-b-v2ayucldzk {
  from {
    transform: translate(-50%, -50%) rotate(0deg);
  }
  to {
    transform: translate(-50%, -50%) rotate(360deg);
  }
}

/* Story 32.20: a card that cannot be bought here (a wishlist item with no price in
   the shopper's country). Greyed image and body; a caller's own controls in the
   photo (the wishlist's remove button) stay at full strength. */
.item.is-unavailable[b-v2ayucldzk],
.card.is-unavailable[b-v2ayucldzk] {
  cursor: default;
  background: var(--neutral-100);
}
.item.is-unavailable:hover[b-v2ayucldzk],
.card.is-unavailable:hover[b-v2ayucldzk] {
  box-shadow: none;
  transform: none;
  border-color: var(--gray-200);
}
.item.is-unavailable:hover .img-wrap[b-v2ayucldzk]  img,
.card.is-unavailable:hover .img-wrap[b-v2ayucldzk]  img {
  transform: none;
}
.is-unavailable .img-wrap[b-v2ayucldzk]  img {
  opacity: 0.45;
  filter: grayscale(1);
}
.is-unavailable .body .name[b-v2ayucldzk] {
  opacity: 0.6;
}
/* _content/Odel.Web/Components/Cards/ProductGrid.razor.rz.scp.css */
.grid[b-f50ssev19w] {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-10);
}
/* _content/Odel.Web/Components/Cards/SectionHeaderLink.razor.rz.scp.css */
/* ---- the heading row and its way out (2026-09-28; shared 2026-10-21) -------
   The same shape the Flash Sale header uses for its countdown: the title
   keeps the left, the link takes the right, and the pair stays on one line.
   Lifted out of ShopByCategory and DepartmentBlocks (which each carried an
   identical copy) so those two and the brand page's "All products" link
   cannot drift. */
.header[b-4gfghc59od] {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  margin-bottom: var(--space-16);
}
.header[b-4gfghc59od]  .section-header {
  margin-bottom: 0;
}

/* A quiet link, not a second primary button: it is the way out of a
   merchandised row, and it competes with the cards beside it if it shouts.
   white-space keeps the label from wrapping under its own arrow when the
   title beside it is long. */
.see-all[b-4gfghc59od] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  flex: none;
  white-space: nowrap;
  padding: var(--space-8) var(--space-14);
  border: 1px solid var(--neutral-400);
  border-radius: var(--radius-minimal);
  color: var(--text-dark);
  background: var(--white);
  font-size: var(--text-0-85);
  font-weight: 600;
  text-decoration: none;
  transition: var(--transition);
}
.see-all:hover[b-4gfghc59od],
.see-all:focus-visible[b-4gfghc59od] {
  background: var(--primary-black);
  border-color: var(--primary-black);
  color: var(--white);
}
.see-all i[b-4gfghc59od] {
  font-size: var(--text-0-7);
}

/* The link is the current state (the brand page with no category applied): a
   darker edge, quiet enough not to read as a pressed primary button. */
.see-all.is-active[b-4gfghc59od] {
  border-color: var(--primary-black);
}
.see-all.is-active:hover[b-4gfghc59od],
.see-all.is-active:focus-visible[b-4gfghc59od] {
  border-color: var(--primary-black);
}

/* On a phone the row is tight, so the arrow carries the direction and the
   label shortens rather than the control wrapping to a second line. */
@media (max-width: 575px) {
  .header[b-4gfghc59od] {
    gap: var(--space-8);
    margin-bottom: var(--space-12);
  }
  .see-all[b-4gfghc59od] {
    padding: var(--space-6) var(--space-10);
    font-size: var(--text-0-75);
  }
}

/* A header carrying controls beside its link (the homepage's All Products
   sort). The pair stays on the right; on a phone the title takes its own row
   and the controls share the next, so nothing scrolls sideways at 360px. */
.actions[b-4gfghc59od] {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  flex: none;
}
@media (max-width: 575px) {
  .header.has-actions[b-4gfghc59od] {
    flex-wrap: wrap;
  }
  .header.has-actions[b-4gfghc59od]  .section-header {
    flex: 1 1 100%;
  }
  .actions[b-4gfghc59od] {
    flex: 1 1 100%;
    justify-content: space-between;
    min-width: 0;
  }
}
/* _content/Odel.Web/Components/Carousel/BannerCarousel.razor.rz.scp.css */
.wrap[b-9fth9esi1b] {
  width: 100vw;
  margin-left: calc(-50vw + 50%);
  position: relative;
  overflow: hidden;
}

/* CSS scroll-snap, no JS (Story 11.9). Every slide is real markup from first
   paint — there is no pre-init state to hide, because there is no init step.
   The old Owl-era rule here forced only the first slide visible before a
   script ran; that whole category of bug is now impossible by construction,
   not merely fixed, so it is deleted rather than left inert. */
.track[b-9fth9esi1b] {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.track[b-9fth9esi1b]::-webkit-scrollbar {
  display: none;
}
/* Reduced motion collapses once, at the token layer (app.css's universal
   *, *::before, *::after rule already sets scroll-behavior: auto !important
   under prefers-reduced-motion) — this component declares no block of its
   own. */

/* The ground colour is the fallback guarantee: each variant below gives the
   slot a fixed height, so an image that never loads (SlotImage's own
   onerror falls back to the shared placeholder asset) leaves the slot at its
   declared size instead of collapsing the banner. Cropping and lazy-loading
   are SlotImage's own contract (width/height, aspect-ratio, object-fit,
   loading), not restated here — see the markup's own comment. */
.item[b-9fth9esi1b] {
  flex: 0 0 100%;
  scroll-snap-align: start;
  position: relative;
  display: flex;
  align-items: center;
  background-color: var(--neutral-100);
  overflow: hidden;
}
/* ::deep because SlotImage is a separate component; its rendered <img>
   carries SlotImage's own scope attribute, not this one's. Positioned to
   fill the slot absolutely — SlotImage's own default box is a static 100%
   width/height, sized for a normal-flow layout, not a stacking one. */
.item[b-9fth9esi1b]  .bg {
  position: absolute;
  inset: 0;
  z-index: 1;
  /* The size must be STATED, not left to inset alone. SlotImage emits an inline
     `aspect-ratio: 2 / 1` with `object-fit: cover`, and for an absolutely
     positioned box whose height is auto, the aspect ratio wins over the
     inset — so the image sized itself to width/2 and stopped there, leaving the
     slot's ground colour showing below it.

     That was invisible for as long as the slot was SHORTER than width/2: at
     75vh and 82vh the image happened to be taller than the slide and cropped,
     which looked correct. Raising the large-desktop slide to 91vh (Story
     12.10) made it 819px against a 720px image on a 1440px screen, and the gap
     appeared. The bug was always there; the height only revealed it.

     100%/100% plus the inline object-fit: cover is what makes the image crop to
     whatever the slot is, at every breakpoint, rather than at the ones where
     the arithmetic happens to work out. */
  width: 100%;
  height: 100%;
}

.overlay[b-9fth9esi1b] {
  position: absolute;
  inset: 0;
  z-index: 2;
}

.content[b-9fth9esi1b] {
  position: relative;
  z-index: 3;
  color: var(--white);
}

/* ===== Pagination dots (real anchors, never decorative) ===== */
.dots[b-9fth9esi1b] {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  gap: var(--space-8);
}
.dot[b-9fth9esi1b] {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--white-a45);
  transition: var(--transition);
  display: inline-block;
}
.dot:hover[b-9fth9esi1b],
.dot:focus-visible[b-9fth9esi1b] {
  background: var(--yellow-accent);
  width: 28px;
  border-radius: var(--radius-minimal);
}

/* ===== HERO variant ===== */
/* The phone height (Story 12.10, Product Owner 2026-09-24). 42vh with a 260px
   floor until then — the floor is deliberately gone, not merely reduced, so the
   banner scales all the way down on a short viewport instead of pushing the
   category row below the fold on a landscape phone.

   Only the phone: the 768px and 1200px rules below replace this outright, so a
   desktop hero is unchanged. Worth knowing when editing: at 20vh the slide is
   around 130px on a typical phone, which is a band rather than a picture — the
   overlay gradient and the title still read, but a slide whose artwork carries
   its message in the image rather than in the text has very little room.

   2026-10-15 (Product Owner): the phone hero now sizes by the same 164px floor
   as the mid banner instead of 20vh. Only BRAND pages reach this rule on a
   phone - the homepage hero is display:none below 992px - so this is the
   brand-page banner's phone height. It shows once the brand's CMS document
   carries slides (none are authored today). */
.hero .item[b-9fth9esi1b] {
  min-height: 164px;
}
@media (min-width: 768px) {
  .hero .item[b-9fth9esi1b] {
    height: 75vh;
    min-height: 500px;
  }
}
/* The large-desktop landing banner (Story 12.10): 82vh, then 91vh from
   2026-09-24, and 72vh with NO floor since 2026-09-29 (Product Owner).

   At 72vh a 900px screen shows roughly 648px of banner and leaves the start of
   the next section in view. The 580px floor is removed at this size only, by
   instruction — and removed rather than merely dropped from this rule, because
   the 768px rule above also matches here and would otherwise still impose its
   500px floor. So min-height is reset to 0 and the slide scales with the
   viewport all the way down; the tablet rule keeps its 500px.

   82vh from 2026-10-15 (Product Owner: "desktop hero banner and brand hero
   banner height should be 82"). One rule serves both: the homepage hero and
   the brand-page hero are the same BannerCarousel "hero" variant. Still no
   floor at this size, for the reason above. */
@media (min-width: 1200px) {
  .hero .item[b-9fth9esi1b] {
    height: 82vh;
    min-height: 0;
  }
}
.hero .overlay[b-9fth9esi1b] {
  background: linear-gradient(180deg, var(--black-a10) 0%, var(--black-a40) 65%, var(--black-a70) 100%);
}
.hero .content[b-9fth9esi1b] {
  padding: 0 var(--space-24);
  max-width: 600px;
}
/* The hero slide does not show its CMS tag. The mid-promo variant still
   does, which is why this is scoped to .hero rather than removed from the
   markup: the span is one shared component and the tag is still authored
   content the CMS supplies. It also still names the call to action when a
   slide carries no CTA word (FallbackCta), so the value is read even
   though it is not shown. */
.hero .tag[b-9fth9esi1b] {
  display: none;
}
.hero h2[b-9fth9esi1b] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-2-2);
  font-weight: 700;
  line-height: var(--leading-1-15);
  margin-bottom: var(--space-10);
  color: var(--white);
}
@media (min-width: 768px) {
  .hero h2[b-9fth9esi1b] {
    font-size: var(--text-3-2);
    font-family: 'Playfair Display', serif;
  }
}
.hero p[b-9fth9esi1b] {
  font-size: var(--text-0-95);
  opacity: 0.9;
  margin-bottom: var(--space-20);
  font-weight: 300;
}
.hero .btn[b-9fth9esi1b] {
  display: inline-block;
  background: var(--white);
  color: var(--primary-black);
  padding: var(--space-13) var(--space-34);
  border-radius: 30px;
  font-weight: 600;
  text-decoration: none;
  letter-spacing: 0.5px;
  transition: var(--transition);
  font-size: var(--text-0-9);
  border: none;
  cursor: pointer;
}
.hero .btn:hover[b-9fth9esi1b] {
  background: var(--yellow-accent);
  color: var(--primary-black);
  transform: translateY(-2px);
  box-shadow: 0 8px 28px var(--yellow-a45);
}
.hero .dots[b-9fth9esi1b] {
  bottom: var(--space-20);
}

/* Hero is hidden on mobile (mobile shows tabs + category + mid banner). */
@media (max-width: 991px) {
  .hero[b-9fth9esi1b] {
    display: none;
  }
}
/* Brand pages keep the hero visible on every breakpoint. */
@media (max-width: 991px) {
  .show-on-mobile[b-9fth9esi1b] {
    display: block;
  }
}

/* ===== MID banner variant (mobile only; brand pages opt in on desktop) ===== */
.mid[b-9fth9esi1b] {
  background: var(--black-pure);
}
@media (min-width: 992px) {
  .mid[b-9fth9esi1b] {
    display: none;
  }
  .mid.show-on-desktop[b-9fth9esi1b] {
    display: block;
  }
}
/* Mobile: the banner fills its container - no viewport bleed, no rounded
   corners (Product Owner, 2026-10-15, homepage and brand-page banners). */
@media (max-width: 991px) {
  .mid[b-9fth9esi1b] {
    width: 100%;
  }
}
/* Phones size by a floor rather than a share of the viewport: 22vh made the
   band jump with the browser chrome and shrink on short screens. From 768px
   up the heights below still apply. */
.mid .item[b-9fth9esi1b] {
  min-height: 164px;
}
@media (min-width: 768px) {
  .mid .item[b-9fth9esi1b] {
    height: 38vh;
    min-height: 260px;
  }
}
@media (min-width: 1200px) {
  .mid .item[b-9fth9esi1b] {
    height: 42vh;
    min-height: 300px;
  }
}
.mid .overlay[b-9fth9esi1b] {
  background: linear-gradient(135deg, var(--black-a68) 0%, var(--black-a20) 100%);
}
.mid .content[b-9fth9esi1b] {
  padding: 0 var(--space-24);
  max-width: 520px;
}
.mid .tag[b-9fth9esi1b] {
  display: inline-block;
  background: var(--yellow-accent);
  color: var(--primary-black);
  font-size: var(--text-0-62);
  font-weight: 700;
  letter-spacing: 2px;
  padding: var(--space-4) var(--space-12);
  border-radius: 15px;
  margin-bottom: var(--space-6);
  text-transform: uppercase;
}
.mid h3[b-9fth9esi1b] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-1-25);
  font-weight: 700;
  margin-bottom: var(--space-4);
  line-height: var(--leading-1-2);
  color: var(--white);
}
@media (min-width: 768px) {
  .mid h3[b-9fth9esi1b] {
    font-size: var(--text-2);
    font-family: 'Playfair Display', serif;
  }
}
.mid p[b-9fth9esi1b] {
  font-size: var(--text-0-78);
  opacity: 0.88;
  margin-bottom: var(--space-10);
  font-weight: 300;
}
.mid .btn[b-9fth9esi1b] {
  display: inline-block;
  background: var(--white);
  color: var(--primary-black);
  padding: var(--space-7) var(--space-20);
  border-radius: 25px;
  font-weight: 600;
  text-decoration: none;
  font-size: var(--text-0-78);
  transition: var(--transition);
  border: none;
  cursor: pointer;
}
.mid .btn:hover[b-9fth9esi1b] {
  background: var(--yellow-accent);
  color: var(--primary-black);
  transform: translateY(-1px);
}
.mid .dots[b-9fth9esi1b] {
  bottom: var(--space-10);
}
.mid .dot[b-9fth9esi1b] {
  width: 6px;
  height: 6px;
}
.mid .dot:hover[b-9fth9esi1b],
.mid .dot:focus-visible[b-9fth9esi1b] {
  width: 20px;
  border-radius: 5px;
}

/* ===== PROMO variant (homepage advertisement band; every width) ===== */
/* Unlike .hero and .mid there is no display:none breakpoint: the band shows on
   phones and desktops alike. Full-bleed from 992px up, inset on phones. */
.promo[b-9fth9esi1b] {
  background: var(--neutral-100);
}
@media (max-width: 991px) {
  .promo[b-9fth9esi1b] {
    width: calc(100vw - 32px);
    margin-left: calc(-50vw + 50% + var(--space-16));
    border-radius: var(--radius-minimal);
  }
}
/* The band's height FOLLOWS the slot's declared ratio (BannerCarousel.razor sets
   --promo-ratio and --promo-ratio-mobile from ImageSlots), so an asset authored to
   spec renders whole instead of being cropped by a fixed pixel height. The phone
   ratio applies below 768px, the same window SlotImage's <source> uses. */
.promo .item[b-9fth9esi1b] {
  aspect-ratio: var(--promo-ratio);
  align-self: flex-start;
  height: auto;
}
@media (max-width: 767px) {
  .promo .item[b-9fth9esi1b] {
    aspect-ratio: var(--promo-ratio-mobile);
  }
}
@media (min-width: 992px) {
  .promo[b-9fth9esi1b] {
    width: 100vw;
    margin-left: calc(-50vw + 50%);
    border-radius: 0;
  }
}
.promo .overlay[b-9fth9esi1b] {
  background: linear-gradient(135deg, var(--black-a68) 0%, var(--black-a20) 100%);
}
.promo .content[b-9fth9esi1b] {
  padding: 0 var(--space-24);
  max-width: 520px;
}
.promo .tag[b-9fth9esi1b] {
  display: inline-block;
  background: var(--yellow-accent);
  color: var(--primary-black);
  font-size: var(--text-0-62);
  font-weight: 700;
  letter-spacing: 2px;
  padding: var(--space-4) var(--space-12);
  border-radius: 15px;
  margin-bottom: var(--space-6);
  text-transform: uppercase;
}
.promo .tag:empty[b-9fth9esi1b] {
  display: none;
}
.promo h3[b-9fth9esi1b] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-1-25);
  font-weight: 700;
  margin-bottom: var(--space-4);
  line-height: var(--leading-1-2);
  color: var(--white);
}
.promo h3:empty[b-9fth9esi1b],
.promo p:empty[b-9fth9esi1b] {
  display: none;
}
@media (min-width: 768px) {
  .promo h3[b-9fth9esi1b] {
    font-size: var(--text-2);
    font-family: 'Playfair Display', serif;
  }
}
.promo p[b-9fth9esi1b] {
  font-size: var(--text-0-78);
  opacity: 0.88;
  margin-bottom: var(--space-10);
  font-weight: 300;
}
.promo .btn[b-9fth9esi1b] {
  display: inline-block;
  background: var(--white);
  color: var(--primary-black);
  padding: var(--space-7) var(--space-20);
  border-radius: 25px;
  font-weight: 600;
  text-decoration: none;
  font-size: var(--text-0-78);
  transition: var(--transition);
  border: none;
  cursor: pointer;
}
.promo .btn:hover[b-9fth9esi1b] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}
.promo .dots[b-9fth9esi1b] {
  bottom: var(--space-10);
}
.promo .dot[b-9fth9esi1b] {
  width: 6px;
  height: 6px;
}
.promo .dot:hover[b-9fth9esi1b],
.promo .dot:focus-visible[b-9fth9esi1b] {
  width: 20px;
  border-radius: 5px;
}
/* _content/Odel.Web/Components/Carousel/Carousel.razor.rz.scp.css */
.wrap[b-ytvn31n23p] {
  position: relative;
}

/* CSS scroll-snap, no JS (Story 11.9). No pre-init state to hide any more —
   the track is exactly what a shopper sees, from the first paint, with
   scripting on or off, which is the property Owl's own init step could not
   guarantee. */
.track[b-ytvn31n23p] {
  display: flex;
  gap: var(--gap, 10px);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* Room for a focused slide's own shadow/ring past the scroll clip. */
  padding: var(--space-8) 0;
  margin: var(--space-neg-8) 0;
}
.track[b-ytvn31n23p]::-webkit-scrollbar {
  display: none;
}
/* Reduced motion collapses once, at the token layer (app.css's universal
   *, *::before, *::after rule already sets scroll-behavior: auto !important
   under prefers-reduced-motion) — this component declares no block of its
   own. */

/* Item width comes from --items/--gap/--peek, set per breakpoint by the
   component's generated <style> block (Carousel.razor's BreakpointCss). With
   no breakpoint table at all these default to one full-width item per view. */
.slide[b-ytvn31n23p] {
  scroll-snap-align: start;
  flex: 0 0 calc((100% - (var(--items, 1) - 1) * var(--gap, 10px) - var(--peek, 0px)) / var(--items, 1));
  min-width: 0;
}

/* ===== Pagination dots (real anchors, never decorative) ===== */
.dots[b-ytvn31n23p] {
  display: flex;
  justify-content: center;
  gap: var(--space-8);
  margin-top: var(--space-12);
}
.dot[b-ytvn31n23p] {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--black-a30);
  transition: var(--transition);
  display: inline-block;
}
.dot:hover[b-ytvn31n23p],
.dot:focus-visible[b-ytvn31n23p] {
  background: var(--primary-black);
  width: 20px;
  border-radius: 5px;
}
.yellow-nav .dot:hover[b-ytvn31n23p],
.yellow-nav .dot:focus-visible[b-ytvn31n23p] {
  background: var(--yellow-accent);
}

/* PREV / NEXT (Story 12.4). The wrap is already position:relative (line 1), so
   these sit over the track's vertical middle without a layout of their own.

   `hidden` is what the server renders and what the script removes, and it must
   keep working: the HTML `hidden` attribute is only `display:none` by a UA
   default rule, which ANY later display declaration overrides. Stating it here
   with [hidden] means a rule added to .carousel-nav later cannot silently
   reveal a control that has no handler attached. */
.carousel-nav[hidden][b-ytvn31n23p] {
  display: none;
}

.carousel-nav[b-ytvn31n23p] {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  /* 44px: the target size a single-glyph control needs on a phone. WCAG 2.5.8
     asks 24px as a minimum; this is the comfortable size and matches the
     checkout's icon buttons. */
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--glass-border);
  border-radius: 50%;
  background: var(--glass-bg);
  color: var(--primary-black);
  cursor: pointer;
  transition: var(--transition);
}
.carousel-nav:hover[b-ytvn31n23p] {
  background: var(--white);
  box-shadow: var(--shadow-md);
}
.carousel-nav.prev[b-ytvn31n23p] {
  left: 0;
}
.carousel-nav.next[b-ytvn31n23p] {
  right: 0;
}

/* The yellow-nav variant themes its dots; the arrows follow it, or a row with
   yellow dots would carry two unrelated control palettes. */
.yellow-nav .carousel-nav:hover[b-ytvn31n23p] {
  background: var(--yellow-accent);
  border-color: var(--yellow-accent);
}

/* On a phone the arrows overlap the first and last card, which is worse than
   not having them: the track is already a drag surface there and dragging is
   the native gesture. */
@media (max-width: 575px) {
  .carousel-nav[b-ytvn31n23p] {
    display: none;
  }
}
/* _content/Odel.Web/Components/Cart/CartContents.razor.rz.scp.css */
.layout[b-4b6qfq4ckj] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-28);
  padding: var(--space-24) 0 var(--space-40);
}
@media (min-width: 992px) {
  .layout[b-4b6qfq4ckj] {
    grid-template-columns: 1fr 380px;
    align-items: start;
  }
}

.items[b-4b6qfq4ckj] {
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
}

.item[b-4b6qfq4ckj] {
  display: flex;
  gap: var(--space-14);
  background: var(--white);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-8);
  transition: var(--transition);
  /* Top-aligned, not centred: the details column now carries the quantity
     stepper under the name, so centring would push the thumbnail and the
     prices halfway down a tall line. */
  align-items: flex-start;
  flex-wrap: wrap;
}
.item:hover[b-4b6qfq4ckj] {
  box-shadow: var(--shadow-sm);
}

/* ::deep — the thumbnail is rendered by SlotImage, so it carries that
   component's scope attribute rather than this page's. */
.item[b-4b6qfq4ckj]  .item-img {
  width: 40px;
  height: 50px;
  border-radius: 8px;
}

.item-details[b-4b6qfq4ckj] {
  flex: 1;
  min-width: 160px;
}
@media (max-width: 575px) {
  .item-details[b-4b6qfq4ckj] {
    min-width: 0;
  }
}

.item-name[b-4b6qfq4ckj] {
  font-weight: 600;
  font-size: var(--text-0-9);
  margin-bottom: var(--space-4);
}

.item-meta[b-4b6qfq4ckj] {
  color: var(--text-muted);
  font-size: var(--text-0-75);
  display: flex;
  gap: var(--space-12);
  margin-bottom: var(--space-6);
}

/* Money reads down the right edge of the line: unit price, the struck
   pre-discount price beneath it, then the server's line total. Right-aligned
   so the figures end on one column and lines compare against each other. */
.item-prices[b-4b6qfq4ckj] {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
  flex-shrink: 0;
  margin-left: auto;
}

/* The unit price is context, not the headline: small and de-emphasised above
   the line total, which is the amount the shopper is actually charged. */
.item-price[b-4b6qfq4ckj] {
  font-weight: 500;
  color: var(--text-muted);
  font-size: var(--text-0-78);
}

/* Struck on a promoted line, where this is the price before the promotion. */
.item-price.cut[b-4b6qfq4ckj] {
  text-decoration: line-through;
}

/* The stepper and the remove button sit under the item name, inside the
   details column, so they read as acting on this item. .cart-line-controls
   is already a left-aligned grid (app.css), which is what that wants. */
.item-actions[b-4b6qfq4ckj] {
  margin-top: var(--space-8);
}

/* The quantity and remove controls used to be styled here. They moved into the
   cart-line island in Story 4.5, which lives in Odel.Web.Client and carries none
   of this project's scope attribute — so .qty-control[b-xxxxxxxx] no longer
   reaches them and these rules would have matched nothing. Their successors are
   in wwwroot/css/app.css beside the add-to-cart island's, for the same reason
   and in the same shape. */

/* ===== Summary ===== */
.summary-card[b-4b6qfq4ckj] {
  background: var(--white);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-20);
  position: sticky;
  top: calc(var(--header-height) + 18px);
}
.summary-card h3[b-4b6qfq4ckj],
.summary-card h4[b-4b6qfq4ckj] {
  font-size: var(--text-0-9);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  margin-bottom: var(--space-16);
  color: var(--primary-black);
}

/* The rows are drawn by ChargeSummaryRows (shared with the mini-cart drawer), a
   child component without this stylesheet's scope attribute, so they are reached
   through ::deep from the card that holds them. */
.summary-card[b-4b6qfq4ckj]  .summary-row {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-0-85);
  margin-bottom: var(--space-12);
  color: var(--text-dark);
}
.summary-card[b-4b6qfq4ckj]  .total {
  font-weight: 800;
  font-size: var(--text-1);
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-14);
  margin-top: var(--space-8);
  color: var(--primary-black);
}
.summary-card[b-4b6qfq4ckj]  .discount-value {
  color: var(--yellow-accent);
}

/* The subtotal closes the group above it — the gross items figure and the
   discount taken off it — and that group is the only one on this card whose
   rows add up to the row beneath them. Without the rule a Shopper reads five
   flat lines and tries to sum all of them against the Total, which is not what
   they mean. A hairline and a little weight say "these three resolve here"
   without a second heading. */
.summary-card[b-4b6qfq4ckj]  .summary-row.subtotal {
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-12);
  font-weight: 700;
  color: var(--primary-black);
}

/* A link rather than a button since Story 6.1, so it works without script.
   These four declarations are what make an anchor look and lay out like the
   button it replaced: a block box, centred text and no underline. */
.checkout-btn[b-4b6qfq4ckj] {
  display: block;
  box-sizing: border-box;
  text-align: center;
  text-decoration: none;
  width: 100%;
  background: var(--yellow-accent);
  color: var(--primary-black);
  border: none;
  border-radius: 8px;
  padding: var(--space-14);
  font-weight: 800;
  font-size: var(--text-0-9);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  cursor: pointer;
  transition: var(--transition);
}
.checkout-btn:hover[b-4b6qfq4ckj] {
  background: var(--primary-black);
  color: var(--white);
}

.continue-shopping[b-4b6qfq4ckj] {
  text-align: center;
  margin-top: var(--space-12);
  font-size: var(--text-0-82);
  font-weight: 500;
  color: var(--text-muted);
}
.continue-shopping a[b-4b6qfq4ckj] {
  color: var(--primary-black);
  font-weight: 600;
  text-decoration: none;
}

.empty-cart[b-4b6qfq4ckj] {
  text-align: center;
  padding: var(--space-60) var(--space-20);
  color: var(--text-muted);
}
.empty-cart i[b-4b6qfq4ckj] {
  font-size: var(--text-3-5);
  margin-bottom: var(--space-16);
  opacity: 0.4;
}
.empty-cart a[b-4b6qfq4ckj] {
  display: inline-block;
  background: var(--primary-black);
  color: var(--white);
  padding: var(--space-10) var(--space-28);
  border-radius: 30px;
  text-decoration: none;
  font-weight: 600;
  margin-top: var(--space-16);
  transition: var(--transition);
}
.empty-cart a:hover[b-4b6qfq4ckj] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

/* Uniform mobile page rhythm. */
@media (max-width: 991px) {
  .layout[b-4b6qfq4ckj] {
    padding: var(--space-14) 0 var(--space-28);
  }
}

/* ===== Discount tag + pre-discount price on cart lines ===== */
.disc-tag[b-4b6qfq4ckj] {
  display: inline-block;
  background: var(--yellow-accent);
  color: var(--primary-black);
  font-size: var(--text-0-62);
  font-weight: 700;
  letter-spacing: 0.6px;
  padding: var(--space-2) var(--space-8);
  border-radius: 15px;
  margin-left: var(--space-8);
  vertical-align: 2px;
  white-space: nowrap;
}
/* No .old-price here any more: a cart line states one pre-discount figure, and
   it is the struck .item-price above — two struck prices on one line would be
   a puzzle, not a saving. The gateway's `mrp`, where it sends one, is what
   that struck figure shows. */


/* The line total the server computed, shown under the unit price so the two
   are never read as each other (Story 4.2) — and carrying the weight, because
   it is what this line actually costs. */
.item-line-total[b-4b6qfq4ckj] {
  font-size: var(--text-1-1);
  font-weight: 800;
  color: var(--primary-black);
  line-height: 1.2;
}

/* The gateway's own verdict on one line, attached to that line. */
.line-problem[b-4b6qfq4ckj] {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  color: var(--danger-deep);
  font-size: var(--text-0-74);
  margin-top: var(--space-6);
}

/* "Delivery is calculated at checkout, once you have given us an address."
   (Product Owner, 2026-09-27: "reduce the text size of this".)

   It carried no rule at all and so rendered at the page's body size, which
   put a standing caveat at the same weight as the figures it sits under. It
   is a note about where delivery gets settled, not a charge — so it is set at
   the token the page's other notes use and held back in the muted colour
   rather than removed: the row that used to say "Delivery / FREE" was
   removed for being a promise nothing supported, and this sentence is what
   replaced it. */
.summary-note[b-4b6qfq4ckj] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
  line-height: 1.5;
}

/* ===== In the drawer (Epic 30, Story 30-17, 2026-10-22) =====
   The same markup as the page, one column: the summary follows the lines, is not
   sticky and carries no frame of its own inside the panel. The stepper and the
   remove button are the line's own, exactly as on the page. */
/* Product Owner, 2026-10-27: the lines scroll and the order summary is fixed
   at the foot of the panel. The layout is a column that fills the drawer body;
   only .items scrolls. */
.layout.in-drawer[b-4b6qfq4ckj] {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  gap: 0;
  padding: 0;
}
.layout.in-drawer .items[b-4b6qfq4ckj] {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  gap: 0;
  padding: var(--space-4) 0;
}
.layout.in-drawer .item-actions[b-4b6qfq4ckj] {
  margin-top: var(--space-4);
}
.layout.in-drawer .item[b-4b6qfq4ckj] {
  border: none;
  border-bottom: 1px solid var(--neutral-200);
  border-radius: 0;
  padding: var(--space-10) 0;
}
.layout.in-drawer .item:hover[b-4b6qfq4ckj] {
  box-shadow: none;
}
.layout.in-drawer .summary-card[b-4b6qfq4ckj] {
  position: static;
  flex: none;
  border: none;
  border-top: 1px solid var(--neutral-200);
  border-radius: 0;
  padding: var(--space-12) 0 var(--space-16);
  background: var(--white);
}
.checkout-btn:focus-visible[b-4b6qfq4ckj] {
  outline: 2px solid var(--primary-black);
  outline-offset: 2px;
}
/* _content/Odel.Web/Components/Cart/CartDrawer.razor.rz.scp.css */
/* The cart drawer (Epic 30, Stories 30-14 and 30-17). Closed it is visibility:hidden, which
   keeps it out of the tab order and the accessibility tree; app-interop.js adds
   .open. Motion uses duration tokens, so the one reduced-motion block in app.css
   already collapses it. */
.drawer[b-h8eamrwxu9] {
  position: fixed;
  inset: 0;
  z-index: 2100;
  background: var(--ink-a60);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-0-3) var(--ease-standard), visibility var(--duration-0-3) var(--ease-standard);
}
/* Opening flips visibility at once (Epic 30 browser pass, 2026-10-22): a
   transitioning visibility starts at progress 0, i.e. still hidden, so the
   driver's focus() on the close button silently failed whenever the page had
   already been styled, and Blazor's FocusOnNavigate then put focus on the h1.
   Closing keeps the fade above. Same fix as the mega menu's .dropdown. */
.drawer.open[b-h8eamrwxu9] {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--duration-0-3) var(--ease-standard), visibility 0s;
}

.drawer-panel[b-h8eamrwxu9] {
  background: var(--white);
  width: 100%;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-minimal) var(--radius-minimal) 0 0;
  box-shadow: var(--shadow-lg);
  transform: translateY(100%);
  transition: transform var(--duration-0-3) var(--ease-standard);
}
.drawer.open .drawer-panel[b-h8eamrwxu9] {
  transform: translateY(0);
}

@media (min-width: 768px) {
  .drawer[b-h8eamrwxu9] {
    align-items: stretch;
    justify-content: flex-end;
  }
  .drawer-panel[b-h8eamrwxu9] {
    width: 420px;
    max-width: 100%;
    max-height: none;
    height: 100%;
    border-radius: 0;
    transform: translateX(100%);
  }
  .drawer.open .drawer-panel[b-h8eamrwxu9] {
    transform: translateX(0);
  }
}

.drawer-head[b-h8eamrwxu9] {
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-16) var(--space-20);
  border-bottom: 1px solid var(--neutral-200);
}
.drawer-title[b-h8eamrwxu9] {
  font-size: var(--text-1-05);
  font-weight: 800;
  margin: 0;
  color: var(--primary-black);
}
/* The heading is only ever a programmatic focus target (tabindex -1), never a
   tab stop; the controls take the visible ring below. No outline is removed. */
.drawer-close:focus-visible[b-h8eamrwxu9] {
  outline: 2px solid var(--primary-black);
  outline-offset: 2px;
}
.drawer-count[b-h8eamrwxu9] {
  font-size: var(--text-0-8);
  font-weight: 500;
  color: var(--text-muted);
  margin-left: var(--space-8);
}
.drawer-close[b-h8eamrwxu9] {
  background: none;
  border: none;
  cursor: pointer;
  font-size: var(--text-1-2);
  color: var(--primary-black);
  padding: var(--space-8);
  min-width: var(--space-40);
  min-height: var(--space-40);
}

/* The bag itself is CartContents (Story 30-17), the same component the /cart
   fallback page hosts; its own stylesheet styles the lines and the summary. The
   body scrolls so the head stays put and a long bag never pushes the Checkout
   link off the panel. */
.drawer-body[b-h8eamrwxu9] {
  flex: 1;
  /* A column whose bag (CartContents' .layout.in-drawer) takes the rest of the
     panel: its item list scrolls and its order summary stays pinned at the
     foot (Product Owner, 2026-10-27). min-height: 0 lets it shrink to the
     panel instead of growing past it. The body still scrolls as a fallback
     when it holds only notices or the empty bag. */
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  padding: 0 var(--space-20);
}
/* _content/Odel.Web/Components/Cart/CartFab.razor.rz.scp.css */
/* Story 16.6: fixed bottom-right, on top of the page content but under
   everything else this shell can raise over it — the mobile nav overlay
   (MobileNav.razor.css: 1080), the facet sheet and mega dropdown (Header /
   FacetPanel: 1200) and the toast stack (Toast.razor.css: 2000) — so opening
   any of those never leaves this floating underneath them. 1040 sits just
   below MobileBottomNav's own 1060 too; that is not a problem, because this
   button and the bottom nav bar never occupy the same rectangle in the first
   place (see the offset below) — it is deliberately not the highest z-index
   on the page. Right-aligned rather than centred like the toast, so the two
   rarely occupy the same pixels even though both can be on screen after one
   add-to-cart press.

   Below 992px it sits ABOVE MobileBottomNav.razor.css's fixed bar (68px,
   --bottom-nav-height) by POSITION — bottom: bar height + a spacing gap —
   not by stacking order: the two bands of the viewport never overlap, so
   which one has the higher z-index does not decide what covers what here.
   At 992px+ the bottom nav is gone (display:none) and the button drops to a
   corner offset instead. */
.cart-fab[b-0urgddqce6] {
  position: fixed;
  right: var(--space-16);
  bottom: calc(var(--bottom-nav-height) + var(--space-16));
  z-index: 1040;
  display: flex;
  align-items: center;
  gap: var(--space-8);
  padding: var(--space-12) var(--space-16);
  background: var(--primary-black);
  color: var(--white);
  border-radius: var(--radius-pill);
  text-decoration: none;
  font-size: var(--text-0-85);
  font-weight: 600;
  box-shadow: 0 8px 24px var(--black-a35);
  transition: var(--transition);
}
.cart-fab:hover[b-0urgddqce6] {
  color: var(--yellow-accent);
}
/* Story 23.4: the count is yellow at rest too, so it matches the nav badge.
   Re-stated under :hover because the rule above sets colour on the whole
   anchor -- without this the count would be the one element that does not
   change on hover, which reads as a rendering fault rather than a highlight. */
.cart-fab:hover .cart-fab-count[b-0urgddqce6] {
  color: var(--yellow-hover);
}
.cart-fab:focus-visible[b-0urgddqce6] {
  outline: 2px solid var(--yellow-accent);
  outline-offset: 2px;
}
.cart-fab i[b-0urgddqce6] {
  font-size: var(--text-1-1);
}
.cart-fab-count[b-0urgddqce6] {
  font-weight: 700;
  color: var(--yellow-accent);
}

@media (min-width: 992px) {
  .cart-fab[b-0urgddqce6] {
    bottom: var(--space-24);
    right: var(--space-24);
  }
}

/* The label reads "View cart (3)" at every width the epic asks for; there is
   no narrower "icon only" variant to keep in step with a second layout. */
/* _content/Odel.Web/Components/Checkout/EVoucherPanel.razor.rz.scp.css */
/* The quieter treatment this page gives every control that is not its primary
   action. It is repeated in each panel's own stylesheet rather than shared,
   because a scoped stylesheet does not reach across components: the page holds
   the same rule for the buttons it still draws, and a panel without this copy
   renders a second primary button competing with the real one. */
.submit-btn.secondary[b-96awe9jd08] {
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-400);
}
.submit-btn.secondary:hover[b-96awe9jd08] {
  background: var(--off-white);
  color: var(--primary-black);
}

/* One heading treatment for the whole side column (Product Owner, 2026-09-27:
   "make the checkout page typography consistant"). This <h3> carried no rule
   at all and so rendered at the browser's default h3 size — markedly larger
   than "How you'll pay" and "Place your order" sitting directly above and
   below it in the same card. The values are SubmitPanel's, copied for the
   reason every rule in this file is copied: scoped CSS does not reach across
   components. */
.checkout-evouchers-heading[b-96awe9jd08] {
  font-size: var(--text-0-9);
  font-weight: 700;
  color: var(--text-dark);
  margin: 0 0 var(--space-8);
}

.checkout-evouchers .notice[b-96awe9jd08],
.checkout-evouchers-none[b-96awe9jd08],
.checkout-evouchers-unused[b-96awe9jd08] {
  font-size: var(--text-0-85);
  color: var(--text-dark);
  margin: 0 0 var(--space-8);
}
/* _content/Odel.Web/Components/Checkout/LoyaltyPanel.razor.rz.scp.css */
/* The quieter treatment this page gives every control that is not its primary
   action. It is repeated in each panel's own stylesheet rather than shared,
   because a scoped stylesheet does not reach across components: the page holds
   the same rule for the buttons it still draws, and a panel without this copy
   renders a second primary button competing with the real one. */
.submit-btn.secondary[b-m6ga57bgh2] {
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-400);
}
.submit-btn.secondary:hover[b-m6ga57bgh2] {
  background: var(--off-white);
  color: var(--primary-black);
}
/* _content/Odel.Web/Components/Checkout/OrderLinesPanel.razor.rz.scp.css */
/* The quieter treatment this page gives every control that is not its primary
   action. It is repeated in each panel's own stylesheet rather than shared,
   because a scoped stylesheet does not reach across components: the page holds
   the same rule for the buttons it still draws, and a panel without this copy
   renders a second primary button competing with the real one. */
.submit-btn.secondary[b-ykys2a06kw] {
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-400);
}
.submit-btn.secondary:hover[b-ykys2a06kw] {
  background: var(--off-white);
  color: var(--primary-black);
}

/* The two line controls carry an icon rather than a word (Story 12.2). The
   button still holds its full label — inside .sr-only — so this is a square
   sized to the glyph, not to text it no longer shows.

   min-width/min-height are 44px because a control whose visible content is a
   single glyph is otherwise a pointer target a few pixels across, and WCAG
   2.5.8 asks for 24px minimum while 44px is the comfortable size on a phone.
   This is the one thing that gets WORSE when a label becomes an icon, and it
   is the reason the size is stated here rather than left to the padding. */
.submit-btn.icon-only[b-ykys2a06kw] {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  line-height: 1;
}

/* The removal reads as the destructive one on hover, which is the affordance a
   word was carrying before. Not stated at rest: a row of red buttons beside
   every line would read as four warnings rather than four controls. */
.submit-btn.icon-only.line-remove:hover[b-ykys2a06kw] {
  border-color: var(--danger-dark);
  color: var(--danger-dark);
}

/* The order rows, moved here with the markup they style. Scoped CSS follows
   the component, so these rules had to travel with it. */
.order-items[b-ykys2a06kw] {
  max-height: 320px;
  overflow-y: auto;
  margin-bottom: var(--space-4);
}

.order-item[b-ykys2a06kw] {
  display: flex;
  gap: var(--space-12);
  padding-bottom: var(--space-16);
  border-bottom: 1px solid var(--neutral-200);
  margin-bottom: var(--space-16);
}
/* The image is SlotImage's, a component below this one, so it carries no scope
   of ours and is reached with ::deep. */
.order-item[b-ykys2a06kw]  img {
  width: 70px;
  height: 70px;
  border-radius: 8px;
}
.order-item-details[b-ykys2a06kw] {
  flex: 1;
}
.order-item-name[b-ykys2a06kw] {
  font-weight: 600;
  font-size: var(--text-0-82);
  color: var(--text-dark);
  margin-bottom: var(--space-4);
}
.qty-note[b-ykys2a06kw] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
}

/* The per-line figures (Story: checkout order lines show the price). The bag
   reads this way already; the page a Shopper commits on now reads the same.
   The was-price is struck rather than coloured, so the saving is legible
   without relying on colour alone.

   NOTE the class name: .order-item-qty is taken, by the HIDDEN quantity
   control below — the "× 2" multiplier here is a different thing entirely and
   must not inherit that display:none. */
.order-item-prices[b-ykys2a06kw] {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-bottom: var(--space-2);
}
.order-item-was[b-ykys2a06kw] {
  font-size: var(--text-0-72);
  color: var(--text-muted);
  text-decoration: line-through;
}
.order-item-unit[b-ykys2a06kw] {
  font-size: var(--text-0-8);
  font-weight: 700;
  color: var(--primary-black);
}
.order-item-mult[b-ykys2a06kw],
.order-item-each[b-ykys2a06kw] {
  font-size: var(--text-0-72);
  color: var(--text-muted);
}

/* Drawn by no markup today — a bag row here carries neither a meta line nor a
   per-line price — and kept with the rest of the family rather than left behind
   on the page, where they would style nothing at all. */
.empty-order-note[b-ykys2a06kw] {
  font-size: var(--text-0-8);
  color: var(--text-muted);
  padding: var(--space-8) 0 var(--space-14);
}
.empty-order-note a[b-ykys2a06kw] {
  color: var(--primary-black);
  font-weight: 600;
}
.order-item-meta[b-ykys2a06kw] {
  font-size: var(--text-0-72);
  color: var(--text-muted);
  display: flex;
  gap: var(--space-10);
  margin-bottom: var(--space-2);
}
.order-item-price[b-ykys2a06kw] {
  font-weight: 700;
  color: var(--primary-black);
  font-size: var(--text-0-9);
}

/* THE ORDER LIST IS READ-ONLY (Product Owner, 2026-09-25).

   "in the check out page ordered item list make it read only, hide editing and
   delting icons, dont remve just hide."

   Hidden, not deleted, and deliberately so: the quantity input, the update
   button and the remove button are all still rendered, still named, still
   posted to the same handler, and every test over that form still exercises
   it. Making the list editable again is deleting this one rule.

   display:none rather than visibility or opacity, because a control that is
   merely invisible is still focusable — a keyboard Shopper would tab into a
   quantity field they cannot see, on the page where they commit. This takes
   the whole group out of the document's flow and out of the tab order
   together, which is what "read only" has to mean for everyone and not just
   for the eye.

   The quantity itself is stated above this, in .qty-note — see the markup's
   own comment. Hiding the control without replacing the figure would have left
   a Shopper unable to see how many of each item they were buying. */
.order-item-qty[b-ykys2a06kw] {
  display: none;
}
/* _content/Odel.Web/Components/Checkout/PaymentOffers.razor.rz.scp.css */
/* The bank offers (Story 7.2). A scoped stylesheet does not cross a component
   boundary, so the shared-looking rules at the bottom are this component's own
   copies rather than a reach into the panel that draws it. */

.checkout-payment-offers[b-w4o3wyga7n] {
  margin-top: var(--space-14);
  padding-top: var(--space-14);
  border-top: 1px solid var(--neutral-200);
}
.checkout-payment-offers-heading[b-w4o3wyga7n] {
  /* var(--text-0-9), not 0-85: one heading size for the whole side column
     (Product Owner, 2026-09-27, "make the checkout page typography
     consistant"). This heading was a half-step smaller than the two it sits
     between, which reads as a mistake rather than as a hierarchy. */
  font-size: var(--text-0-9);
  font-weight: 700;
  color: var(--text-dark);
  margin: 0 0 var(--space-8);
}

.checkout-offer-banks[b-w4o3wyga7n],
.checkout-offers[b-w4o3wyga7n] {
  list-style: none;
  margin: 0;
  padding: 0;
}
.checkout-offer-banks[b-w4o3wyga7n] {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}

/* One bank. Grouping is the epic's rule, so the heading has to read as a group
   heading and not as another offer. */
.checkout-offer-bank-name[b-w4o3wyga7n] {
  font-size: var(--text-0-8);
  font-weight: 700;
  color: var(--text-dark);
  margin: 0 0 var(--space-4);
}

.checkout-offers[b-w4o3wyga7n] {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}

/* One offer. A column, because a plan carries a term, a monthly amount and an
   interest under its label and each of them runs the full width at phone size
   — the purchase path must complete on a mid-tier phone with no horizontal
   scroll. */
.checkout-offer[b-w4o3wyga7n] {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border: 1px solid var(--neutral-200);
  border-radius: 10px;
  padding: var(--space-10) var(--space-12);
  background: var(--white);
}
.checkout-offer-label[b-w4o3wyga7n] {
  font-weight: 500;
  font-size: var(--text-0-85);
  color: var(--text-dark);
}
.checkout-offer-effect[b-w4o3wyga7n] {
  font-size: var(--text-0-8);
  font-weight: 600;
  color: var(--text-dark);
}
.checkout-offer-term[b-w4o3wyga7n],
.checkout-offer-monthly[b-w4o3wyga7n],
.checkout-offer-interest[b-w4o3wyga7n] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
}

/* An offer that cannot be taken is SHOWN with its reason — never hidden — so
   the reason has to read as the reason and not as another note. */
.checkout-offer-ineligible[b-w4o3wyga7n] {
  font-size: var(--text-0-72);
  font-weight: 600;
  color: var(--danger);
}
.checkout-offer[data-offer-eligible="false"][b-w4o3wyga7n] {
  background: var(--neutral-50);
}
.checkout-offer[data-offer-eligible="false"] .checkout-offer-label[b-w4o3wyga7n] {
  color: var(--text-muted);
}

.hint[b-w4o3wyga7n] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
  margin: 0 0 var(--space-8);
}
.checkout-note[b-w4o3wyga7n] {
  font-size: var(--text-0-8);
  color: var(--text-muted);
  margin: 0;
}
.notice[b-w4o3wyga7n] {
  font-size: var(--text-0-8);
  color: var(--text-dark);
  margin: 0;
}
/* _content/Odel.Web/Components/Checkout/PaymentPanel.razor.rz.scp.css */
/* The quieter treatment this page gives every control that is not its primary
   action. It is repeated in each panel's own stylesheet rather than shared,
   because a scoped stylesheet does not reach across components: the page holds
   the same rule for the buttons it still draws, and a panel without this copy
   renders a second primary button competing with the real one. */
.submit-btn.secondary[b-8llfr0w9rf] {
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-400);
}
.submit-btn.secondary:hover[b-8llfr0w9rf] {
  background: var(--off-white);
  color: var(--primary-black);
}

.checkout-payment[b-8llfr0w9rf] {
  margin-top: var(--space-16);
}
.checkout-payment-heading[b-8llfr0w9rf] {
  font-size: var(--text-0-9);
  font-weight: 700;
  color: var(--text-dark);
  margin: 0 0 var(--space-8);
}

/* The group and its arms, styled the way the delivery options are — the same
   control doing the same job, so a Shopper meets one pattern twice rather than
   two patterns once. The page's own copy of these rules cannot reach in here:
   a scoped stylesheet does not cross a component boundary. */
.checkout-options[b-8llfr0w9rf] {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  margin-bottom: var(--space-14);
}

/* THE LIST, CAPPED AFTER FOUR AND SCROLLABLE, ON DESKTOP ONLY (Product Owner,
   2026-09-25): "payment methods after four methods lock the section height
   there for that and active scroll." Desktop only for the reason the sticky
   column beside this panel is: a capped, internally-scrolling list inside a
   column that already scrolls with the page is the ordinary case on a
   phone, and a second scroll region there would trap a Shopper's thumb
   between two scrollers. The stub this storefront tests against renders two
   methods (KOKO and Cash On Delivery); the cap is sized for FOUR because the
   live gateway can name more than the stub does, and four is what the
   Product Owner asked to see before scrolling starts.

   THE HEIGHT IS A calc() BUILT FROM THE SAME TOKENS AN OPTION IS BUILT FROM —
   two lots of --space-12 padding and one line of label per option, four of
   them, plus three --space-8 gaps between them — rather than a number picked
   by eye, so the cap tracks this list's own spacing scale instead of
   drifting from it as that scale changes. */
@media (min-width: 992px) {
  .checkout-options[b-8llfr0w9rf] {
    max-height: calc((var(--space-12) * 2 + var(--space-20)) * 4 + var(--space-8) * 3);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* One arm. A grid rather than a row, because a method carries a name and, where
   the payload states them, sentences underneath it that run the full width. */
.checkout-option[b-8llfr0w9rf] {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--space-4) var(--space-12);
  border: 1px solid var(--neutral-200);
  border-radius: 10px;
  padding: var(--space-12) var(--space-14);
  background: var(--white);
  transition: var(--transition);
}
.checkout-option:hover[b-8llfr0w9rf] {
  border-color: var(--yellow-accent);
}
.checkout-option[data-payment-chosen="true"][b-8llfr0w9rf] {
  border-color: var(--primary-black);
  background: var(--neutral-50);
}
.checkout-option input[type="radio"][b-8llfr0w9rf] {
  width: 18px;
  height: 18px;
  accent-color: var(--primary-black);
  cursor: pointer;
  margin: 0;
}
.checkout-option-label[b-8llfr0w9rf] {
  font-weight: 500;
  font-size: var(--text-0-85);
  color: var(--text-dark);
  cursor: pointer;
  margin: 0;
}

/* Whatever the payload said about this method, under its name. */
.checkout-option-notes[b-8llfr0w9rf] {
  grid-column: 2 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.checkout-option-settlement[b-8llfr0w9rf] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
}

/* A method that cannot be chosen is SHOWN with its reason — never hidden — so
   the reason has to read as the reason and not as another note. */
.checkout-option-unavailable[b-8llfr0w9rf] {
  font-size: var(--text-0-72);
  font-weight: 600;
  color: var(--danger);
}
.checkout-option[data-payment-available="false"][b-8llfr0w9rf] {
  background: var(--neutral-50);
}
.checkout-option[data-payment-available="false"] .checkout-option-label[b-8llfr0w9rf] {
  color: var(--text-muted);
}

.hint[b-8llfr0w9rf] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
  margin: 0 0 var(--space-8);
}
.checkout-note[b-8llfr0w9rf] {
  font-size: var(--text-0-8);
  color: var(--text-muted);
  margin: 0;
}
.notice[b-8llfr0w9rf] {
  font-size: var(--text-0-8);
  color: var(--text-dark);
  margin: 0 0 var(--space-8);
}
/* _content/Odel.Web/Components/Checkout/PromoPanel.razor.rz.scp.css */
/* The quieter treatment this page gives every control that is not its primary
   action. It is repeated in each panel's own stylesheet rather than shared,
   because a scoped stylesheet does not reach across components: the page holds
   the same rule for the buttons it still draws, and a panel without this copy
   renders a second primary button competing with the real one. */
.submit-btn.secondary[b-xkwoz6py34] {
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-400);
}
.submit-btn.secondary:hover[b-xkwoz6py34] {
  background: var(--off-white);
  color: var(--primary-black);
}

/* ---- the promo code field (Product Owner, 2026-09-27) -------------------
   "for promo code label on checkout use checkout-submit-heading styles ...
   and promo code input make below the promo code label and make it width
   100%."

   THIS PANEL HAD NO STYLESHEET OF ITS OWN BEYOND THE BUTTON RULE ABOVE, and
   that is why it looked unlike everything around it. Scoped CSS follows the
   component that renders the markup, so neither the page's .group/.control
   field rules nor SubmitPanel's heading rule ever reached this form: the
   label rendered at the browser's default inline size and the input at the
   browser's default width, inside a column where every other heading is
   var(--text-0-9)/700 and every other field fills its line.

   The values below are copied from .checkout-submit-heading and from the
   page's own .delivery-details field treatment rather than invented. A copy
   is what a scoped stylesheet leaves as the only option — the same reason
   the .submit-btn.secondary rule above is repeated in every panel. */
.checkout-promo-heading[b-xkwoz6py34] {
  display: block;
  font-size: var(--text-0-9);
  font-weight: 700;
  color: var(--text-dark);
  margin: 0 0 var(--space-8);
}

.checkout-promo .form-group[b-xkwoz6py34] {
  margin-bottom: var(--space-12);
}

/* BELOW THE LABEL AND ACROSS THE COLUMN. The label is display:block above,
   so the input already starts its own line; the width is what makes it read
   as a field rather than as a box that happens to sit there. */
.checkout-promo .form-group input[b-xkwoz6py34] {
  display: block;
  width: 100%;
  border: 1px solid var(--neutral-400);
  border-radius: 8px;
  padding: var(--space-10) var(--space-14);
  font-size: var(--text-0-85);
  font-family: inherit;
  background: var(--off-white);
  transition: var(--transition);
}
.checkout-promo .form-group input:focus[b-xkwoz6py34] {
  border-color: var(--yellow-accent);
  box-shadow: 0 0 0 3px var(--yellow-a10);
  background: var(--white);
}
.checkout-promo .form-group input[aria-invalid="true"][b-xkwoz6py34] {
  border-color: var(--danger);
  background: var(--danger-bg);
}

/* The panel's own sentences, set the way every other panel's are, so the
   column reads as one surface. */
.checkout-promo .notice[b-xkwoz6py34],
.checkout-promo-applied[b-xkwoz6py34] {
  font-size: var(--text-0-85);
  color: var(--text-dark);
  margin: 0 0 var(--space-8);
}
/* _content/Odel.Web/Components/Checkout/SubmitPanel.razor.rz.scp.css */
/* PLACING THE ORDER (Story 7.3).

   This is the page's PRIMARY action and the only one on it — every other button
   in the checkout wears the quieter `.submit-btn.secondary` treatment its own
   panel repeats. So this stylesheet deliberately does NOT copy that rule: the
   one button here is meant to be the loudest thing on the surface, because it is
   the thing a Shopper came to press. */

.checkout-submit[b-9d3yyjtl53] {
  margin-top: var(--space-16);
  padding-top: var(--space-16);
  border-top: 1px solid var(--neutral-200);
}
.checkout-submit-heading[b-9d3yyjtl53] {
  font-size: var(--text-0-9);
  font-weight: 700;
  color: var(--text-dark);
  margin: 0 0 var(--space-8);
}

.hint[b-9d3yyjtl53] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
  margin: 0 0 var(--space-8);
}

/* A checkout something is stopping. The sentence where the button would be has
   to read as a reason rather than as a note — a Shopper scanning for the button
   meets this instead of it — while the condition itself stays worded once, in
   the notice above the checkout. */
.hint[data-submit-state="blocked"][b-9d3yyjtl53],
.hint[data-submit-state="handed-off"][b-9d3yyjtl53],
.hint[data-submit-state="pickup"][b-9d3yyjtl53] {
  font-weight: 600;
  color: var(--danger);
}

/* A refusal the gateway keyed to something this page draws no input for. Same
   treatment as a blocker: it is the reason the order did not go. */
.error[b-9d3yyjtl53] {
  font-size: var(--text-0-8);
  font-weight: 600;
  color: var(--danger);
  margin: 0 0 var(--space-8);
}

.checkout-place-order[b-9d3yyjtl53] {
  width: 100%;
}

/* A checkout that is not ready to go. The button STAYS — a Shopper who cannot
   find the way to pay is the defect this rule exists to correct — and it is
   marked unavailable rather than removed or natively disabled, so it keeps its
   place in the tab order and reads out with the reason beside it. It still
   posts if pressed, and the server refuses it with that same reason. */
.checkout-place-order[aria-disabled="true"][b-9d3yyjtl53] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* The Terms checkbox directly above Pay Now (Product Owner, 2026-10-23): a
   real control, so the label is the click target and the row reads as one line
   of text. The link is the one thing in it that is not the label's own click, so
   it keeps the text colour and an underline. */
.checkout-terms[b-9d3yyjtl53] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
  margin: 0 0 var(--space-8);
}
.checkout-terms-row[b-9d3yyjtl53] {
  display: flex;
  align-items: flex-start;
  gap: var(--space-8);
}
.checkout-terms-row input[b-9d3yyjtl53] {
  margin-top: var(--space-2);
  cursor: pointer;
}
.checkout-terms-row label[b-9d3yyjtl53] {
  margin: 0;
  cursor: pointer;
}
.checkout-terms a[b-9d3yyjtl53] {
  color: var(--text-dark);
  text-decoration: underline;
}
.checkout-terms .error[b-9d3yyjtl53],
.checkout-terms .hint[b-9d3yyjtl53] {
  margin: var(--space-8) 0 0;
}
/* _content/Odel.Web/Components/Checkout/VerifyMobilePanel.razor.rz.scp.css */
/* The quieter treatment this page gives every control that is not its primary
   action. It is repeated in each panel's own stylesheet rather than shared,
   because a scoped stylesheet does not reach across components: the page holds
   the same rule for the buttons it still draws, and a panel without this copy
   renders a second primary button competing with the real one. */
.submit-btn.secondary[b-8oi0r581rh] {
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-400);
}
.submit-btn.secondary:hover[b-8oi0r581rh] {
  background: var(--off-white);
  color: var(--primary-black);
}

/* The panel's two forms, moved here with the markup they style. Scoped CSS
   follows the component, so these rules had to travel with it. */
.gate-form[b-8oi0r581rh] {
  margin-top: var(--space-12);
}
.gate-group[b-8oi0r581rh] {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  margin-bottom: var(--space-12);
  max-width: 320px;
}
.gate-group label[b-8oi0r581rh] {
  font-size: var(--text-0-8);
  font-weight: 700;
}
.gate-group input[b-8oi0r581rh] {
  padding: var(--space-12);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  font-size: var(--text-0-9);
}
.gate-resend[b-8oi0r581rh] {
  font-size: var(--text-0-8);
  color: var(--text-muted);
  margin-top: var(--space-8);
}

/* The offer itself. A quiet, bordered block among the checkout's sections: it
   is optional, and a panel styled as loudly as the delivery details would read
   as another thing to get through. */
.verify-offer[b-8oi0r581rh] {
  margin-top: var(--space-24);
  padding: var(--space-16);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  background: var(--off-white);
}
.verify-offer-heading[b-8oi0r581rh] {
  font-size: var(--text-1);
  margin: 0 0 var(--space-8);
}
.verify-offer-note[b-8oi0r581rh] {
  font-size: var(--text-0-9);
  color: var(--text-muted);
  margin: 0;
}
/* _content/Odel.Web/Components/Footer/Footer.razor.rz.scp.css */
.footer[b-p7oqcg0nuu] {
  background: var(--primary-black);
  color: var(--gray-300);
  padding: var(--space-40) 0 var(--space-30);
  font-size: var(--text-0-85);
}
@media (max-width: 991px) {
  .footer[b-p7oqcg0nuu] {
    padding-bottom: var(--space-90);
  }
}

/* 12-column responsive grid mirroring the original Bootstrap layout. */
.grid[b-p7oqcg0nuu] {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-24);
}

.brand-col[b-p7oqcg0nuu] {
  grid-column: span 12;
}
.link-col[b-p7oqcg0nuu] {
  grid-column: span 6;
}
/* Two link columns remain (the Get To Know Us column went with its two links,
   2026-10-21). On a phone they sit side by side and the group companies take a
   full row beneath, rather than leaving a half-empty row. On desktop the grid
   still sums to 12: 4 + 2 + 2 + 4. */
.group-col[b-p7oqcg0nuu] {
  grid-column: span 12;
}
@media (min-width: 992px) {
  .brand-col[b-p7oqcg0nuu] {
    grid-column: span 4;
  }
  .link-col[b-p7oqcg0nuu] {
    grid-column: span 2;
  }
  .group-col[b-p7oqcg0nuu] {
    grid-column: span 4;
  }
}

/* The footer's column titles are h2, not h6: they are sections of the page,
   and an h6 after a page's h1 skipped four levels in every document outline
   in the storefront. The type treatment is unchanged. */
h2[b-p7oqcg0nuu] {
  color: var(--white);
  font-weight: 600;
  letter-spacing: 1px;
  font-size: var(--text-0-8);
  text-transform: uppercase;
  margin-bottom: var(--space-14);
}

.link-col ul[b-p7oqcg0nuu] {
  list-style: none;
  padding: 0;
  margin: 0;
}
.link-col li[b-p7oqcg0nuu] {
  margin-bottom: var(--space-8);
}

a[b-p7oqcg0nuu] {
  color: var(--gray-350);
  text-decoration: none;
  transition: var(--transition);
  font-size: var(--text-0-8);
}
a:hover[b-p7oqcg0nuu] {
  color: var(--yellow-accent);
}

.brand-link[b-p7oqcg0nuu] {
  display: inline-block;
  margin-bottom: var(--space-12);
}
.logo[b-p7oqcg0nuu] {
  height: 30px;
  width: auto;
}
.about[b-p7oqcg0nuu] {
  color: var(--gray-350);
  font-size: var(--text-0-82);
}

.social[b-p7oqcg0nuu] {
  margin-top: var(--space-12);
}
.social a[b-p7oqcg0nuu] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--white-a08);
  color: var(--gray-300);
  margin-right: var(--space-8);
  transition: var(--transition);
  font-size: var(--text-0-85);
}
.social a:hover[b-p7oqcg0nuu] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

.fgc-title[b-p7oqcg0nuu] {
  display: block;
  color: var(--white);
  font-size: var(--text-0-72);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: var(--space-8);
}
.fgc-links[b-p7oqcg0nuu] {
  display: flex;
  gap: var(--space-16);
}

.bottom[b-p7oqcg0nuu] {
  border-top: 1px solid var(--white-a10);
  padding-top: var(--space-18);
  margin-top: var(--space-24);
  text-align: center;
  font-size: var(--text-0-75);
  color: var(--gray-500);
}
/* _content/Odel.Web/Components/Header/CountrySwitch.razor.rz.scp.css */
/* The country switch — a flag, and a dropdown naming each country you can
   switch to (Product Owner, 2026-09-27; the open menu's names are Story
   15.1).

   ONE APPEARANCE FOR BOTH PLACEMENTS. Every rule here is shared: the desktop
   copy beside the brand mark and the phone copy beside the hamburger are the
   same control at the same size. Only the visibility pair at the foot of this
   file differs between them, and that is placement rather than appearance.

   ::deep IS NOT OPTIONAL ON THE FLAG RULES. The flags are injected as a
   MarkupString, and Blazor stamps its b-xxxx scope attribute only onto
   elements the Razor compiler emitted — never onto raw markup. A plain
   `.country-flag` rule compiles to `.country-flag[b-xxxx]` and matches
   nothing, which is exactly how the flags first shipped invisible. ::deep puts
   the scope on the ANCESTOR and matches a descendant whatever produced it. */

.country-switch[b-wzrfw57jrj] {
  position: relative;
  margin-right: var(--space-4);

  /* ON A PHONE the switch takes the free space itself, so it is pushed to the
     end of the row and sits immediately beside the hamburger. .nav-links is
     display:none below 992px, so the row is brand, switch, hamburger and there
     is nothing else to absorb the gap. */
  margin-left: auto;
}

/* ON DESKTOP the nav cluster is what gets pushed right (Header.razor.css gives
   .nav-links the auto margin), and the switch sits tight against the end of
   it. Two auto margins would SPLIT the free space between them and leave the
   department links floating in the middle of the bar, which is the thing this
   pair exists to prevent — so the switch gives its own up at this width. */
@media (min-width: 992px) {
  .country-switch[b-wzrfw57jrj] {
    margin-left: 0;
  }
}

/* ---- the trigger -------------------------------------------------------- */

/* The default disclosure triangle is removed in both spellings — the modern
   `display` one and WebKit's pseudo-element, which still wins on Safari. The
   chevron in the markup is what replaces it, so the control never shows two
   affordances or, worse, none. */
.country-trigger[b-wzrfw57jrj] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-6);
  border-radius: var(--radius-minimal);
  cursor: pointer;
  list-style: none;
  color: var(--white);
  transition: var(--transition);
}
.country-trigger[b-wzrfw57jrj]::-webkit-details-marker {
  display: none;
}
.country-trigger:hover[b-wzrfw57jrj] {
  background: var(--white-a08);
}

/* A caret that turns when the menu is open: the state is shown, not implied.
   Small, because it is a hint beside the flag rather than a control of its
   own. */
.country-caret[b-wzrfw57jrj] {
  font-size: var(--text-0-6);
  opacity: 0.7;
  transition: var(--transition);
}
.country-switch[open] .country-caret[b-wzrfw57jrj] {
  transform: rotate(180deg);
}

/* ---- the menu ----------------------------------------------------------- */

/* RIGHT-ALIGNED, and that is about the phone rather than taste: the mobile
   placement sits against the hamburger at the end of the bar, so a menu
   anchored left would run off the screen edge. Anchoring right keeps it on
   screen in both placements.

   z-index above the header's own 1050, because the menu has to sit over the
   page it overlaps — and over the sticky filter panel on a listing, which is
   the surface most likely to be underneath it.

   WIDTH: MAX-CONTENT, CAPPED (Story 15.1). Without an explicit width this is
   an absolutely positioned box with no containing-block width of its own to
   fill, so it shrink-to-fits to its content's min-content — the width a name
   wraps at when given no other choice — and "Sri Lanka" wrapped to two lines
   on every viewport, not just a narrow one. `width: max-content` asks for the
   width the content would take with NO wrapping instead, which is what a
   one-line row actually needs; `max-width` still caps that against the
   viewport so the menu itself cannot spill past the screen at 360px, where
   its right edge sits at the switch — flush with the header's own right
   edge — with no room to the left, not at the viewport's own right edge. */
.country-menu[b-wzrfw57jrj] {
  position: absolute;
  top: calc(100% + var(--space-6));
  right: 0;
  z-index: 1100;
  margin: 0;
  padding: var(--space-4);
  width: max-content;
  max-width: min(240px, calc(100vw - var(--space-8) * 2));
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);

  /* THE PANEL WEARS THE HEADER'S OWN CLOTHES (Product Owner, 2026-10-12). It
     was a white card on a dark bar — the one surface in the header that broke
     the bar's material, and it read as a different component borrowed from the
     page rather than part of the navigation it hangs from.

     These five values are not chosen here. They are the SAME five the header's
     department dropdown already uses (Header.razor.css `.dropdown`): the ink
     ground, the blur that makes it glass rather than paint, the hairline the
     header draws every edge with, its 10px radius and its lifted shadow.
     Copied rather than referenced because a scoped stylesheet cannot reach
     another component's rules — so if `.dropdown` is ever restyled, THIS BLOCK
     MUST MOVE WITH IT. That is the cost of the two menus matching, and it is
     cheaper than the country menu being the one panel that does not. */
  background: var(--ink-a97);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--white-a08);
  border-radius: 10px;
  box-shadow: 0 14px 34px var(--black-a40);
}

/* ---- one country -------------------------------------------------------- */

/* Deliberately generous for the size of what it contains. The visible target
   is a 22x15 flag, which on a phone is far below the 44px touch minimum — the
   padding is what makes the row itself the target rather than the image
   inside it. */
.country-option[b-wzrfw57jrj] {
  display: flex;
  align-items: center;
  /* Left-aligned rather than centred (Story 15.1): the row now carries a name
     beside the flag, and a centred row of uneven-length names ("Sri Lanka" vs
     "Maldives") shuffles the flag sideways between them, which centring a
     flag-only row never showed. */
  justify-content: flex-start;
  gap: var(--space-6);
  padding: var(--space-8) var(--space-12);
  /* 6px, the header dropdown's own row radius — not `--radius-minimal`, which
     is 10px and is the radius of the PANEL. A row cornered as roundly as the
     box holding it reads as a card inside a card; the header already settled
     this by giving its rows a tighter corner than their panel. */
  border-radius: 6px;
  text-decoration: none;
  transition: var(--transition);
}
/* The same hover the header's dropdown rows take: a lift in the panel's own
   material, never a light wash. `--neutral-100` stood here while the panel was
   white and would now be a near-opaque slab on the ink ground. */
.country-option:hover[b-wzrfw57jrj],
.country-option:focus-visible[b-wzrfw57jrj] {
  background: var(--white-a08);
}
/* And the name goes to the accent on hover, exactly as a nav link does, so the
   row answers the pointer the way every other row in this header does. */
.country-option:hover .country-name[b-wzrfw57jrj],
.country-option:focus-visible .country-name[b-wzrfw57jrj] {
  color: var(--yellow-accent);
}

/* ---- the name -------------------------------------------------------- */

/* The visible name (Story 15.1). Compiler-emitted, not part of the flag's
   MarkupString, so it needs no ::deep — a plain scoped rule reaches it.
   nowrap rather than letting a name break: the menu is already sized to its
   content's unwrapped width (see .country-menu's own comment), so a name
   that wrapped anyway would leave dead space beside it rather than reading
   as a mid-word break the menu was actually too narrow for. */
.country-name[b-wzrfw57jrj] {
  /* The header's own menu-text colour (Header.razor.css `.dropdown li a`), not
     the page's near-black — the panel is dark now and `--primary-black` on it
     was black on black. */
  color: var(--glass-bg);
  font-size: var(--text-1);
  white-space: nowrap;
  transition: var(--transition);
}

/* The country already in force: shown, never offered. It is a span rather
   than a link — see the markup — so it takes no hover and no pointer. The
   ring is a second, non-verbal cue alongside the name (Story 15.1 put the
   name there) and alongside aria-current for a screen reader — not the only
   way this entry is told apart from the others. */
.country-option.is-current[b-wzrfw57jrj] {
  cursor: default;
  /* The ring was `--primary-black`, which on the white panel it was drawn for
     was the strongest mark available and on the ink panel is INVISIBLE — black
     on black. The accent is what this header already uses to say "this one",
     so the current country is marked the same way the active nav link is.
     Thinned to 1px and softened to 45% of the accent (2026-10-26): at 2px
     full strength the ring read too bright against the panel. */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--yellow-accent) 45%, transparent);
}
/* Shown, never offered: it takes no hover on the row and none on its name
   either, so the one entry that is not a destination never answers a pointer
   like the two that are. */
.country-option.is-current:hover[b-wzrfw57jrj],
.country-option.is-current:focus-visible[b-wzrfw57jrj] {
  background: none;
}
.country-option.is-current:hover .country-name[b-wzrfw57jrj],
.country-option.is-current:focus-visible .country-name[b-wzrfw57jrj] {
  color: var(--glass-bg);
}

/* ---- the flags ---------------------------------------------------------- */

.country-switch[b-wzrfw57jrj]  .country-flag {
  width: 22px;
  height: 15px;
  flex: none;
  border-radius: 2px;
  display: block;
  /* Story 23.12: the flags are real artwork now, and the two files are
     different shapes — Sri Lanka 270×148, Maldives 198×148. `contain` fits each
     inside the same box instead of stretching it to fill, because a squashed
     national flag is the same category of wrong the drawn ones were written to
     avoid. The box stays one size so the trigger and the menu rows do not jump
     when the country changes. */
  object-fit: contain;
  /* An edge of its own, because a flag with white in it needs a boundary. ONE
     hairline now, not two: this used to be a dark edge with a light override
     on the trigger, because the flag sat on two different grounds — the dark
     bar behind the trigger and the white panel behind the menu. Both grounds
     are the header's ink now, so the light hairline is simply correct in both
     places and the override that reconciled them is deleted rather than left
     restating what the base rule already says. */
  box-shadow: 0 0 0 1px var(--white-a45);
}

/* There is no placement pair and no visibility rules any more: the header
   renders ONE switch, after the navigation and before the hamburger, at every
   width. The two media queries that used to live here showed one copy and hid
   the other, and their position in this file was load-bearing — a hide rule and
   a layout rule on the same class compile to the same scoped selector, so the
   one written later simply won and the pair had to stay at the foot. Deleting
   the second copy deleted that trap with it. */
/* _content/Odel.Web/Components/Header/Header.razor.rz.scp.css */
.header[b-t9tc69gxqw] {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1050;
  background: var(--ink-a88);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-bottom: 1px solid var(--white-a08);
  height: var(--header-height);
  display: flex;
  align-items: center;
  transition: var(--transition);
}

.inner[b-t9tc69gxqw] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-32);
  width: 100%;
  padding: 0 var(--space-16);
  max-width: 1320px;
  margin: 0 auto;
}
@media (min-width: 768px) {
  .inner[b-t9tc69gxqw] {
    padding: 0 var(--space-24);
  }
}
@media (min-width: 1200px) {
  .inner[b-t9tc69gxqw] {
    padding: 0 var(--space-32);
  }
}

/* The navigation landmark wrapper adds semantics only: the <ul> inside it
   stays a direct flex item of .inner, so nothing moves. */
.primary-nav[b-t9tc69gxqw] {
  display: contents;
}

.brand[b-t9tc69gxqw] {
  flex-shrink: 0;
  line-height: var(--leading-1);
  text-decoration: none;
}

.logo[b-t9tc69gxqw] {
  height: 32px;
  width: auto;
  display: block;
  object-fit: contain;
}
@media (min-width: 992px) {
  .logo[b-t9tc69gxqw] {
    height: 38px;
  }
}

.nav-links[b-t9tc69gxqw] {
  display: none;
  gap: var(--space-26);
  list-style: none;
  margin: 0;
  padding: 0;
  align-items: center;
}
@media (min-width: 992px) {
  .nav-links[b-t9tc69gxqw] {
    display: flex;
    /* THE WHOLE RIGHT-HAND SET, PUSHED RIGHT (Product Owner, 2026-09-27): the
       department links, the search field, the bag and the account control end
       where the country switch begins, instead of sitting beside the brand
       mark with the bar empty between them.

       The auto margin lives HERE rather than on the switch. .inner is a flex
       row with space-between, so whichever item carries the auto margin
       absorbs the free space — put it on the switch and the cluster is left
       stranded against the logo; put it here and the cluster and the switch
       travel right together as one group. CountrySwitch.razor.css drops its
       own auto margin at this same breakpoint for exactly that reason. */
    margin-left: auto;
  }
}

/* Tighten spacing between the top-nav icon buttons (search/cart/account). */
.nav-links li:has(.icon-btn)[b-t9tc69gxqw] {
  margin-left: var(--space-neg-12);
}

.nav-links li a[b-t9tc69gxqw] {
  text-decoration: none;
  color: var(--white-a85);
  font-family: 'Poppins', sans-serif;
  font-weight: 500;
  font-size: var(--text-0-88);
  letter-spacing: 0.4px;
  transition: var(--transition);
  position: relative;
  padding-bottom: var(--space-3);
  white-space: nowrap;
}
.nav-links li a[b-t9tc69gxqw]::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0;
  height: 2px;
  background: var(--yellow-accent);
  transition: var(--transition);
}
.nav-links li a:hover[b-t9tc69gxqw] {
  color: var(--yellow-accent);
}
.nav-links li a:hover[b-t9tc69gxqw]::after {
  width: 100%;
}

/* ===== Inline nav search (desktop) =====
   The shared look of the field, its controls and the panel is in app.css, once.
   What is here is what is genuinely the header's: the dark chrome the field sits
   on, and the stacking context the panel drops out of. ::deep is how a scoped
   stylesheet reaches a child component's markup — the island lives in
   Odel.Web.Client and carries none of this project's scope attribute. */
.search-li[b-t9tc69gxqw] {
  position: relative;
  flex: 1 1 170px;
  max-width: 320px;
  margin-left: var(--space-6);
}
.search-li[b-t9tc69gxqw]  .typeahead-field {
  gap: var(--space-8);
  background: var(--white-a10);
  border: 1px solid var(--white-a16);
  padding: var(--space-7) var(--space-14);
}
.search-li[b-t9tc69gxqw]  .typeahead-field:focus-within {
  background: var(--white-a16);
  border-color: var(--yellow-accent);
  box-shadow: 0 0 0 3px var(--yellow-a15);
}
.search-li[b-t9tc69gxqw]  .typeahead-field i {
  color: var(--white-a75);
  font-size: var(--text-0-85);
}
.search-li[b-t9tc69gxqw]  .typeahead-input {
  color: var(--white);
  font-size: var(--text-0-82);
}
.search-li[b-t9tc69gxqw]  .typeahead-input::placeholder {
  color: var(--white-a55);
}
.search-li[b-t9tc69gxqw]  .typeahead-clear,
.search-li[b-t9tc69gxqw]  .typeahead-submit {
  color: var(--white-a75);
  font-size: var(--text-0-78);
}
.search-li[b-t9tc69gxqw]  .typeahead-clear:hover,
.search-li[b-t9tc69gxqw]  .typeahead-submit:hover {
  color: var(--yellow-accent);
}
.search-li[b-t9tc69gxqw]  .typeahead-panel {
  z-index: 1060;
}

/* The cart and account icons are anchors now, so they pick up the nav link
   rules; the sliding underline belongs to the word links, not to them. */
a.icon-btn[b-t9tc69gxqw] {
  text-decoration: none;
  padding-bottom: 0;
}
a.icon-btn[b-t9tc69gxqw]::after {
  display: none;
}

.icon-btn[b-t9tc69gxqw] {
  background: none;
  border: none;
  color: var(--white-a85);
  font-size: var(--text-1-1);
  cursor: pointer;
  transition: var(--transition);
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.icon-btn:hover[b-t9tc69gxqw] {
  color: var(--yellow-accent);
  background: var(--black-a04);
}

/* Story 16.4: the sign-out button is a <button> rendered by SignOutControl, a
   child component, so this scoped stylesheet only reaches it via ::deep from
   the .account-li it sits in — the plain .icon-btn rule above is scoped to
   Header's own elements and never reaches inside the child.
   button.icon-btn — element-qualified, deliberately — rather than folding
   this into .icon-btn's own selector list: .account-li ::deep .icon-btn
   compiles to .account-li[b-x] .icon-btn, which ALSO matches the account
   <a class="icon-btn"> sitting in the same <li> at higher specificity than
   .nav-links li a, silently bumping the account icon's own computed font-size
   from that rule's 0.88rem to this rule's 1.1rem — a real regression a first
   pass of this story shipped and a review caught. Qualifying with the
   element name makes the selector structurally unable to match an <a> at
   all, so the account and cart anchors keep computing from .nav-links li a
   exactly as before. font-size here is written out to match what those
   anchors actually compute (var(--text-0-88), not this component's own
   var(--text-1-1)) for the same reason: the sign-out button carries no
   .nav-links li a rule of its own to inherit that figure from, since it is
   not an <a>. font/padding/margin/cursor are reset because a <button>
   carries browser chrome an <a> never had to shed. */
.account-li[b-t9tc69gxqw]  button.icon-btn {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: var(--white-a85);
  font-size: var(--text-0-88);
  cursor: pointer;
  transition: var(--transition);
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.account-li[b-t9tc69gxqw]  button.icon-btn:hover {
  color: var(--yellow-accent);
  background: var(--black-a04);
}
.account-li[b-t9tc69gxqw]  button.icon-btn:focus-visible {
  outline: 2px solid var(--yellow-accent);
  outline-offset: 2px;
}

/* The cart badge sits over the cart icon, so its <li> is what it is positioned
   against — the badge is a sibling of the link rather than a child of it
   (Header.razor says why). ::deep is how a scoped stylesheet reaches a child
   component's markup: the badge is rendered by an island in Odel.Web.Client,
   which carries none of this project's scope attribute.

   This replaces .cart-dot, the decorative pip that used to sit here and said
   nothing about the bag.

   ONLY the offsets live here. How the badge looks — its size, colours and how
   it grows with a second digit — is in app.css under .cart-badge .badge,
   because the same island is placed in the bottom nav too and two scoped
   copies of one appearance drift apart. */
.cart-li[b-t9tc69gxqw] {
  position: relative;
}

.cart-li[b-t9tc69gxqw]  .badge {
  top: -4px;
  right: var(--space-neg-2);
}

.hamburger[b-t9tc69gxqw] {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  cursor: pointer;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  transition: var(--transition);
  color: var(--white);
  font-size: var(--text-1-35);
  flex-shrink: 0;
}
.hamburger:hover[b-t9tc69gxqw] {
  background: var(--black-a05);
}
@media (min-width: 992px) {
  .hamburger[b-t9tc69gxqw] {
    display: none;
  }
}

/* ===== Desktop hover dropdowns ===== */
.has-dropdown[b-t9tc69gxqw] {
  position: relative;
}
.has-dropdown > a[b-t9tc69gxqw]::after {
  content: '\f078';
  font-family: 'Font Awesome 6 Free';
  font-weight: 900;
  font-size: var(--text-0-5);
  margin-left: var(--space-5);
  opacity: 0.55;
  vertical-align: middle;
  background: none !important;
  position: static !important;
  width: auto !important;
  height: auto !important;
}
/* data-open is set by app-interop.js on ArrowDown/Space (Story 30-2, Epic 30 / 2026-10-22). */
.has-dropdown:hover .dropdown[b-t9tc69gxqw],
.has-dropdown[data-open] .dropdown[b-t9tc69gxqw] {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateX(-50%) translateY(4px);
  /* Opening flips visibility at once (Epic 30 / 2026-10-22): a fading
     visibility would leave the first link unfocusable for the first frames
     after ArrowDown. Closing keeps the .dropdown transition below. */
  transition: opacity var(--duration-0-25) ease, transform var(--duration-0-25) ease, visibility 0s;
}
/* The links inside a panel inherit `.nav-links li a`'s `transition: all`, and
   with `all` an INHERITED visibility change transitions too: each link stayed
   visibility:hidden for the first frames after the panel opened, so ArrowDown's
   focus() on the first link did nothing (Epic 30 browser pass, 2026-10-22,
   measured with getComputedStyle at the moment of focus). Their hover colours
   still fade; visibility follows the panel at once. */
.nav-links .dropdown a[b-t9tc69gxqw] {
  transition: color var(--duration-0-3) var(--ease-standard), background-color var(--duration-0-3) var(--ease-standard);
}

.dropdown[b-t9tc69gxqw] {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%) translateY(12px);
  min-width: 195px;
  background: var(--ink-a97);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--white-a08);
  border-radius: var(--radius-minimal);
  padding: var(--space-8);
  margin: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--duration-0-25) ease, transform var(--duration-0-25) ease, visibility var(--duration-0-25) ease;
  z-index: 1200;
  box-shadow: 0 14px 34px var(--black-a40);
  /* A tall menu scrolls inside itself instead of running off the bottom of the
     screen (Story 12.11). Measured 2026-09-24 at 1440x900: the Kids mega
     dropdown was 1,338px tall from y=46, so its bottom edge sat at 1,384px —
     484px of it below the fold, unreachable, with no scrollbar to say so. The
     department with the most sub-categories is the one a Shopper could use
     least.

     The 72px allows for the header the menu hangs from plus the 4px hover
     offset and a little air, so the panel stops short of the viewport edge
     rather than flush against it.

     overscroll-behavior stops a flick that reaches the end of the menu from
     carrying on and scrolling the page underneath, which reads as the menu
     closing itself. */
  max-height: calc(100vh - 72px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.dropdown li[b-t9tc69gxqw] {
  width: 100%;
}
.dropdown li a[b-t9tc69gxqw] {
  display: block;
  padding: var(--space-8) var(--space-14);
  font-size: var(--text-0-82);
  font-weight: 400;
  color: var(--glass-bg);
  border-radius: var(--radius-xs);
  white-space: nowrap;
}
.dropdown li a[b-t9tc69gxqw]::after {
  display: none;
}
.dropdown li a:hover[b-t9tc69gxqw] {
  background: var(--white-a08);
  color: var(--yellow-accent);
}

/* ===== Mega dropdown (sub categories + their items) ===== */
.dropdown.mega[b-t9tc69gxqw] {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-6) var(--space-26);
  padding: var(--space-18) var(--space-22);
  width: max-content;
  max-width: min(740px, calc(100vw - 32px));
  min-width: 320px;
}
.mega .mega-col[b-t9tc69gxqw] {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 128px;
  width: auto;
  flex: 0 0 auto;
}
.mega .mega-col-title[b-t9tc69gxqw] {
  display: block;
  padding: var(--space-6) var(--space-10) var(--space-5);
  font-size: var(--text-0-78);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--yellow-accent);
  border-radius: var(--radius-xs);
  white-space: nowrap;
  text-decoration: none;
}
.mega .mega-col-link[b-t9tc69gxqw] {
  display: block;
  padding: var(--space-5) var(--space-10);
  font-size: var(--text-0-8);
  font-weight: 400;
  color: var(--white-a75);
  border-radius: var(--radius-xs);
  white-space: nowrap;
  text-decoration: none;
}
.mega .mega-col-link[b-t9tc69gxqw]::after,
.mega .mega-col-title[b-t9tc69gxqw]::after {
  display: none;
}
.mega .mega-col-link:hover[b-t9tc69gxqw],
.mega .mega-col-title:hover[b-t9tc69gxqw] {
  background: var(--white-a08);
  color: var(--yellow-accent);
}
/* A curated entry the live hierarchy does not carry. It stays in the menu as a
   label — the authored shape is preserved and the shopper is not sent to a
   404 — and it does not respond to the pointer, so it does not read as a link
   that failed to work. */
.mega .mega-col-link.is-unmatched[b-t9tc69gxqw],
.mega .mega-col-title.is-unmatched[b-t9tc69gxqw] {
  color: var(--white-a45);
  cursor: default;
}
.mega .mega-col-link.is-unmatched:hover[b-t9tc69gxqw],
.mega .mega-col-title.is-unmatched:hover[b-t9tc69gxqw] {
  background: none;
  color: var(--white-a45);
}

/* Keep wide panels on-screen for nav links near the viewport edges
   (edge classes are applied by appInterop.fixNavDropdownEdges). */
.dropdown.edge-right[b-t9tc69gxqw] {
  left: auto;
  right: 0;
  transform: translateY(12px);
}
.has-dropdown:hover .dropdown.edge-right[b-t9tc69gxqw],
.has-dropdown[data-open] .dropdown.edge-right[b-t9tc69gxqw] {
  transform: translateY(4px);
}
.dropdown.edge-left[b-t9tc69gxqw] {
  left: 0;
  right: auto;
  transform: translateY(12px);
}
.has-dropdown:hover .dropdown.edge-left[b-t9tc69gxqw],
.has-dropdown[data-open] .dropdown.edge-left[b-t9tc69gxqw] {
  transform: translateY(4px);
}

/* The account slot holds the account icon and, when there is a session to end,
   the sign-out control beside it (Story 5.1). A flex row of its own, so the two
   round buttons sit side by side and the slot keeps the single place in the
   nav's row that the account icon used to hold on its own.

   ::deep, because the control is a child component and carries its own scope
   attribute rather than this stylesheet's. */
.account-li[b-t9tc69gxqw] {
  display: flex;
  align-items: center;
}

.account-li[b-t9tc69gxqw]  .signout {
  display: flex;
  margin: 0;
}

/* A configured department the hierarchy does not carry (Story 28.4): a label,
   not a link to somewhere that is not that department. Same read as an
   unmatched mega-menu entry. */
.nav-links li > span.is-unmatched[b-t9tc69gxqw] {
  color: var(--white-a45);
  font-family: 'Poppins', sans-serif;
  font-weight: 500;
  font-size: var(--text-0-88);
  letter-spacing: 0.4px;
  padding-bottom: var(--space-3);
  white-space: nowrap;
  cursor: default;
}
/* _content/Odel.Web/Components/Info/InfoAccordion.razor.rz.scp.css */
/* One accordion panel (Epic 29). Tokens only. The body is gateway or ported
   HTML, so its elements are reached with ::deep. */

.panel[b-toq3obtwb6] {
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  background: var(--white);
  margin-bottom: var(--space-12);
}

.panel-title[b-toq3obtwb6] {
  cursor: pointer;
  padding: var(--space-16) var(--space-20);
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-1-1);
  font-weight: 600;
  line-height: var(--leading-1-5);
  color: var(--primary-black);
}

.panel[open] > .panel-title[b-toq3obtwb6] {
  border-bottom: 1px solid var(--neutral-200);
}

.panel-title:focus-visible[b-toq3obtwb6] {
  outline: 2px solid var(--yellow-accent);
  outline-offset: 2px;
}

.panel-body[b-toq3obtwb6] {
  padding: var(--space-16) var(--space-20);
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  overflow-wrap: anywhere;
}

.panel-body[b-toq3obtwb6]  p {
  margin-bottom: var(--space-12);
}

.panel-body[b-toq3obtwb6]  ul,
.panel-body[b-toq3obtwb6]  ol {
  padding-left: var(--space-24);
  margin-bottom: var(--space-12);
}

.panel-body[b-toq3obtwb6]  a {
  color: var(--text-dark);
  text-decoration: underline;
}

.panel-body[b-toq3obtwb6]  table {
  max-width: 100%;
  border-collapse: collapse;
  margin-bottom: var(--space-12);
}

.panel-body[b-toq3obtwb6]  th,
.panel-body[b-toq3obtwb6]  td {
  border: 1px solid var(--neutral-200);
  padding: var(--space-8);
  vertical-align: top;
}
/* _content/Odel.Web/Components/Info/InfoPageShell.razor.rz.scp.css */
/* The shared frame of the information pages (Epic 29). Tokens only; one
   centred column that reads at every width, so no breakpoint of its own. */

.info-page[b-skxb4ahf6k] {
  max-width: 960px;
  margin-left: auto;
  margin-right: auto;
  padding-top: var(--space-24);
  padding-bottom: var(--space-40);
}

.info-head[b-skxb4ahf6k] {
  text-align: center;
  margin-bottom: var(--space-32);
}

.info-title[b-skxb4ahf6k] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-1-9);
  line-height: var(--leading-1-2);
  color: var(--primary-black);
  margin-bottom: var(--space-8);
}

.info-strapline[b-skxb4ahf6k] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-95);
  line-height: var(--leading-1-6);
  color: var(--text-muted);
  margin-bottom: 0;
}
/* _content/Odel.Web/Components/Info/InfoUnavailable.razor.rz.scp.css */
.unavailable[b-jzfbgq5doh] {
  border: 1px solid var(--neutral-200);
  border-left-width: 3px;
  background: var(--neutral-100);
  padding: var(--space-20);
}

.unavailable-title[b-jzfbgq5doh] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-1-1);
  font-weight: 600;
  color: var(--primary-black);
  margin-bottom: var(--space-8);
}

.unavailable-body[b-jzfbgq5doh] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: 0;
}

.unavailable-body a[b-jzfbgq5doh] {
  color: var(--text-dark);
  text-decoration: underline;
}
/* _content/Odel.Web/Components/Info/StoreMap.razor.rz.scp.css */
/* Full width, 550px on a desktop (the legacy page's height) and a shorter box on
   a phone, where 550px would be more than a screen of scrolling. */
.map[b-9gz70cowib] {
  width: 100%;
  height: 360px;
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  overflow: hidden;
}

.map iframe[b-9gz70cowib] {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

@media (min-width: 768px) {
  .map[b-9gz70cowib] {
    height: 550px;
  }
}
/* _content/Odel.Web/Components/Listing/AppliedFilters.razor.rz.scp.css */
/* Applied-filter chips (Story 30-10, Epic 30, 2026-10-22). Tokens only. */
.applied-filters[b-ko1ps7chyy] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
  margin: 0 0 var(--space-16);
}
.applied-filter[b-ko1ps7chyy] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  /* Tap target: 32px on a pointer, 48px on a phone (below). Same type size as
     the toolbar beside it (ListingCount). */
  min-height: var(--space-32);
  padding: var(--space-5) var(--space-12);
  border: 1px solid var(--neutral-400);
  border-radius: var(--radius-pill);
  font-size: var(--text-0-8);
  color: var(--text-dark);
  background: var(--white);
  text-decoration: none;
  transition: var(--transition);
}
.applied-filter:hover[b-ko1ps7chyy] {
  border-color: var(--primary-black);
}
.applied-filter:focus-visible[b-ko1ps7chyy],
.applied-filters-clear:focus-visible[b-ko1ps7chyy] {
  outline: 2px solid var(--primary-black);
  outline-offset: 2px;
}
.applied-filters-clear[b-ko1ps7chyy] {
  display: inline-flex;
  align-items: center;
  min-height: var(--space-32);
  font-size: var(--text-0-8);
  color: var(--text-dark);
  text-decoration: underline;
  padding: var(--space-5) var(--space-8);
}
@media (max-width: 767px) {
  .applied-filter[b-ko1ps7chyy],
  .applied-filters-clear[b-ko1ps7chyy] {
    min-height: var(--space-48);
  }
}
/* _content/Odel.Web/Components/Listing/FacetPanel.razor.rz.scp.css */
/* The facet panel's own chrome (Story 2.4): the sidebar on a desktop, a sheet
   on a phone, and the way out of a refinement.

   What the island inside it renders — the form, the sections and the chips —
   is styled in app.css, for the reason the typeahead's rules are: an island
   lives in Odel.Web.Client and carries none of this project's scope attribute,
   so a scoped rule here would never reach it.

   The chip rules that used to be written out twice, in Categories.razor.css and
   again in BrandSpec.razor.css, are gone from both. There is one panel now, so
   there is one set of rules for it. */

.facet-panel[b-bhs0yiqszn] {
  margin: 0 0 var(--space-16);
}
@media (min-width: 992px) {
  .facet-panel[b-bhs0yiqszn] {
    margin: 0;
  }
}

/* ===== The phone trigger ===== */
/* A real button with a real dialog behind it, driven by the generic
   [data-dialog] attributes — never a control that takes focus and does
   nothing. On a desktop there is no sheet to open, so there is no trigger. */
.facet-open[b-bhs0yiqszn] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  width: 100%;
  justify-content: center;
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-300);
  border-radius: var(--radius-minimal);
  padding: var(--space-10) var(--space-16);
  font-family: inherit;
  font-size: var(--text-0-82);
  font-weight: 600;
  cursor: pointer;
}
.facet-open-count[b-bhs0yiqszn] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 var(--space-5);
  border-radius: var(--radius-pill, 999px);
  background: var(--primary-black);
  color: var(--white);
  font-size: var(--text-0-72);
}
@media (min-width: 992px) {
  .facet-open[b-bhs0yiqszn] {
    display: none;
  }
}

/* ===== The sheet ===== */
/* Closed by default on a phone and opened by the dialog driver, which adds
   `open`. On a desktop it is not a sheet at all: it is the sidebar, always
   present, with no backdrop and nothing to dismiss. */
.facet-sheet[b-bhs0yiqszn] {
  position: fixed;
  inset: 0;
  z-index: 1200;
  background: var(--black-a45);
  display: none;
}
.facet-sheet.open[b-bhs0yiqszn] {
  display: block;
}
.facet-sheet-inner[b-bhs0yiqszn] {
  position: absolute;
  inset: auto 0 0 0;
  max-height: 85vh;
  overflow-y: auto;
  background: var(--white);
  border-radius: var(--radius-minimal) var(--radius-minimal) 0 0;
  padding: var(--space-16);
}
@media (min-width: 992px) {
  .facet-sheet[b-bhs0yiqszn],
  .facet-sheet.open[b-bhs0yiqszn] {
    position: static;
    display: block;
    background: none;
    z-index: auto;
    /* The grid item (.facet-panel) now stretches to the row's height, but a
       block child does not inherit a stretched ancestor's height on its own —
       it stays sized to its own content unless told to fill. Without this,
       .facet-sheet (and so .facet-sheet-inner, the sticky element's own
       containing block) stayed exactly as tall as the folded accordion, which
       left the sticky rule below nothing to travel in even after .facet-panel
       itself was fixed. */
    height: 100%;
  }
  /* Only the properties that turn a bottom sheet into a sidebar are reset
     here (four of them, plus the `height: 100%` added above so the sheet
     fills its stretched grid-item parent and gives the sticky rule below
     room to travel). The panel's own surface — its white ground, its padding and
     its rounded corners — is the base rule's and is deliberately NOT undone:
     this block used to set `padding: 0` and `border-radius: 0`, which left the
     desktop filter column a white area with its controls hard against the
     edge. A shopper on a wide screen gets the same framed panel a shopper on a
     phone gets; only its position and its scrolling differ, because only those
     are actually about being a sheet. */
  .facet-sheet-inner[b-bhs0yiqszn] {
    /* STICKY WHILE THE LISTING SCROLLS (Product Owner, 2026-09-25): the panel
       follows the Shopper down a long grid instead of scrolling away after the
       first row, so refining does not mean scrolling back to the top first.

       Sticky rather than fixed, deliberately. Fixed would take the panel out of
       the flow and out of its column, so it would overlap the grid at narrow
       desktop widths and keep its place when the listing ended. Sticky stays
       inside its own grid column and stops at the bottom of it, which is what
       makes it behave when the results are shorter than the viewport.

       It works because .content is a grid whose items stretch, so the column is
       as tall as the row and the panel has somewhere to travel. A future change
       to `align-items: start` on that grid would collapse the column to the
       panel's own height and silently stop this working — the panel would look
       correct and simply never stick.

       DESKTOP ONLY, and it always was: on a phone this element is the bottom
       sheet, already fixed over a backdrop with its own max-height. That is the
       base rule above, and none of this reaches it. */
    position: sticky;
    /* Clear of the fixed header, plus a little air. --header-height is the
       token the body's own padding-top uses, so the two cannot drift. */
    top: calc(var(--header-height) + var(--space-16));
    /* Tall panels scroll inside themselves rather than running off the screen —
       the Brand dimension alone offers 120 values. overscroll-behavior stops a
       flick that reaches the end of the panel carrying on into the page. */
    max-height: calc(100vh - var(--header-height) - var(--space-32));
    overflow-y: auto;
    overscroll-behavior: contain;
    /* The panel is white and so is the page (`body` is --white), so on desktop
       the padding alone buys nothing a shopper can see — it would be a white
       area inside a white area. The sheet does not need this on a phone, where
       it sits over a dimmed backdrop that draws its edge for it. A hairline in
       the existing border token gives the panel an edge without changing the
       white ground that was asked for. */
    border: 1px solid var(--glass-border);
  }
}

.facet-head[b-bhs0yiqszn] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-10);
  margin-bottom: var(--space-12);
}
.facet-heading[b-bhs0yiqszn] {
  font-size: var(--text-0-95);
  font-weight: 700;
  margin: 0;
}
.facet-close[b-bhs0yiqszn] {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: var(--text-1-1);
  line-height: 1;
  padding: var(--space-4);
  cursor: pointer;
}
@media (min-width: 992px) {
  .facet-close[b-bhs0yiqszn] {
    display: none;
  }
}

.facet-clear[b-bhs0yiqszn] {
  display: inline-block;
  margin-bottom: var(--space-12);
  font-size: var(--text-0-78);
  color: var(--text-dark);
  text-decoration: underline;
}
/* _content/Odel.Web/Components/Listing/ListingCount.razor.rz.scp.css */
/* The count is supporting text, not a heading: it sits at the type scale's
   small step in the muted ink. That is the treatment the category and brand
   listings already gave it (.results-count). /search is the surface that
   changed: its count was a SectionHeader Subtitle, so it sat under the h1 in
   the subtitle's type, read "250 products", and stated no position. It now
   sits in the toolbar strip above the results, in this type, and says which
   page of the result set the shopper is on. */
.results-count[b-ksdtzx670j] {
  display: inline-block;
  font-size: var(--text-0-8);
  color: var(--text-muted);
}
/* _content/Odel.Web/Components/Listing/ListingEmptyState.razor.rz.scp.css */
/* An empty state occupies the space the grid would have, so the surface does
   not collapse into a page that looks unfinished. The rhythm is /search's old
   .empty band; the wording is the design, and the way forward is set apart from
   it rather than hidden inside the sentence. */
.empty-state[b-vp2n478b9k] {
  text-align: center;
  padding: var(--space-60) var(--space-20);
}

.lead[b-vp2n478b9k] {
  font-size: var(--text-1);
  color: var(--text-dark);
  font-weight: 600;
  margin: 0 0 var(--space-10);
}

.detail[b-vp2n478b9k],
.ways[b-vp2n478b9k] {
  font-size: var(--text-0-9);
  color: var(--text-muted);
  margin: 0;
}

.detail a[b-vp2n478b9k],
.ways a[b-vp2n478b9k] {
  color: var(--text-dark);
  font-weight: 600;
  text-decoration: underline;
  transition: var(--transition);
}

/* :focus-visible paired with :hover, as the pager does it. These two anchors
   are the whole way forward out of this state and a keyboard shopper reaches
   them by tab, not by pointer. */
.detail a:hover[b-vp2n478b9k],
.detail a:focus-visible[b-vp2n478b9k],
.ways a:hover[b-vp2n478b9k],
.ways a:focus-visible[b-vp2n478b9k] {
  color: var(--gold-dark);
}
/* _content/Odel.Web/Components/Listing/ListingPager.razor.rz.scp.css */
/* Paging is by URL, so these are links, not controls. The pill treatment is
   the one the brand page's .page-item already drew; the centred row with the
   position between the two links is /search's .paging. Both came from the token
   layer and neither changes here. */
.paging[b-woq2rwh8be] {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-16);
  margin-top: var(--space-28);
  font-size: var(--text-0-85);
}

.paging a[b-woq2rwh8be] {
  border: 1px solid var(--neutral-400);
  border-radius: var(--radius-pill);
  padding: var(--space-8) var(--space-16);
  background: var(--white);
  color: var(--text-dark);
  text-decoration: none;
  font-weight: 600;
  transition: var(--transition);
}

.paging a:hover[b-woq2rwh8be],
.paging a:focus-visible[b-woq2rwh8be] {
  background: var(--primary-black);
  border-color: var(--primary-black);
  color: var(--white);
}

/* The position, which is the whole reason this variant won. */
.position[b-woq2rwh8be] {
  color: var(--text-muted);
}
/* _content/Odel.Web/Components/Listing/ListingSearchBox.razor.rz.scp.css */
/* The one in-page search control (Story 11.8). Tokens only, and sized to sit
   comfortably above a grid or a toolbar on every surface it is placed on. */

.listing-search[b-itve44y6zg] {
  margin: 0 0 var(--space-16);
}

.listing-search-label[b-itve44y6zg] {
  display: block;
  margin-bottom: var(--space-6);
  font-size: var(--text-0-78);
  font-weight: 600;
  color: var(--text-dark);
}

.listing-search-row[b-itve44y6zg] {
  display: flex;
  max-width: 420px;
}

.listing-search-input[b-itve44y6zg] {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-300);
  border-right: none;
  border-radius: var(--radius-minimal) 0 0 var(--radius-minimal);
  padding: var(--space-10) var(--space-12);
  font-family: inherit;
  font-size: var(--text-0-82);
}
.listing-search-input[b-itve44y6zg]::placeholder {
  color: var(--text-muted);
}
.listing-search-input:focus-visible[b-itve44y6zg] {
  outline: 3px solid var(--primary-black);
  outline-offset: -3px;
}

.listing-search-submit[b-itve44y6zg] {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  background: var(--primary-black);
  color: var(--white);
  border: 1px solid var(--primary-black);
  border-radius: 0 var(--radius-minimal) var(--radius-minimal) 0;
  cursor: pointer;
}
.listing-search-submit:focus-visible[b-itve44y6zg] {
  outline: 3px solid var(--primary-black);
  outline-offset: 2px;
}
/* _content/Odel.Web/Components/Listing/ListingSortControl.razor.rz.scp.css */
/* The sort control's own chrome (Story 2.5): the toolbar group, and nothing
   inside it.

   What the island renders — the form, the label, the select and the unscripted
   submit — is styled in app.css, for the reason the facet panel's rules are: an
   island lives in Odel.Web.Client and carries none of this project's scope
   attribute, so a scoped rule here would never reach it. */

.listing-sort[b-t3eojrqzpk] {
  display: flex;
  align-items: center;
}

/* Story 30-13: on a phone the filter sheet carries the sort as radios, so the
   standalone select is hidden below 992px, and only where the sheet exists. */
@media (max-width: 991px) {
  .listing-sort[data-in-sheet][b-t3eojrqzpk] {
    display: none;
  }
}
/* _content/Odel.Web/Components/Media/SlotImage.razor.rz.scp.css */
/* The slot image itself. Ratio and `object-fit` are set inline from the
   declared slot so nothing can override them; what is left here is the
   fluid default box and the neutral ground that shows through while an
   image is still loading — and stays visible, at the slot's size, if it
   never arrives. A layout that needs a different box sets one on its own
   element; it does not restate how the image crops. */
.slot-image[b-fna9r3pnpp] {
  display: block;
  width: 100%;
  height: 100%;
  background-color: var(--neutral-100);
}

/* A slot that declares a second, phone ratio (ImageSlotSpec.MobileRatio) gets it
   only inside the same window the <picture> source uses, so the phone artwork and
   its shape switch together. Inline style carries the value as a custom property;
   !important is needed only to outrank the inline desktop aspect-ratio. */
@media (max-width: 767px) {
  .slot-image[style*="--slot-ratio-mobile"][b-fna9r3pnpp] {
    aspect-ratio: var(--slot-ratio-mobile) !important;
  }
}
/* _content/Odel.Web/Components/MobileBottomNav/MobileBottomNav.razor.rz.scp.css */
.nav[b-bqh3p8mty9] {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1060;
  background: var(--glass-bg);
  backdrop-filter: blur(22px);
  -webkit-backdrop-filter: blur(22px);
  border-top: 1px solid var(--glass-border);
  display: flex;
  align-items: center;
  justify-content: space-around;
  padding: var(--space-6) 0 var(--space-8);
  height: var(--bottom-nav-height);
  box-shadow: 0 -4px 20px var(--black-a06);
}
@media (min-width: 992px) {
  .nav[b-bqh3p8mty9] {
    display: none;
  }
}

/* Each destination sits in a slot, and the slot is what the cart badge is
   positioned against — the badge is a sibling of the link rather than a child
   of it (MobileBottomNav.razor says why). The slot shrink-wraps its link, so
   the bar's distribution is unchanged. */
.slot[b-bqh3p8mty9] {
  position: relative;
  display: flex;
}

/* NavLink renders the <a> itself, so it never receives this component's
   scope attribute — every item rule must go through ::deep. */
.nav[b-bqh3p8mty9]  .item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--text-muted);
  transition: var(--transition);
  text-decoration: none;
  padding: var(--space-4) var(--space-10);
  border-radius: var(--radius-xs);
  gap: var(--space-2);
  position: relative;
}
.nav[b-bqh3p8mty9]  .item i {
  font-size: var(--text-1-25);
  transition: var(--transition);
}
.nav[b-bqh3p8mty9]  .item span {
  font-size: var(--text-0-65);
  font-weight: 500;
  letter-spacing: 0.3px;
  font-family: 'Poppins', sans-serif;
}

.nav[b-bqh3p8mty9]  .active {
  /* The current-page colour on the phone's primary navigation. Yellow on the
     glass (effectively white) bar is 1.9:1 — the one item a shopper most
     needs to pick out was the least readable thing in the bar. --gold-dark is
     the same accent family at 5.0:1. */
  color: var(--gold-dark);
}
.nav[b-bqh3p8mty9]  .active i {
  transform: translateY(-2px);
}

/* ONLY the offsets live here. How the badge looks — its size, colours and how
   it grows with a second digit — is in app.css under .cart-badge .badge,
   because the same island is placed in the header too and two scoped copies of
   one appearance drift apart. This one did: it kept a fixed 17px circle that a
   count of ten spilled out of, in a different colour from the header's. */
.nav[b-bqh3p8mty9]  .badge {
  top: 0;
  right: 2px;
}
/* _content/Odel.Web/Components/MobileNav/MobileNav.razor.rz.scp.css */
.overlay[b-zxl07lyngt] {
  position: fixed;
  inset: 0;
  z-index: 1080;
  background: var(--ink-a92);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--duration-0-4) ease, visibility var(--duration-0-4) ease;
  overflow-y: auto;
}

.active[b-zxl07lyngt] {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.close-btn[b-zxl07lyngt] {
  position: absolute;
  top: 16px;
  right: 16px;
  background: var(--white-a10);
  border: none;
  color: var(--white);
  font-size: var(--text-1-4);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  cursor: pointer;
  transition: var(--transition);
  display: flex;
  align-items: center;
  justify-content: center;
}
.close-btn:hover[b-zxl07lyngt] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

.content[b-zxl07lyngt] {
  width: 100%;
  max-width: 440px;
  margin: 0 auto;
  padding: var(--space-64) var(--space-28) var(--space-36);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.overlay-logo[b-zxl07lyngt] {
  height: 30px;
  width: auto;
  margin-bottom: var(--space-22);
}

.nav-list[b-zxl07lyngt] {
  list-style: none;
  padding: 0;
  width: 100%;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.tree-item[b-zxl07lyngt] {
  width: 100%;
}

.tree-parent[b-zxl07lyngt] {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: none;
  border: none;
  border-bottom: 1px solid var(--white-a08);
  color: var(--white);
  font-family: 'Poppins', sans-serif;
  font-weight: 500;
  font-size: var(--text-1-05);
  padding: var(--space-12) var(--space-6);
  cursor: pointer;
}

.tree-chevron[b-zxl07lyngt] {
  transition: transform var(--duration-0-3) ease;
  font-size: var(--text-0-78);
  opacity: 0.65;
}

.open .tree-parent[b-zxl07lyngt] {
  color: var(--yellow-accent);
}
.open .tree-parent .tree-chevron[b-zxl07lyngt] {
  transform: rotate(90deg);
}

.tree-children[b-zxl07lyngt] {
  list-style: none;
  margin: 0;
  padding: 0 0 0 var(--space-14);
  max-height: 0;
  overflow: hidden;
  transition: max-height var(--duration-0-35) ease;
}
.tree-children li a[b-zxl07lyngt] {
  display: block;
  color: var(--white-a62);
  text-decoration: none;
  font-size: var(--text-0-9);
  padding: var(--space-9) var(--space-6);
  border-bottom: 1px solid var(--white-a05);
}
.tree-children li a:hover[b-zxl07lyngt] {
  color: var(--yellow-accent);
}
/* An entry the live hierarchy does not carry. It keeps the menu's shape and
   reads as a label, because sending a shopper to a category that does not
   exist is worse than telling them it is not shoppable right now. */
.tree-children .tree-unmatched[b-zxl07lyngt] {
  display: block;
  color: var(--white-a45);
  font-size: var(--text-0-9);
  padding: var(--space-9) var(--space-6);
  border-bottom: 1px solid var(--white-a05);
  cursor: default;
}
.tree-section .tree-unmatched[b-zxl07lyngt] {
  padding-left: var(--space-14) !important;
}
.open .tree-children[b-zxl07lyngt] {
  /* Generous cap: groups can hold several sections of sub-links. */
  max-height: 1200px;
}

/* Mega-menu section: a small heading over its group of sub-links. */
.tree-section[b-zxl07lyngt] {
  padding-top: var(--space-6);
}
.tree-section + .tree-section[b-zxl07lyngt] {
  margin-top: var(--space-4);
}
.tree-section-title[b-zxl07lyngt] {
  display: block;
  color: var(--yellow-accent);
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-72);
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: var(--space-8) var(--space-6) var(--space-4);
}
.tree-section a[b-zxl07lyngt] {
  padding-left: var(--space-14) !important;
}

.icons-row[b-zxl07lyngt] {
  display: flex;
  gap: var(--space-18);
  justify-content: center;
  margin-top: var(--space-24);
}

.icon-btn[b-zxl07lyngt] {
  background: var(--white-a10);
  border: none;
  text-decoration: none;
  color: var(--white);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  cursor: pointer;
  transition: var(--transition);
  font-size: var(--text-1-1);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.icon-btn:hover[b-zxl07lyngt] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

.cart-dot[b-zxl07lyngt] {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 9px;
  height: 9px;
  background: var(--yellow-accent);
  border-radius: 50%;
  border: 2px solid var(--ink-a90);
}

.footer-links[b-zxl07lyngt] {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-18) var(--space-12);
  margin-top: var(--space-26);
  padding-top: var(--space-22);
  border-top: 1px solid var(--white-a12);
  text-align: left;
}

.col[b-zxl07lyngt] {
  display: flex;
  flex-direction: column;
  gap: var(--space-9);
}
.col a[b-zxl07lyngt] {
  color: var(--white-a70);
  text-decoration: none;
  font-size: var(--text-0-82);
  transition: var(--transition);
}
.col a:hover[b-zxl07lyngt] {
  color: var(--yellow-accent);
}

.col-title[b-zxl07lyngt] {
  color: var(--white);
  font-size: var(--text-0-72);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1px;
  opacity: 0.9;
  margin-bottom: var(--space-2);
}

.social[b-zxl07lyngt] {
  display: flex;
  gap: var(--space-14);
  justify-content: center;
  margin-top: var(--space-22);
}
.social a[b-zxl07lyngt] {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--white-a10);
  color: var(--white);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  transition: var(--transition);
}
.social a:hover[b-zxl07lyngt] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

/* A department with nothing under it is a plain link, or a label where the
   hierarchy does not carry it (Story 28.4). It takes the disclosure button's
   look, minus the pointer affordance a label must not have. */
a.tree-parent-link[b-zxl07lyngt] {
  text-decoration: none;
}
.tree-parent-unmatched[b-zxl07lyngt] {
  color: var(--white-a45);
  cursor: default;
}
/* _content/Odel.Web/Components/Orders/OrderStatusBar.razor.rz.scp.css */
/* The status bar (Story 23.5).

   No fixed widths and no media queries: the bar is a flex row that divides
   whatever it is given, so it works at 390px and at desktop without a
   breakpoint of its own. OrderDetailSurfaceTests asserts the order-detail
   stylesheet carries neither, and this one follows the same rule. */

.order-status-bar[b-z3fi02jkor] {
  display: flex;
  align-items: flex-start;
  gap: var(--space-8);
  list-style: none;
  margin: var(--space-12) 0 0;
  padding: 0;
}

.order-status-step[b-z3fi02jkor] {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
  position: relative;
  text-align: center;
}

/* The connecting line, drawn from each step back towards the previous one so
   the first step has none. It sits behind the dot and is decorative: the list
   semantics already carry the order. */
.order-status-step + .order-status-step[b-z3fi02jkor]::before {
  content: '';
  position: absolute;
  top: 6px;
  right: 50%;
  left: -50%;
  height: 2px;
  background: var(--neutral-200);
  margin: 0 var(--space-8);
}

.order-status-step.reached + .order-status-step.reached[b-z3fi02jkor]::before {
  background: var(--yellow-accent);
}

.order-status-dot[b-z3fi02jkor] {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--neutral-200);
  position: relative;
  z-index: 1;
  flex: none;
}

.order-status-step.reached .order-status-dot[b-z3fi02jkor] {
  background: var(--yellow-accent);
}

.order-status-label[b-z3fi02jkor] {
  font-size: var(--text-0-75);
  /* --gold-dark rather than --yellow-accent: this is TEXT on the page's own
     white, where the brand yellow carries well under the 4.5:1 WCAG 1.4.3
     wants. --gold-dark is the token that exists for exactly this case. The dot
     above is a graphic, not text, and may stay the brand yellow. */
  color: var(--text-muted);
}

.order-status-step.reached .order-status-label[b-z3fi02jkor] {
  color: var(--gold-dark);
  font-weight: 600;
}

/* Cancelled is a single terminal state, not a step in a sequence. */
.order-status-cancelled[b-z3fi02jkor] {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  margin: var(--space-12) 0 0;
}

.order-status-cancelled .order-status-dot[b-z3fi02jkor] {
  background: var(--danger);
}

.order-status-cancelled .order-status-label[b-z3fi02jkor] {
  /* --danger-dark, not --danger: this is TEXT on white, and the brighter token
     falls short of the 4.5:1 WCAG 1.4.3 wants. The dot above is a graphic and
     may use the brighter one. */
  color: var(--danger-dark);
  font-weight: 600;
}
/* _content/Odel.Web/Components/Orders/OrderSummary.razor.rz.scp.css */
/* The order, on both surfaces that render one (Story 7.5). Tokens only, and no
   breakpoint of its own — it is a list of labels and figures and it reads at
   every width without one.

   Deliberately NOT styled as a success. There is no tick, no green panel and
   no celebratory anything: the resolution these pages almost always carry is
   "we cannot confirm this yet", and a surface that looked like a confirmation
   would undo the wording above it. The one state that IS a defect to act on —
   figures that do not sum, an order with nothing on it — is the only thing
   here drawn in the danger tokens.

   These rules moved out of Payment.razor.css unchanged when the markup moved
   into this component. Blazor's scoped CSS is per-component, so they had to
   come with it. */

.order-summary-reference[b-8adxb0l3ea],
.order-summary-figures[b-8adxb0l3ea],
.order-summary-lines[b-8adxb0l3ea] {
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-20);
  margin-bottom: var(--space-24);
}

.order-summary-label[b-8adxb0l3ea] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-75);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-8);
}

/* The reference reads as the thing to keep: large, dark, and selectable as one
   run of text rather than wrapped mid-token on a phone. */
.order-summary-value[b-8adxb0l3ea] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-1-1);
  font-weight: 600;
  color: var(--primary-black);
  overflow-wrap: anywhere;
  margin-bottom: 0;
}

.order-summary-missing[b-8adxb0l3ea] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-muted);
  margin-bottom: 0;
}

.order-summary-list[b-8adxb0l3ea] {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-8);
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  margin-bottom: 0;
}

.order-summary-list dt[b-8adxb0l3ea] {
  color: var(--text-muted);
}

.order-summary-list dd[b-8adxb0l3ea] {
  color: var(--text-dark);
  text-align: right;
  margin-left: 0;
}

.order-summary-mismatch[b-8adxb0l3ea] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  line-height: var(--leading-1-6);
  color: var(--danger);
  margin-top: var(--space-16);
  margin-bottom: 0;
}

.order-summary-line-list[b-8adxb0l3ea] {
  list-style: none;
  padding-left: 0;
  margin-bottom: 0;
}

/* ONE ROW PER LINE, matching the checkout (Product Owner, 2026-10-09: "item
   image is too big get it like the check out, in one row").

   Two things were wrong. The line WRAPPED, so a long product name pushed the
   quantity and total onto rows of their own; and nothing sized the image at
   all, so SlotImage rendered at its slot's natural width and the thumbnail
   dwarfed the text beside it. */
.order-summary-line[b-8adxb0l3ea] {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-12);
  align-items: center;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  color: var(--text-dark);
  padding-top: var(--space-8);
  padding-bottom: var(--space-8);
  border-bottom: 1px solid var(--neutral-100);
}

/* The checkout's own 70px box (OrderLinesPanel.razor.css:60) as the UPPER
   bound, so an order and the checkout it came from show the same product at the
   same size on anything but a narrow phone.

   STATED AS A CLAMP RATHER THAN A WIDTH, and this is a constraint rather than a
   preference: OrderConfirmationSurfaceTests.TheConfirmationsStylesheetsHaveNo
   FixedWidthAndNoBreakpoint holds this stylesheet to NO media query and NO literal
   `width: <number>`, so that the confirmation fits a 360px phone by being fluid
   rather than by being told about one. A breakpoint here would have been the
   obvious way to shrink the thumbnail and would have broken that property. The
   clamp shrinks it on a narrow screen with no breakpoint at all.

   ::deep because the <img> belongs to SlotImage, a component below this one,
   and carries no scope of ours. flex: 0 0 so a long product name cannot squeeze
   it into a sliver, and aspect-ratio keeps it square without naming a height.

   NO object-fit HERE: SlotImage applies the declared slot's own crop inline,
   and MerchandisingImageRoutingTests.NoComponentStylesheetRestatesHowAMerchandising
   ImageCrops fails any component stylesheet that restates it. The checkout's own
   line image does not set one either. */
.order-summary-line[b-8adxb0l3ea]  img {
  flex: 0 0 clamp(56px, 18vw, 70px);
  aspect-ratio: 1 / 1;
  border-radius: 8px;
}

/* The name yields rather than the figures: a price broken mid-number is
   unreadable, a shortened product name is still the product. min-width:0 is
   what lets a flex item shrink below its content at all, and the ellipsis then
   appears only when there is genuinely no room -- so this needs no breakpoint
   either. */
.order-summary-line-name[b-8adxb0l3ea] {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.order-summary-line-qty[b-8adxb0l3ea] {
  color: var(--text-muted);
  white-space: nowrap;
  flex-shrink: 0;
}

.order-summary-line-total[b-8adxb0l3ea] {
  font-weight: 600;
  white-space: nowrap;
  flex-shrink: 0;
  margin-left: auto;
}

/* _content/Odel.Web/Components/Orders/OrderView.razor.rz.scp.css */
/* The order as a page shows it (Story 27.3) and as a bill prints (Story 27.4).
   Tokens only. The status styles moved here from Pages/OrderDetail.razor.css when
   the markup moved: Blazor's scoped CSS is per component, so they had to come
   with it.

   The status section is deliberately the same visual language as the order
   summary's sections. It is mostly an admission too: one status the gateway
   worded, and a bar inferred from it. The vocabulary is open (register A-23), so
   no state-derived colour and no state-derived class. */

.order-view-tracking[b-8jkrqgqp09],
.order-view-delivery[b-8jkrqgqp09] {
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-20);
  margin-bottom: var(--space-24);
}

.order-view-label[b-8jkrqgqp09] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-75);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-8);
}

.order-view-state[b-8jkrqgqp09] {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  align-items: baseline;
  font-family: 'Poppins', sans-serif;
  margin-bottom: var(--space-12);
}

.order-view-state-label[b-8jkrqgqp09] {
  font-size: var(--text-0-75);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.order-view-state-value[b-8jkrqgqp09] {
  font-size: var(--text-1);
  line-height: var(--leading-1-4);
  color: var(--primary-black);
}

.order-view-gap-body[b-8jkrqgqp09] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-muted);
  margin-bottom: 0;
}

/* The Shopper's own words, one per line, as they would write them on an
   envelope. */
.order-view-delivery-body[b-8jkrqgqp09] {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: 0;
  overflow-wrap: anywhere;
}

.order-view-delivery-note[b-8jkrqgqp09] {
  color: var(--text-muted);
}

/* The order's date, delivery method and expected delivery: label left, value
   right, the same two-column reading as the order summary's figures (that
   component's own scoped rule cannot reach this markup, so it is repeated here
   on this component's own class). */
.order-view-details[b-8jkrqgqp09] {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-8);
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  margin-bottom: 0;
}

.order-view-details dt[b-8jkrqgqp09] {
  color: var(--text-muted);
}

.order-view-details dd[b-8jkrqgqp09] {
  color: var(--text-dark);
  text-align: right;
  margin-left: 0;
  overflow-wrap: anywhere;
}

.order-view-bill[b-8jkrqgqp09] {
  margin-top: var(--space-24);
  margin-bottom: 0;
}

.order-view-bill-button[b-8jkrqgqp09] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  font-weight: 500;
  padding: var(--space-8) var(--space-16);
  border: 1px solid var(--primary-black);
  background: var(--white);
  color: var(--primary-black);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.order-view-bill-button:hover[b-8jkrqgqp09] {
  background: var(--primary-black);
  color: var(--white);
}

/* The bill's head exists for paper only. */
.order-view-bill-head[b-8jkrqgqp09] {
  display: none;
}

/* THE PRINTED BILL (Story 27.4). The site's own chrome is hidden by the global
   print block in app.css; this lays the order itself out as a clean single
   column: the store, the reference (OrderSummary), the date, the status, the
   delivery address, the lines with quantity and line total, and the figures.
   Black on white, no background images, no line split across a page. Every figure
   is exactly what the gateway stated, through Money.Format. Nothing is computed
   here. */
@media print {
  .order-view-bill-head[b-8jkrqgqp09] {
    display: block;
    border-bottom: 2px solid var(--primary-black);
    padding-bottom: var(--space-12);
    margin-bottom: var(--space-16);
  }

  .order-view-bill-store[b-8jkrqgqp09] {
    font-family: 'Playfair Display', serif;
    font-size: var(--text-1-6);
    font-weight: 700;
    color: var(--primary-black);
    margin: 0;
  }

  .order-view-bill-title[b-8jkrqgqp09],
  .order-view-bill-date[b-8jkrqgqp09] {
    font-family: 'Poppins', sans-serif;
    font-size: var(--text-0-9);
    color: var(--primary-black);
    margin: 0;
  }

  .order-view-tracking[b-8jkrqgqp09],
  .order-view-delivery[b-8jkrqgqp09] {
    border-top: 1px solid var(--primary-black);
    break-inside: avoid;
  }

  .order-view-label[b-8jkrqgqp09],
  .order-view-state-label[b-8jkrqgqp09],
  .order-view-state-value[b-8jkrqgqp09],
  .order-view-gap-body[b-8jkrqgqp09],
  .order-view-delivery-body[b-8jkrqgqp09],
  .order-view-delivery-note[b-8jkrqgqp09],
  .order-view-details dt[b-8jkrqgqp09],
  .order-view-details dd[b-8jkrqgqp09] {
    color: var(--primary-black);
  }

  .order-view-bill[b-8jkrqgqp09] {
    display: none;
  }
}

/* The order summary's own sections, reached with ::deep because their markup and
   their scoped styles live in OrderSummary. They are print rules and not a
   breakpoint; they live HERE, beside the rest of the bill, because
   OrderSummary.razor.css is held to having no @media rule at all
   (OrderConfirmationSurfaceTests). Black on white, a line is never split across
   a page, and the figures print exactly as the screen states them. */
@media print {
  .order-view[b-8jkrqgqp09]  .order-summary-reference,
  .order-view[b-8jkrqgqp09]  .order-summary-figures,
  .order-view[b-8jkrqgqp09]  .order-summary-lines {
    border-top: 1px solid var(--primary-black);
    color: var(--primary-black);
  }

  .order-view[b-8jkrqgqp09]  .order-summary-label,
  .order-view[b-8jkrqgqp09]  .order-summary-value,
  .order-view[b-8jkrqgqp09]  .order-summary-list,
  .order-view[b-8jkrqgqp09]  .order-summary-missing {
    color: var(--primary-black);
  }

  .order-view[b-8jkrqgqp09]  .order-summary-line {
    break-inside: avoid;
    background: none;
    color: var(--primary-black);
  }

  .order-view[b-8jkrqgqp09]  .order-summary-line-image {
    display: none;
  }
}
/* _content/Odel.Web/Components/Orders/ShippingCarrierBlock.razor.rz.scp.css */
/* The shipping company block (Story 26.10). Tokens only, no breakpoint of its
   own: a label and a value per row reads at every width. */

.shipping-carrier[b-bmhgplhcqc] {
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-20);
  margin-top: var(--space-20);
  margin-bottom: var(--space-24);
}

.shipping-carrier-heading[b-bmhgplhcqc] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-75);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 var(--space-12);
}

.shipping-carrier-list[b-bmhgplhcqc] {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-8) var(--space-16);
  margin: 0;
}

.shipping-carrier-list dt[b-bmhgplhcqc] {
  color: var(--text-muted);
}

.shipping-carrier-list dd[b-bmhgplhcqc] {
  margin: 0;
  overflow-wrap: anywhere;
}

/* Story 30-22 (PO 2026-10-22): the checkout's collapsed form. A native details,
   closed by default; the summary reads as a text link with a chevron. */
.shipping-carrier-more[b-bmhgplhcqc] {
  margin-top: var(--space-8);
}

.shipping-carrier-more > summary[b-bmhgplhcqc] {
  /* flex + fit-content, not inline-flex: inline-flex on a summary loses its
     disclosure behaviour in Safari. */
  display: flex;
  width: fit-content;
  align-items: center;
  gap: var(--space-8);
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  color: var(--text-dark);
  font-weight: 600;
  text-decoration: underline;
}

.shipping-carrier-more > summary[b-bmhgplhcqc]::-webkit-details-marker {
  display: none;
}

.shipping-carrier-more > summary[b-bmhgplhcqc]::after {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--duration-0-3) var(--ease-standard);
}

.shipping-carrier-more[open] > summary[b-bmhgplhcqc]::after {
  transform: rotate(-135deg);
}

.shipping-carrier-more > summary:focus-visible[b-bmhgplhcqc] {
  outline: 2px solid var(--primary-black);
  outline-offset: 2px;
}

.shipping-carrier-more > .shipping-carrier-list[b-bmhgplhcqc] {
  margin-top: var(--space-8);
}
/* _content/Odel.Web/Components/PromoPopup/PromoPopup.razor.rz.scp.css */
.overlay[b-cui747db2n] {
  position: fixed;
  inset: 0;
  z-index: 2000;
  background: var(--ink-a60);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-20);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-0-35) ease, visibility var(--duration-0-35) ease;
}

.show[b-cui747db2n] {
  opacity: 1;
  visibility: visible;
}
.show .popup[b-cui747db2n] {
  transform: scale(1) translateY(0);
}

.popup[b-cui747db2n] {
  position: relative;
  width: 100%;
  max-width: 740px;
  background: var(--white);
  border-radius: 14px;
  overflow: hidden;
  display: flex;
  box-shadow: 0 30px 70px var(--black-a45);
  transform: scale(0.92) translateY(12px);
  transition: transform var(--duration-0-35) ease;
}
@media (max-width: 575px) {
  .popup[b-cui747db2n] {
    flex-direction: column;
    max-width: 360px;
  }
}

/* ::deep — the illustration is rendered by SlotImage and carries that
   component's scope attribute, not this one's. The popup gives it a column
   width; the slot decides how the image behaves inside it. */
.popup[b-cui747db2n]  .img {
  width: 45%;
}
@media (max-width: 575px) {
  .popup[b-cui747db2n]  .img {
    width: 100%;
    height: 170px;
  }
}

.body[b-cui747db2n] {
  flex: 1;
  padding: var(--space-34) var(--space-30);
}
@media (max-width: 575px) {
  .body[b-cui747db2n] {
    padding: var(--space-24) var(--space-22);
  }
}
.body h2[b-cui747db2n] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-1-7);
  margin: var(--space-12) 0 var(--space-10);
  color: var(--primary-black);
}
@media (max-width: 575px) {
  .body h2[b-cui747db2n] {
    font-size: var(--text-1-45);
  }
}
.body p[b-cui747db2n] {
  color: var(--gray-700);
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  margin-bottom: var(--space-18);
}

.tag[b-cui747db2n] {
  display: inline-block;
  background: var(--yellow-accent);
  color: var(--primary-black);
  font-size: var(--text-0-68);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: var(--space-4) var(--space-12);
  border-radius: 20px;
}

.btn[b-cui747db2n] {
  display: inline-block;
  background: var(--primary-black);
  color: var(--white);
  text-decoration: none;
  padding: var(--space-11) var(--space-24);
  border-radius: 25px;
  font-size: var(--text-0-82);
  font-weight: 600;
  transition: var(--transition);
}
.btn:hover[b-cui747db2n] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

.close[b-cui747db2n] {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 3;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: none;
  background: var(--white-a92);
  color: var(--ink-900);
  cursor: pointer;
  font-size: var(--text-1-05);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: var(--transition);
}
.close:hover[b-cui747db2n] {
  background: var(--primary-black);
  color: var(--white);
}
/* _content/Odel.Web/Components/Toast/Toast.razor.rz.scp.css */
/* THE TOAST REGION (Product Owner, 2026-09-25).

   It replaced the prototype's single centred pill, which was positioned
   fixed, hidden at opacity 0 and revealed by a class a client-side service
   set — a service nothing on this storefront ever called. The region below
   holds a STACK, because more than one thing can be true at once: a checkout
   can fail its pickup check and also have an unserviceable address, and one
   toast overwriting the other would lose a message the Shopper needs.

   The region itself is inert when empty: no background, no box, and
   pointer-events are off so it never sits invisibly over the page's own
   controls. Each toast turns pointer-events back on for itself, which is
   what makes the dismiss button clickable without the empty region
   swallowing clicks on the page beneath it. */
.toast-region[b-avclwce7ih] {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 90px;
  /* Above the cart drawer (z 2100, Story 30-17): a removal made inside the drawer
     parks its toast for the reload, and it must be readable over the open drawer. */
  z-index: 2200;
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  align-items: center;
  width: calc(100vw - 32px);
  max-width: 520px;
  pointer-events: none;
}

/* Above the phone the bottom navigation is gone, so the stack can sit lower. */
@media (min-width: 992px) {
  .toast-region[b-avclwce7ih] {
    bottom: 30px;
  }
}

/* ---- everything below needs ::deep, and the reason is the whole bug ----
   Blazor scopes a component stylesheet by appending that component's own
   [b-xxxx] attribute to every selector in it. A toast is NOT rendered by this
   component: ToastMessage projects it in through a section outlet and carries
   ITS scope id, and the cart island's script builds one with no scope id at
   all. So a plain '.toast-item' rule here compiles to '.toast-item[b-of-this-
   component]' and matches neither.

   It was written that way first and the toasts rendered with NO STYLING: not
   fixed, not stacked, in normal document flow at the bottom of the page, where
   the footer painted straight over them and Chromium refused to click the
   dismiss button because the footer intercepted the pointer. Nothing in the
   served HTML was wrong, every test passed, and only a real browser showed it.

   '::deep' compiles to a descendant selector with the scope on the ANCESTOR —
   '.toast-region[b-xxxx] .toast-item' — which matches a child whatever
   rendered it. That is what makes one stylesheet cover both origins. */

/* ---- one toast --------------------------------------------------------- */

/* Rendered by the server, so it is VISIBLE AS IT ARRIVES rather than starting
   at opacity 0 and waiting for script to reveal it. That inversion is the
   whole accessibility argument for this component: with no JavaScript the
   Shopper still reads every message, and script's only job is to take them
   away again. The .toast-leaving class below is what the script adds. */
.toast-region[b-avclwce7ih]  .toast-item {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-10);
  width: 100%;
  background: var(--primary-black);
  color: var(--white);
  padding: var(--space-12) var(--space-16);
  border-radius: var(--radius-minimal);
  font-size: var(--text-0-8);
  font-weight: 500;
  letter-spacing: 0.3px;
  box-shadow: 0 8px 30px var(--black-a35);
  transition: opacity var(--duration-0-35) ease, transform var(--duration-0-35) ease;
}

.toast-region[b-avclwce7ih]  .toast-leaving {
  opacity: 0;
  transform: translateY(8px);
}

.toast-region[b-avclwce7ih]  .toast-icon {
  flex: none;
  margin-top: var(--space-2);
}

.toast-region[b-avclwce7ih]  .toast-text {
  margin: 0;
  flex: 1 1 auto;
  /* A blocker's sentence is two lines and must wrap rather than be clipped:
     "choose a different delivery city to continue" is the actionable half and
     it is at the end. */
  line-height: var(--leading-1-35);
}

/* Hidden until the script un-hides it — a control that needs JavaScript must
   not be shown to somebody who does not have it. See ToastMessage's header. */
.toast-region[b-avclwce7ih]  .toast-dismiss {
  flex: none;
  background: transparent;
  border: none;
  color: inherit;
  opacity: 0.75;
  cursor: pointer;
  padding: 0 var(--space-4);
  font-size: var(--text-0-9);
  line-height: 1;
}
.toast-region[b-avclwce7ih]  .toast-dismiss:hover {
  opacity: 1;
}
.toast-region[b-avclwce7ih]  .toast-dismiss[hidden] {
  display: none;
}

/* ---- tones ------------------------------------------------------------- */
/* The colour is the SECOND signal, never the only one: each tone also carries
   its own icon and its own words, so a Shopper who cannot distinguish the
   colours still gets the distinction (WCAG 1.4.1). */

.toast-region[b-avclwce7ih]  .toast-success .toast-icon {
  color: var(--success);
}

.toast-region[b-avclwce7ih]  .toast-warning {
  background: var(--primary-black);
}
.toast-region[b-avclwce7ih]  .toast-warning .toast-icon {
  color: var(--yellow-accent);
}

/* A blocker is the one that does not time out, and it reads as the serious
   one: the Shopper's order does not go through until they act on it. */
.toast-region[b-avclwce7ih]  .toast-blocker,
.toast-region[b-avclwce7ih]  .toast-error {
  background: var(--danger);
  color: var(--white);
}
.toast-region[b-avclwce7ih]  .toast-blocker .toast-icon,
.toast-region[b-avclwce7ih]  .toast-error .toast-icon {
  color: var(--white);
}
/* _content/Odel.Web/Layout/MainLayout.razor.rz.scp.css */
/* Demo-mode indicator. Rendered by the layout, so it is present on every page.
   It sticks just below the fixed header and under it in stacking order, so it
   stays visible while scrolling without ever covering the chrome.
   Colours, radius, shadow and motion come from the token layer in css/app.css —
   no literal values here (Story 1.4 owns the tokens themselves). */
.demo-mode-banner[b-q15832izva] {
  position: sticky;
  top: var(--header-height);
  z-index: 1040;
  padding: var(--space-rem-0-5) var(--space-rem-1);
  background-color: var(--yellow-accent);
  color: var(--primary-black);
  border-bottom: 1px solid var(--glass-border);
  box-shadow: var(--shadow-sm);
  font-size: var(--text-0-8125);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-align: center;
}
/* _content/Odel.Web/Pages/Account.razor.rz.scp.css */
/* The card itself is the shared one (`account-card` in wwwroot/css/app.css),
   including the form groups, the labels, the inputs, the hint and the submit
   button — for the reason the change-password page gives: a second spelling of
   the account card is how two account surfaces start to disagree about what an
   account page looks like. What is here is only what this page genuinely has
   that no other account surface does. */

/* Widened for the two-column layout (Story 17.3). The single-column 560px
   card held five fields, a read-only value, a policy hint, a warning and, some
   of the time, an approval pair; two columns side by side now need room for
   that AND a full address book (list, add form, edit form) without either
   squeezing. 1080px is `--container-max` territory rather than a new token —
   it is wide enough that 1fr/1fr at ≥992px gives each column close to the old
   single-column card's own width. */
.account-card .card[b-54q71vysua] {
  max-width: 1080px;
}

/* Top-aligned rather than centred. `.account-card`'s base rule (app.css)
   vertically centres a short sign-in/registration card in the viewport, which
   made sense while this page held one five-field form; it does not once the
   content is two columns tall and the taller of the two (the address book,
   once it holds a few entries plus an open add form) can easily exceed the
   viewport height, at which point "centred" means the top of the page scrolls
   out of view before the shopper has read anything. */
.account-card[b-54q71vysua] {
  align-items: flex-start;
}

/* 1fr on mobile; two even columns at ≥992px, profile left and first in the
   DOM so the reading order and the visual order agree (WCAG 2.4.3) at every
   width — the same pattern Checkout.razor.css's own `.layout` uses, adapted:
   both columns here are account content rather than a form and a fixed-width
   side panel, so there is no 380px column and no need for an `order`
   declaration either (see Checkout.razor.css's own remark on why its absence
   is deliberate — the same reasoning applies here, one column deeper). */
.layout[b-54q71vysua] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-24);
}
@media (min-width: 992px) {
  .layout[b-54q71vysua] {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-32);
    align-items: start;
  }
}

/* The address column's own heading. Not inside AddressBookPanel's scoped
   stylesheet — the <h2> is rendered directly on this page, beside the
   component rather than by it, so the panel's own body starts straight from
   the book or the outage notice on both of its hosts. Matches the sizing
   AddressBookPanel.razor.css gives its own "Change this address" / "Add an
   address" headings, so the column's top-level heading does not read as a
   different scale from the sub-headings under it. */
.col-addresses h2[b-54q71vysua] {
  font-size: var(--text-1);
  margin: 0 0 var(--space-16);
}

/* A value the shopper reads and cannot change. It is a paragraph rather than a
   disabled input, so there is no field on this form for a post to name — and it
   is styled to look unlike the inputs beside it, because an input-shaped thing
   that does not take input is the confusion the stated route exists to end. */
.label[b-54q71vysua] {
  font-size: var(--text-0-8);
  font-weight: 600;
  color: var(--text-dark);
  margin-bottom: var(--space-4);
  display: block;
}

.readonly-value[b-54q71vysua] {
  margin: 0;
  padding: var(--space-11) 0;
  font-size: var(--text-0-85);
  font-weight: 600;
  color: var(--text-dark);
  word-break: break-all;
}

/* The sentence that says the save worked. */
.notice[b-54q71vysua] {
  margin-bottom: var(--space-16);
  padding: var(--space-11) var(--space-14);
  border-radius: 8px;
  border: 1px solid var(--neutral-200);
  background: var(--off-white);
  font-size: var(--text-0-85);
  color: var(--text-dark);
}

/* The loyalty consequence, shown before the change rather than after. Marked
   out from the form because it is the thing the shopper is being asked to
   decide, not another field to fill in. */
.warning[b-54q71vysua] {
  margin-bottom: var(--space-16);
  padding: var(--space-14);
  border-radius: 8px;
  border: 1px solid var(--yellow-accent);
  background: var(--yellow-light, var(--off-white));
}

.warning h2[b-54q71vysua] {
  font-size: var(--text-0-9);
  margin-bottom: var(--space-8);
}

.warning p[b-54q71vysua] {
  margin-bottom: var(--space-8);
}

/* An outstanding date-of-birth request: the value the account holds and the one
   somebody has been asked to approve, both visible, because both are true. */
.pending[b-54q71vysua] {
  display: block;
  margin-top: var(--space-8);
  padding: var(--space-11);
  border-radius: 8px;
  border: 1px dashed var(--neutral-400);
  font-size: var(--text-0-8);
}

.pending-title[b-54q71vysua] {
  display: block;
  font-weight: 700;
  color: var(--text-dark);
  margin-bottom: var(--space-4);
}

.pending-pair[b-54q71vysua] {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-16);
  margin: 0 0 var(--space-4);
}

.pending-pair .was[b-54q71vysua],
.pending-pair .will[b-54q71vysua] {
  font-weight: 600;
  color: var(--text-dark);
}

.pending-note[b-54q71vysua] {
  display: block;
  color: var(--text-muted);
}

/* The way out, on every branch of this page and at every width. It is a block of
   its own rather than a member of .routes above because it renders when that
   block does not: the header's sign-out control is hidden below 992px and the
   phone's bottom nav now links here instead of offering one, so on a phone this
   is the only exit — including on the branch where the profile could not be
   read at all. */
.routes.exit[b-54q71vysua] {
  border-top: 1px solid var(--neutral-200);
  margin-top: var(--space-16);
  padding-top: var(--space-16);
}

/* The ways on from this page, side by side: the password surface when a
   deployment offers it, and signing out. */
.routes[b-54q71vysua] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-16);
}

/* The sign-out button, which belongs to <SignOutControl> and not to this page —
   hence ::deep. A page's scoped stylesheet rewrites its last selector onto this
   component's own elements, and the button is a child component's. */
.routes[b-54q71vysua]  .link-btn {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
  font-weight: 600;
  color: var(--primary-black);
  text-decoration: underline;
}
/* _content/Odel.Web/Pages/Addresses.razor.rz.scp.css */
/* The card itself is the shared one (`account-card` in wwwroot/css/app.css).
   Everything the book needs beyond that — the list, the entry rows, the add
   and edit forms' groups-adjacent chrome, the confirm panel, the notice and
   hint text — moved to AddressBookPanel.razor.css (Story 17.3), alongside the
   markup that now lives in that component rather than in this page. What
   remains here is only what belongs to THIS PAGE's own `.card` element: its
   width. */

/* Wider than a credential form, and for the same reason the account page is:
   this card holds a list of addresses and a seven-field form under it. */
.account-card .card[b-o9mg2n7ol4] {
  max-width: 640px;
}
/* _content/Odel.Web/Pages/Brands.razor.rz.scp.css */
/* The brand index. Tokens only: colour, type, spacing and motion come from
   the token layer, and every media query uses a declared breakpoint. */

.grid[b-xjjdkidpa0] {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-12);
  padding-bottom: var(--space-40);
}
@media (min-width: 576px) {
  .grid[b-xjjdkidpa0] {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 992px) {
  .grid[b-xjjdkidpa0] {
    grid-template-columns: repeat(5, 1fr);
    gap: var(--space-16);
  }
}

.card[b-xjjdkidpa0] {
  display: block;
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-minimal);
  text-decoration: none;
  color: var(--white);
  background: var(--off-white);
  border: 1px solid var(--neutral-200);
  transition: var(--transition);
}
.card[b-xjjdkidpa0]  img {
  transition: var(--transition);
}
.card[b-xjjdkidpa0]::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--black-a05), var(--black-a58));
  z-index: 1;
}
.card span[b-xjjdkidpa0] {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 12px;
  z-index: 2;
  font-size: var(--text-0-8);
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.card:hover[b-xjjdkidpa0] {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  color: var(--white);
  border-color: transparent;
}
.card:hover[b-xjjdkidpa0]  img {
  transform: scale(1.05);
}

.empty[b-xjjdkidpa0] {
  text-align: center;
  padding: var(--space-40) var(--space-20);
  color: var(--text-muted);
}
/* _content/Odel.Web/Pages/BrandSpec.razor.rz.scp.css */
/* The brand page. The prototype's shape is kept — a brand band, a category
   row, then the brand's products with the facet context beside them — while
   every value comes from the token layer and every media query from the
   declared breakpoint set. */

/* ===== The brand band ===== */
.brand-hero[b-ts8ciw1xk4] {
  background: var(--white);
  color: var(--primary-black);
  padding: var(--space-28) 0;
}
.brand-hero-inner[b-ts8ciw1xk4] {
  display: flex;
  align-items: center;
  gap: var(--space-18);
  flex-wrap: wrap;
}
.brand-mark[b-ts8ciw1xk4] {
  width: 84px;
  flex: 0 0 auto;
  border-radius: var(--radius-minimal);
  overflow: hidden;
  background: var(--white);
  /* The band itself is white now (Story 18.4); without an edge of its own
     the white logo box would disappear into it. */
  border: 1px solid var(--neutral-200);
}
/* 18.4 carry-over, review finding #9: SlotImage sets object-fit:cover INLINE
   (by design — see its own remarks, a guideline it enforces everywhere else),
   which crops a wordmark logo rather than showing it whole (e.g. GIORDANO's
   logo read as "ORDAI"). An inline style can only be beaten by `!important`
   in a stylesheet, never by specificity alone, and `::deep` is required
   because SlotImage is a child component — its rendered <img> carries ITS
   OWN scope attribute, not this page's. Padding keeps the now-uncropped
   logo off the box's own edge. */
.brand-mark[b-ts8ciw1xk4]  img {
  object-fit: contain !important;
  padding: var(--space-8);
}
.brand-words[b-ts8ciw1xk4] {
  flex: 1 1 auto;
  min-width: 0;
}
.brand-title[b-ts8ciw1xk4] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-1-9);
  line-height: var(--leading-1-2);
  font-weight: 800;
  margin: var(--space-6) 0 var(--space-8);
}
.brand-blurb[b-ts8ciw1xk4] {
  font-size: var(--text-0-85);
  color: var(--text-muted);
  margin: 0;
}
@media (min-width: 992px) {
  .brand-hero[b-ts8ciw1xk4] {
    padding: var(--space-40) 0;
  }
  .brand-mark[b-ts8ciw1xk4] {
    width: 120px;
  }
  .brand-title[b-ts8ciw1xk4] {
    font-size: var(--text-2-4);
  }
}

/* ===== Category row ===== */
.category-section[b-ts8ciw1xk4] {
  padding: var(--space-24) 0 var(--space-20);
  background: var(--white);
}
.cat-grid[b-ts8ciw1xk4] {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-12);
}
@media (min-width: 576px) {
  .cat-grid[b-ts8ciw1xk4] {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 992px) {
  .cat-grid[b-ts8ciw1xk4] {
    grid-template-columns: repeat(5, 1fr);
    gap: var(--space-16);
  }
}
.cat-card[b-ts8ciw1xk4] {
  display: block;
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-minimal);
  text-decoration: none;
  color: var(--white);
  background: var(--off-white);
  transition: var(--transition);
}
.cat-card[b-ts8ciw1xk4]::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--black-a05), var(--black-a58));
  z-index: 1;
}
.cat-card span[b-ts8ciw1xk4] {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 10px;
  z-index: 2;
  font-size: var(--text-0-78);
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.cat-card:hover[b-ts8ciw1xk4] {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  color: var(--white);
}
/* Story 18.5: the tile for the category this brand page is currently
   refined by (the "All products" header link carries the none-applied state). A ring rather than a colour
   swap, so it reads against every tile image rather than only some. */
.cat-card.is-active[b-ts8ciw1xk4] {
  outline: 2px solid var(--yellow-accent);
  outline-offset: 2px;
}

/* ===== The listing ===== */
.all-section[b-ts8ciw1xk4] {
  padding: var(--space-40) 0 var(--space-50);
  background: var(--off-white);
  /* Review patch P3: a category tile's href jumps to #all-products, and
     without this the fixed header (MainLayout.razor.css) covers the top of
     the section the jump landed on. --header-height plus a small breathing
     token, the same shape Checkout.razor.css already uses for its own
     sticky-under-header offset. */
  scroll-margin-top: calc(var(--header-height) + var(--space-16));
}

.page-content[b-ts8ciw1xk4] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-24);
  padding: var(--space-16) 0 var(--space-40);
}
@media (min-width: 992px) {
  .page-content[b-ts8ciw1xk4] {
    grid-template-columns: 240px 1fr;
    gap: var(--space-30);
  }
}
/* ===== Toolbar ===== */
.toolbar[b-ts8ciw1xk4] {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-16);
  flex-wrap: wrap;
  gap: var(--space-10);
}
/* The pager, the count and the empty state are Components/Listing's, and so
   are their rules (Story 2.1). This page's own .pagination, .page-item,
   .results-count and .empty rules went with the markup they styled — the pill
   treatment survived as the shared pager's. */

/* Uniform mobile page rhythm (matches the home sections' 18px band). */
@media (max-width: 991px) {
  .category-section[b-ts8ciw1xk4] {
    padding: var(--space-18) 0 var(--space-12);
  }
  .all-section[b-ts8ciw1xk4] {
    padding: var(--space-18) 0 var(--space-28);
  }
  .page-content[b-ts8ciw1xk4] {
    padding: var(--space-10) 0 var(--space-28);
  }
}

/* A 1fr grid item does not shrink below its content's natural width on its
   own; the toolbar and the nowrap product-card .name text pushed this column
   past the grid track at narrow widths, past the viewport, past 390px — and
   body's own overflow-x:hidden silently clipped it instead of showing a
   horizontal scrollbar, so the page just looked narrower than it should until
   measured. Search.razor.css carried this fix already; Categories and
   BrandSpec did not. */
.products[b-ts8ciw1xk4] {
  min-width: 0;
}

/* With no facets to offer the panel renders nothing at all, and the grid would
   otherwise keep an empty 240px column beside the results. :only-child is the
   test, because "the panel rendered nothing" and "the products are the only
   child" are the same fact. */
.products:only-child[b-ts8ciw1xk4] {
  grid-column: 1 / -1;
}
/* _content/Odel.Web/Pages/Categories.razor.rz.scp.css */
.inline-search[b-j21fm2ezmn] {
  display: none;
  margin: 0 0 var(--space-14);
}
@media (max-width: 991px) {
  .inline-search[b-j21fm2ezmn] {
    display: block;
  }
}

.search-inner[b-j21fm2ezmn] {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  background: var(--off-white);
  border: 1.5px solid var(--neutral-300);
  border-radius: 25px;
  padding: var(--space-9) var(--space-16);
  transition: var(--transition);
}
.search-inner:focus-within[b-j21fm2ezmn] {
  border-color: var(--yellow-accent);
  box-shadow: 0 0 0 3px var(--yellow-a10);
  background: var(--white);
}
.search-inner i[b-j21fm2ezmn] {
  color: var(--text-muted);
  font-size: var(--text-0-9);
}
.search-inner input[b-j21fm2ezmn] {
  flex: 1;
  border: none;
  background: transparent;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  color: var(--text-dark);
  min-width: 0;
}

.content[b-j21fm2ezmn] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-24);
  padding: var(--space-16) 0 var(--space-40);
}
@media (min-width: 992px) {
  .content[b-j21fm2ezmn] {
    grid-template-columns: 240px 1fr;
    gap: var(--space-30);
  }
}
/* ===== Toolbar ===== */
.toolbar[b-j21fm2ezmn] {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-16);
  flex-wrap: wrap;
  gap: var(--space-10);
}
/* The count and the sort control are both shared components now (Stories 2.1
   and 2.5), and so are their rules. This page styles the strip, not what sits
   in it. */

/* The pager, the count and the empty state are Components/Listing's, and so
   are their rules (Story 2.1). This page's own .pagination, .page-item and
   .results-count rules went with the markup they styled. */

/* Uniform mobile page rhythm. */
@media (max-width: 991px) {
  .content[b-j21fm2ezmn] {
    padding: var(--space-10) 0 var(--space-28);
  }
}

/* A 1fr grid item does not shrink below its content's natural width on its
   own; the toolbar and the nowrap product-card .name text pushed this column
   past the grid track at narrow widths, past the viewport, past 390px — and
   body's own overflow-x:hidden silently clipped it instead of showing a
   horizontal scrollbar, so the page just looked narrower than it should until
   measured. Search.razor.css carried this fix already; Categories and
   BrandSpec did not. */
.products[b-j21fm2ezmn] {
  min-width: 0;
}

/* With no facets to offer the panel renders nothing at all, and the grid would
   otherwise keep an empty 240px column beside the results. :only-child is the
   test, because "the panel rendered nothing" and "the products are the only
   child" are the same fact. */
.products:only-child[b-j21fm2ezmn] {
  grid-column: 1 / -1;
}
/* _content/Odel.Web/Pages/CategoryIndex.razor.rz.scp.css */
/* The category index (Story 11.4). Same rules as Brands.razor.css, which this
   page is deliberately modelled on — a scoped stylesheet cannot be shared
   across two components, so the rules are repeated rather than referenced,
   the same way FacetPanel's header records the alternative (two sidebars
   that drift apart) as the thing to avoid for behaviour, not for a stylesheet
   this small and this static.

   Tokens only: colour, type, spacing and motion come from the token layer,
   and every media query uses a declared breakpoint. */

.grid[b-rjillrvrg7] {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-12);
  padding-bottom: var(--space-40);
}
@media (min-width: 576px) {
  .grid[b-rjillrvrg7] {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 992px) {
  .grid[b-rjillrvrg7] {
    grid-template-columns: repeat(5, 1fr);
    gap: var(--space-16);
  }
}

.card[b-rjillrvrg7] {
  display: block;
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-minimal);
  text-decoration: none;
  color: var(--white);
  background: var(--off-white);
  border: 1px solid var(--neutral-200);
  transition: var(--transition);
}
.card[b-rjillrvrg7]  img {
  transition: var(--transition);
}
.card[b-rjillrvrg7]::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--black-a05), var(--black-a58));
  z-index: 1;
}
.card span[b-rjillrvrg7] {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 12px;
  z-index: 2;
  font-size: var(--text-0-8);
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.card:hover[b-rjillrvrg7] {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  color: var(--white);
  border-color: transparent;
}
.card:hover[b-rjillrvrg7]  img {
  transform: scale(1.05);
}

.empty[b-rjillrvrg7] {
  text-align: center;
  padding: var(--space-40) var(--space-20);
  color: var(--text-muted);
}

/* The department chooser (Story 12.14) — a row of real links rather than a
   <select>, matching Story 11.6's own rule that a control needing script has
   no place on a page rendered once, server-side, from the URL alone. */
.department-chooser[b-rjillrvrg7] {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  padding-bottom: var(--space-20);
}
.department-link[b-rjillrvrg7] {
  display: inline-block;
  padding: var(--space-8) var(--space-16);
  border-radius: var(--radius-minimal);
  border: 1px solid var(--neutral-200);
  color: var(--text-dark);
  text-decoration: none;
  font-size: var(--text-0-8);
  font-weight: 600;
  transition: var(--transition);
}
.department-link:hover[b-rjillrvrg7] {
  border-color: var(--neutral-400);
}
.department-link[aria-current='true'][b-rjillrvrg7] {
  background: var(--text-dark);
  border-color: var(--text-dark);
  color: var(--white);
}
/* _content/Odel.Web/Pages/CatFull.razor.rz.scp.css */
.search-wrapper[b-ui2wywifsn] {
  max-width: 600px;
  margin: 0 auto var(--space-24);
  position: relative;
}
.search-wrapper i[b-ui2wywifsn] {
  position: absolute;
  top: 50%;
  left: 18px;
  transform: translateY(-50%);
  color: var(--gray-375);
}
.search-wrapper input[b-ui2wywifsn] {
  width: 100%;
  padding: var(--space-14) var(--space-18) var(--space-14) var(--space-48);
  border: 2px solid var(--neutral-200);
  border-radius: 40px;
  font-size: var(--text-0-9);
  font-weight: 500;
  transition: var(--transition);
  background: var(--white);
}
.search-wrapper input:focus[b-ui2wywifsn] {
  border-color: var(--yellow-accent);
  box-shadow: 0 0 0 4px var(--yellow-a10);
}

.grid[b-ui2wywifsn] {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-12);
  padding-bottom: var(--space-40);
}
@media (min-width: 576px) {
  .grid[b-ui2wywifsn] {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 992px) {
  .grid[b-ui2wywifsn] {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-16);
  }
}

.card[b-ui2wywifsn] {
  display: block;
  position: relative;
  overflow: hidden;
  min-height: 180px;
  border-radius: var(--radius-minimal);
  text-decoration: none;
  color: var(--white);
  background: var(--off-white);
  transition: var(--transition);
}
/* The tile takes its shape from the category-tile slot's declared 1:1 ratio;
   the card only sets a floor so a short grid row still reads as a tile. */
.card[b-ui2wywifsn]  img {
  transition: var(--transition);
  filter: saturate(0.92);
}
.card[b-ui2wywifsn]::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--black-a05), var(--black-a58));
  z-index: 1;
}
.card span[b-ui2wywifsn] {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 12px;
  z-index: 2;
  font-size: var(--text-0-9);
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.card:hover[b-ui2wywifsn] {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  color: var(--white);
}
.card:hover[b-ui2wywifsn]  img {
  transform: scale(1.05);
}

.no-results[b-ui2wywifsn] {
  text-align: center;
  padding: var(--space-40) var(--space-20);
  color: var(--text-muted);
}
/* _content/Odel.Web/Pages/ChangePassword.razor.rz.scp.css */
/* The card itself is the shared one (`account-card` in wwwroot/css/app.css),
   including the hint that states the password policy — which is shared because
   the register and recovery forms state the same rule in the same place. This
   page adds no look of its own: a third spelling of the account card is how the
   three surfaces would start to disagree about what an account page looks
   like. The file exists so that anything genuinely this page's own has an
   obvious home rather than reaching for the shared sheet. */
/* _content/Odel.Web/Pages/Checkout.razor.rz.scp.css */
.layout[b-zfeoqf1tuw] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-24);
  padding: var(--space-24) 0 var(--space-40);
}
@media (min-width: 992px) {
  /* The delivery and order column takes the room; the payment side panel keeps
     its 380px, ON THE RIGHT (Product Owner, 2026-09-25).

     IT WAS BRIEFLY ON THE LEFT, and moving it back is worth a sentence for what
     came with it. Putting the panel first visually meant reordering the grid
     with `order`, because the delivery form comes first in the markup and
     several hundred lines of conditional markup were not worth moving. That
     works for the eye and for nothing else: keyboard focus and screen readers
     follow the DOM, so a Shopper tabbing through met the delivery form, then
     the order, then payment — while the eye read payment first on the left. A
     visual order that contradicts the focus order is WCAG 2.4.3s concern, and
     it was the price of the arrangement.

     With the panel back on the right the two agree again: the form is first in
     the markup and first on the screen, and no `order` declaration is needed at
     all. Its absence below is deliberate. */
  .layout[b-zfeoqf1tuw] {
    grid-template-columns: 1fr 380px;
    gap: var(--space-32);
  }
}

.section[b-zfeoqf1tuw] {
  background: var(--white);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-20);
  margin-bottom: var(--space-20);
  transition: var(--transition);
}
.section h4[b-zfeoqf1tuw] {
  font-size: var(--text-0-9);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  margin-bottom: var(--space-12);
  color: var(--primary-black);
  display: flex;
  align-items: center;
  gap: var(--space-8);
}
.section h4 i[b-zfeoqf1tuw] {
  color: var(--yellow-accent);
}
.section p[b-zfeoqf1tuw] {
  font-size: var(--text-0-8);
  color: var(--text-muted);
  margin-bottom: var(--space-12);
}

.group[b-zfeoqf1tuw] {
  margin-bottom: var(--space-14);
}
.group label[b-zfeoqf1tuw] {
  font-weight: 600;
  font-size: var(--text-0-82);
  margin-bottom: var(--space-4);
  color: var(--text-dark);
  display: block;
}

.control[b-zfeoqf1tuw] {
  border: 1px solid var(--neutral-400);
  border-radius: var(--radius-sm);
  padding: var(--space-10) var(--space-14);
  font-size: var(--text-0-85);
  background: var(--off-white);
  transition: var(--transition);
  width: 100%;
  font-family: inherit;
}
.control:focus[b-zfeoqf1tuw] {
  border-color: var(--yellow-accent);
  box-shadow: 0 0 0 3px var(--yellow-a10);
  background: var(--white);
}

textarea.control[b-zfeoqf1tuw] {
  min-height: 80px;
}

/* Validation states */
.control.invalid[b-zfeoqf1tuw] {
  border-color: var(--danger);
  background: var(--danger-bg);
}
.control.invalid:focus[b-zfeoqf1tuw] {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-a12);
}
.field-error[b-zfeoqf1tuw] {
  color: var(--danger);
  font-size: var(--text-0-72);
  font-weight: 600;
  margin-top: var(--space-5);
}

.hint[b-zfeoqf1tuw] {
  font-size: var(--text-0-72);
  color: var(--text-muted);
}

.radio-group[b-zfeoqf1tuw] {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}
.radio-option[b-zfeoqf1tuw] {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-12) var(--space-14);
  cursor: pointer;
  transition: var(--transition);
  background: var(--white);
}
.radio-option:hover[b-zfeoqf1tuw] {
  border-color: var(--yellow-accent);
}
.radio-check[b-zfeoqf1tuw] {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid var(--gray-300);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: var(--transition);
  flex-shrink: 0;
}
.radio-label[b-zfeoqf1tuw] {
  font-weight: 500;
  font-size: var(--text-0-85);
  flex: 1;
  display: block;
}
.radio-desc[b-zfeoqf1tuw] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
  display: block;
}
.selected.radio-option[b-zfeoqf1tuw] {
  border-color: var(--primary-black);
  background: var(--neutral-50);
}
.selected .radio-check[b-zfeoqf1tuw] {
  border-color: var(--primary-black);
  background: var(--primary-black);
}
.selected .radio-check[b-zfeoqf1tuw]::after {
  content: '';
  width: 8px;
  height: 8px;
  background: var(--white);
  border-radius: 50%;
}

.payment-grid[b-zfeoqf1tuw] {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  gap: var(--space-8);
  margin-top: var(--space-8);
}
.payment-card[b-zfeoqf1tuw] {
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-12) var(--space-8);
  text-align: center;
  cursor: pointer;
  transition: var(--transition);
  background: var(--white);
  font-weight: 600;
  font-size: var(--text-0-8);
  color: var(--text-dark);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
}
.payment-card i[b-zfeoqf1tuw] {
  font-size: var(--text-1-6);
  color: var(--primary-black);
  margin-bottom: var(--space-4);
}
.payment-card:hover[b-zfeoqf1tuw] {
  border-color: var(--yellow-accent);
}
.payment-card.selected[b-zfeoqf1tuw] {
  border-color: var(--primary-black);
  background: var(--neutral-50);
}

.promo-row[b-zfeoqf1tuw] {
  display: flex;
  gap: var(--space-8);
}
.promo-row input[b-zfeoqf1tuw] {
  flex: 1;
}
.promo-row button[b-zfeoqf1tuw] {
  white-space: nowrap;
  background: var(--primary-black);
  color: var(--white);
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--space-10) var(--space-18);
  font-weight: 600;
  font-size: var(--text-0-85);
  cursor: pointer;
  transition: var(--transition);
}
.promo-row button:hover[b-zfeoqf1tuw] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

/* ===== The side panel: payment, promo code, vouchers, loyalty, submit =====
   Repurposed from "Order summary" (Product Owner, 2026-09-25): "get the
   payment method selection top of the left column ... after payment method
   put promocode input and the button below." The card that used to hold the
   order lines and the charge breakdown now holds the controls that decide
   what is paid and how much of it, in that order; the order lines and the
   breakdown moved to .checkout-order-summary, inside the OTHER column, below
   the delivery form (see Checkout.razor for why). */
.checkout-side-panel[b-zfeoqf1tuw] {
  background: var(--white);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-20);
}

/* STICKY, AND ONLY ON DESKTOP (Product Owner, 2026-09-25): "that left column
   should be fixed to the screen desktop like sticky with the scroll down."
   Declared inside the breakpoint rather than declared-and-undone outside it —
   the same rule the delivery form's own field-row grid follows below — so a
   phone gets a panel that scrolls with the rest of the page, never one
   pinned over content it has no screen to spare for.

   top ACCOUNTS FOR THE FIXED HEADER: the offset is the header's own height
   plus a token of clearance, so the panel settles just under the header
   rather than beneath it. The step bar (--checkout-steps-height) shows only
   below 768px (Story 30-21), where this panel is not sticky, so it adds
   nothing to the offset here.

   align-self: start IS WHAT MAKES STICKY WORK HERE. The grid's default for
   an item is stretch, which would size this column to the row's full height
   — exactly as tall as .form, the taller of the two columns — and a box
   already stretched to fill its row has nowhere left to travel as the page
   scrolls, so `position: sticky` would have no visible effect at all.
   `align-self: start` sizes the panel to its OWN content instead, leaving
   room in the still-tall grid row for it to travel through before it meets
   the row's edge and unsticks. */
@media (min-width: 992px) {
  .layout > .checkout-side-panel[b-zfeoqf1tuw] {
    position: sticky;
    top: calc(var(--header-height) + var(--space-16));
    align-self: start;
  }
}

/* ===== Your Order, moved here from the side panel (2026-09-25) ===== */
.checkout-order-summary[b-zfeoqf1tuw] {
  background: var(--white);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-20);
  margin-bottom: var(--space-20);
}
.checkout-order-summary h2[b-zfeoqf1tuw] {
  font-size: var(--text-1-1);
  font-weight: 700;
  margin-bottom: var(--space-12);
}

.count-note[b-zfeoqf1tuw] {
  color: var(--text-muted);
  font-size: var(--text-0-75);
}

/* The order rows' own rules travel with OrderLinesPanel: scoped CSS follows the
   component that renders the markup. */

.summary-line[b-zfeoqf1tuw] {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-0-8);
  margin-bottom: var(--space-10);
  color: var(--text-dark);
}
.total[b-zfeoqf1tuw] {
  font-weight: 800;
  font-size: var(--text-1);
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-12);
  margin-top: var(--space-8);
  color: var(--primary-black);
}
.discount-value[b-zfeoqf1tuw] {
  color: var(--yellow-accent);

/* The subtotal closes the group above it — the gross items figure and the
   discount taken off it — and that group is the only one in this breakdown
   whose rows add up to the row beneath them. Without the rule a Shopper reads
   flat lines and tries to sum all of them against the Total, which is not what
   they mean. The cart states the same thing in the same way, under
   .summary-row.subtotal. */
.summary-line.subtotal {
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-10);
  font-weight: 700;
  color: var(--primary-black);
}
}

.terms-check[b-zfeoqf1tuw] {
  display: flex;
  align-items: flex-start;
  gap: var(--space-8);
  margin: var(--space-16) 0;
  font-size: var(--text-0-78);
  color: var(--text-muted);
}
.terms-check input[b-zfeoqf1tuw] {
  margin-top: var(--space-2);
}
.terms-check a[b-zfeoqf1tuw] {
  color: var(--primary-black);
  font-weight: 600;
}
.terms-check label[b-zfeoqf1tuw] {
  margin: 0;
}

.place-order-btn[b-zfeoqf1tuw] {
  width: 100%;
  background: var(--primary-black);
  color: var(--white);
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--space-16);
  font-weight: 800;
  font-size: var(--text-0-9);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  cursor: pointer;
  transition: var(--transition);
}
.place-order-btn:hover[b-zfeoqf1tuw] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

/* Uniform mobile page rhythm. */
@media (max-width: 991px) {
  .layout[b-zfeoqf1tuw] {
    padding: var(--space-14) 0 var(--space-28);
  }
}

/* ===== Discount tag + pre-discount price on order lines ===== */
.disc-tag[b-zfeoqf1tuw] {
  display: inline-block;
  background: var(--yellow-accent);
  color: var(--primary-black);
  font-size: var(--text-0-6);
  font-weight: 700;
  letter-spacing: 0.6px;
  padding: var(--space-2) var(--space-7);
  border-radius: 15px;
  margin-left: var(--space-6);
  vertical-align: 1px;
  white-space: nowrap;
}
.old-price[b-zfeoqf1tuw] {
  text-decoration: line-through;
  /* WCAG 1.4.3: --gray-400 on white is 2.4:1, --text-muted is 4.8:1. Same
     correction as ProductCard.razor.css and Cart.razor.css. */
  color: var(--text-muted);
  font-size: var(--text-0-7);
  font-weight: 500;
  margin-left: var(--space-6);
}

/* ---- the eligibility gate (Story 6.2) ---------------------------------- */
/* Each unmet condition is its own card, so two of them read as two pieces of
   work rather than as one paragraph. Composed from the design system's own
   tokens: nothing here is a new colour or a new spacing scale. */
.checkout-gate[b-zfeoqf1tuw] {
  padding: var(--space-24) 0 var(--space-40);
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
  align-items: flex-start;
}
.gate-condition[b-zfeoqf1tuw] {
  width: 100%;
  background: var(--white);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-20);
}
.gate-condition-reason[b-zfeoqf1tuw] {
  font-size: var(--text-0-9);
  color: var(--primary-black);
  margin-bottom: var(--space-12);
}
.gate-route[b-zfeoqf1tuw] {
  display: inline-block;
  font-size: var(--text-0-8);
  font-weight: 700;
  text-decoration: underline;
}
/* The two forms' own rules travel with GateVerification: scoped CSS follows
   the component that renders the markup. */

/* ---- delivery details (Story 6.3) --------------------------------------
   The one server-rendered form every Shopper meets on this page. It borrows
   the page's own .group/.hint tokens rather than introducing a second field
   style, so the address inputs and the gate's code input look like one page. */
.delivery-details[b-zfeoqf1tuw] {
  background: var(--white);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-20);
  margin-bottom: var(--space-20);
}
.delivery-details h2[b-zfeoqf1tuw] {
  font-size: var(--text-1-1);
  font-weight: 700;
  margin-bottom: var(--space-12);
}
.delivery-details .notice[b-zfeoqf1tuw] {
  font-size: var(--text-0-85);
  color: var(--text-dark);
  margin-bottom: var(--space-12);
}
.delivery-details input[b-zfeoqf1tuw],
.delivery-details select[b-zfeoqf1tuw],
.delivery-details textarea[b-zfeoqf1tuw] {
  border: 1px solid var(--neutral-400);
  border-radius: var(--radius-sm);
  padding: var(--space-10) var(--space-14);
  font-size: var(--text-0-85);
  background: var(--off-white);
  width: 100%;
  font-family: inherit;
}
.delivery-details input:focus[b-zfeoqf1tuw],
.delivery-details select:focus[b-zfeoqf1tuw],
.delivery-details textarea:focus[b-zfeoqf1tuw] {
  border-color: var(--yellow-accent);
  box-shadow: 0 0 0 3px var(--yellow-a10);
  background: var(--white);
}
.delivery-details input[aria-invalid="true"][b-zfeoqf1tuw],
.delivery-details select[aria-invalid="true"][b-zfeoqf1tuw],
.delivery-details textarea[aria-invalid="true"][b-zfeoqf1tuw] {
  border-color: var(--danger);
  background: var(--danger-bg);
}
.delivery-details .readonly-value[b-zfeoqf1tuw] {
  background: var(--neutral-100);
  color: var(--text-muted);
}
.saved-addresses[b-zfeoqf1tuw] {
  padding-bottom: var(--space-16);
  margin-bottom: var(--space-16);
  border-bottom: 1px solid var(--neutral-200);
}

/* The saved addresses as radio cards (Story 20.2), modelled on
   .checkout-option above: a grid with the radio in its own column, a 10px
   radius and a neutral border that darkens on the chosen card. Their own
   class names, and not the address book's .entry/.book, because scoped
   stylesheets do not reach across components — AddressBookPanel's rules would
   style nothing here. */
.saved-address-list[b-zfeoqf1tuw] {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  margin: 0 0 var(--space-12);
  padding: 0;
  border: 0;
  min-width: 0;
}
.saved-address-list legend[b-zfeoqf1tuw] {
  font-weight: 600;
  font-size: var(--text-0-85);
  color: var(--text-dark);
  padding: 0;
  margin-bottom: var(--space-8);
}
.saved-address-card[b-zfeoqf1tuw] {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: var(--space-12);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-12) var(--space-14);
  background: var(--white);
  transition: var(--transition);
}
.saved-address-card:hover[b-zfeoqf1tuw] {
  border-color: var(--yellow-accent);
}
/* The chosen card reads as chosen without relying on the radio dot alone. */
.saved-address-card:has(input[type="radio"]:checked)[b-zfeoqf1tuw] {
  border-color: var(--primary-black);
  background: var(--neutral-50);
}
.saved-address-card input[type="radio"][b-zfeoqf1tuw] {
  width: 18px;
  height: 18px;
  accent-color: var(--primary-black);
  cursor: pointer;
  /* Nudged down onto the label's first line, through the scale rather than a
     raw pixel: DesignTokenLayerTests holds component stylesheets to the
     tokens, and --space-2 IS 2px. */
  margin: var(--space-2) 0 0;
}
.saved-address-body[b-zfeoqf1tuw] {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  cursor: pointer;
  margin: 0;
}
.saved-address-label[b-zfeoqf1tuw] {
  font-weight: 600;
  font-size: var(--text-0-85);
  color: var(--text-dark);
}
.saved-address-recipient[b-zfeoqf1tuw],
.saved-address-line[b-zfeoqf1tuw],
.saved-address-mobile[b-zfeoqf1tuw] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
}

/* The "Deliver to this address" button is not the primary action on this form —
   saving is — so it wears the same quieter treatment VerifyMobile gives its
   secondary control. Without this rule the class does nothing here: scoped
   stylesheets do not reach across components, so it rendered as a second
   primary button competing with Save. */
.submit-btn.secondary[b-zfeoqf1tuw] {
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-400);
}
.submit-btn.secondary:hover[b-zfeoqf1tuw] {
  background: var(--off-white);
  color: var(--primary-black);
}

/* ---- choosing a delivery option (Story 6.4) -----------------------------
   The options moved onto this page from the island, which never styled them:
   a raw run of radios sat beside the fully styled address card. They are drawn
   here as the page's own .radio-group/.radio-option pattern — the same card,
   the same border, the same hover — rather than as a third field style, and
   the section itself matches .delivery-details so the two read as one column. */
.delivery-options[b-zfeoqf1tuw] {
  background: var(--white);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-20);
  margin-bottom: var(--space-20);
}
.delivery-options h2[b-zfeoqf1tuw] {
  font-size: var(--text-1-1);
  font-weight: 700;
  margin-bottom: var(--space-12);
  display: flex;
  align-items: center;
  gap: var(--space-8);
}
.delivery-options h2 i[b-zfeoqf1tuw] {
  color: var(--yellow-accent);
}
.delivery-options .notice[b-zfeoqf1tuw] {
  font-size: var(--text-0-85);
  color: var(--text-dark);
  margin-bottom: var(--space-12);
}
.delivery-options .hint[b-zfeoqf1tuw] {
  margin-bottom: var(--space-12);
}

/* The group: one choice with several arms, spaced the way .radio-group is. */
.checkout-options[b-zfeoqf1tuw] {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  margin-bottom: var(--space-14);
}

/* One arm. A grid rather than a row, because an option carries a name, a
   charge and up to three sentences underneath them — the charge stays on the
   name's line and the notes run the full width beneath both. */
.checkout-option[b-zfeoqf1tuw] {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-4) var(--space-12);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  padding: var(--space-12) var(--space-14);
  background: var(--white);
  transition: var(--transition);
}
.checkout-option:hover[b-zfeoqf1tuw] {
  border-color: var(--yellow-accent);
}
.checkout-option[data-delivery-chosen="true"][b-zfeoqf1tuw] {
  border-color: var(--primary-black);
  background: var(--neutral-50);
}
/* Story 26.10: a lone available option has no radio; its tick stands where the
   radio would, in the same column. */
.checkout-option .fa-circle-check[b-zfeoqf1tuw] {
  color: var(--primary-black);
  font-size: var(--text-1-1);
}
.checkout-option input[type="radio"][b-zfeoqf1tuw] {
  width: 18px;
  height: 18px;
  accent-color: var(--primary-black);
  cursor: pointer;
  margin: 0;
}
.checkout-option-label[b-zfeoqf1tuw] {
  font-weight: 500;
  font-size: var(--text-0-85);
  color: var(--text-dark);
  cursor: pointer;
  margin: 0;
}
.checkout-option-charge[b-zfeoqf1tuw] {
  font-weight: 700;
  font-size: var(--text-0-85);
  color: var(--primary-black);
  white-space: nowrap;
  justify-self: end;
}

/* The sentences the gateway wrote about this option, under the name and
   spanning the charge column so a long one does not squeeze the figure. */
.checkout-option-notes[b-zfeoqf1tuw] {
  grid-column: 2 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.checkout-option-timing[b-zfeoqf1tuw],
.checkout-option-info[b-zfeoqf1tuw] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
}

/* An option that cannot be taken is SHOWN with its reason — never hidden — so
   the reason has to read as the reason and not as another note. */
.checkout-option-unavailable[b-zfeoqf1tuw] {
  font-size: var(--text-0-72);
  font-weight: 600;
  color: var(--danger);
}
.checkout-option[data-delivery-available="false"][b-zfeoqf1tuw] {
  background: var(--neutral-50);
}
.checkout-option[data-delivery-available="false"] .checkout-option-label[b-zfeoqf1tuw],
.checkout-option[data-delivery-available="false"] .checkout-option-charge[b-zfeoqf1tuw] {
  color: var(--text-muted);
}

/* A statement about the whole control rather than about one option — today,
   that this order has no delivery options at all. */
.checkout-note[b-zfeoqf1tuw] {
  font-size: var(--text-0-8);
  color: var(--text-muted);
}

/* THE DELIVERY ACCORDION AND THE DEFAULT ADDRESS ABOVE IT (Story 12.8).
   A native <details>/<summary>, so there is no script and nothing to
   initialise — the element is already in the right state on first paint
   because `open` is decided on the server. */
.delivery-default[b-zfeoqf1tuw] {
  margin: 0 0 var(--space-12);
  padding: var(--space-12);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-minimal);
  background: var(--off-white);
}
.delivery-default-label[b-zfeoqf1tuw] {
  margin: 0;
  font-weight: 700;
  color: var(--primary-black);
}
.delivery-default-line[b-zfeoqf1tuw] {
  margin: var(--space-3) 0 0;
  color: var(--text-dark);
}
.delivery-default .hint[b-zfeoqf1tuw] {
  margin: var(--space-7) 0 0;
}

.delivery-accordion[b-zfeoqf1tuw] {
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-minimal);
  padding: var(--space-10) var(--space-12);
}

/* The summary IS the control, so it carries the affordance. list-style is
   removed on both spellings because Safari uses the WebKit pseudo-element and
   ignores `list-style`, which would otherwise leave one browser with a
   disclosure triangle and every other without. */
.delivery-summary[b-zfeoqf1tuw] {
  cursor: pointer;
  font-weight: 700;
  color: var(--primary-black);
  list-style: none;
}
.delivery-summary[b-zfeoqf1tuw]::-webkit-details-marker {
  display: none;
}
.delivery-summary[b-zfeoqf1tuw]::after {
  content: '＋';
  float: right;
  font-weight: 400;
}
.delivery-accordion[open] .delivery-summary[b-zfeoqf1tuw]::after {
  content: '－';
}
.delivery-accordion[open] .delivery-summary[b-zfeoqf1tuw] {
  margin-bottom: var(--space-12);
}

/* The summary is the one control on this surface that is not a <button>, so it
   does not inherit the button focus treatment. Given as an outline of its own
   rather than left to the base ring, which Story 11.2 made transparent. */
.delivery-summary:focus-visible[b-zfeoqf1tuw] {
  outline: 2px solid var(--primary-black);
  outline-offset: 2px;
}

/* ---- the delivery form's grid rows (Product Owner, 2026-09-25) ----------
   "make a grid from those input so reduce scrolling space, name 70%, mobile
   number 30%, then address inputs put in a column grid."

   Six full-width fields stacked vertically is a lot of scrolling on the page a
   Shopper is trying to finish. These two rows turn four of them into two.

   ONLY ABOVE THE PHONE. Below the sm breakpoint every row collapses back to one
   column — a 30% mobile-number field on a 390px screen is about 100px of input
   for a ten-digit number, which is worse than the scrolling it saves. The grid
   is therefore declared INSIDE the media query rather than declared and undone
   outside it, so the phone keeps the layout it already had and the stylesheet
   states the narrow case as the default rather than as an exception.

   576px is breakpoint-sm from the token layer, not a number chosen for this
   form: every media query on this storefront uses a declared breakpoint, and
   DesignTokenLayerTests fails on one that does not. */
@media (min-width: 576px) {
  .field-row[b-zfeoqf1tuw] {
    display: grid;
    /* The same column gap the form's own rhythm uses between fields, so a row
       of two does not read as a different form from the rows of one. */
    gap: 0 var(--space-16);
    align-items: start;
  }

  /* 70/30 as asked, written as fractions of the row rather than as percentages:
     percentages would not account for the gap between them and the pair would
     overflow its container by exactly one gap. 7fr/3fr is the ratio applied to
     what is left AFTER the gap, which is what "70% and 30%" means to read. */
  .field-row-name-mobile[b-zfeoqf1tuw] {
    grid-template-columns: 7fr 3fr;
  }

  /* House and street, then city and country. Two equal columns: none of the
     four is the longer value, and an uneven split would make the country —
     which is stated rather than chosen — look like the important one. */
  .field-row-address[b-zfeoqf1tuw] {
    grid-template-columns: 1fr 1fr;
  }

  /* A field inside a row keeps its own bottom margin, so the rows below it sit
     on the same rhythm as the full-width fields above and beneath them. The
     .group rule already supplies it; this exists only to stop a row's last
     child collapsing it. */
  .field-row > .group[b-zfeoqf1tuw] {
    min-width: 0;
  }
}
/* _content/Odel.Web/Pages/ContactUs.razor.rz.scp.css */
/* Contact Us (Epic 29 story 29-6). Tokens only; the details stack on a phone and
   sit in a row from the declared medium breakpoint up. */

.contact-details[b-1ej80ziyz5] {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-40);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-16);
}

.detail[b-1ej80ziyz5] {
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-minimal);
  background: var(--neutral-100);
  padding: var(--space-16) var(--space-20);
}

.detail-title[b-1ej80ziyz5] {
  display: block;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-8);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-8);
}

.detail-text[b-1ej80ziyz5] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-95);
  line-height: var(--leading-1-5);
  color: var(--text-dark);
  overflow-wrap: anywhere;
}

a.detail-text[b-1ej80ziyz5] {
  text-decoration: underline;
}

.get-in-touch[b-1ej80ziyz5] {
  margin-bottom: var(--space-40);
}

.get-in-touch-title[b-1ej80ziyz5] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-1-4);
  line-height: var(--leading-1-2);
  color: var(--primary-black);
  margin-bottom: var(--space-16);
}

.sent[b-1ej80ziyz5] {
  border: 1px solid var(--neutral-200);
  border-left-width: 3px;
  background: var(--yellow-light);
  padding: var(--space-16);
  margin-bottom: var(--space-20);
}

.sent-body[b-1ej80ziyz5] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: 0;
}

.contact-form .group[b-1ej80ziyz5] {
  margin-bottom: var(--space-16);
}

.contact-form label[b-1ej80ziyz5] {
  display: block;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  font-weight: 500;
  color: var(--text-dark);
  margin-bottom: var(--space-8);
}

.contact-form input[b-1ej80ziyz5],
.contact-form textarea[b-1ej80ziyz5] {
  display: block;
  width: 100%;
  box-sizing: border-box;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-5);
  color: var(--text-dark);
  background: var(--white);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-sm);
  padding: var(--space-12);
}

.contact-form input:focus-visible[b-1ej80ziyz5],
.contact-form textarea:focus-visible[b-1ej80ziyz5] {
  outline: 2px solid var(--yellow-accent);
  outline-offset: 1px;
}

.contact-form textarea[b-1ej80ziyz5] {
  resize: vertical;
}

@media (min-width: 768px) {
  .contact-details[b-1ej80ziyz5] {
    grid-template-columns: repeat(3, 1fr);
  }
}
/* _content/Odel.Web/Pages/Deals.razor.rz.scp.css */
.inline-search[b-69e48radg2] {
  display: none;
  margin: 0 0 var(--space-14);
}
@media (max-width: 991px) {
  .inline-search[b-69e48radg2] {
    display: block;
  }
}

.search-inner[b-69e48radg2] {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  background: var(--off-white);
  border: 1.5px solid var(--neutral-300);
  border-radius: 25px;
  padding: var(--space-9) var(--space-16);
  transition: var(--transition);
}
.search-inner:focus-within[b-69e48radg2] {
  border-color: var(--yellow-accent);
  box-shadow: 0 0 0 3px var(--yellow-a10);
  background: var(--white);
}
.search-inner i[b-69e48radg2] {
  color: var(--text-muted);
  font-size: var(--text-0-9);
}
.search-inner input[b-69e48radg2] {
  flex: 1;
  border: none;
  background: transparent;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  color: var(--text-dark);
  min-width: 0;
}

.content[b-69e48radg2] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-24);
  padding: var(--space-16) 0 var(--space-40);
}
@media (min-width: 992px) {
  .content[b-69e48radg2] {
    grid-template-columns: 240px 1fr;
    gap: var(--space-30);
  }
}
/* ===== Toolbar ===== */
.toolbar[b-69e48radg2] {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-16);
  flex-wrap: wrap;
  gap: var(--space-10);
}
/* The count and the sort control are both shared components now (Stories 2.1
   and 2.5), and so are their rules. This page styles the strip, not what sits
   in it. */

/* The pager, the count and the empty state are Components/Listing's, and so
   are their rules (Story 2.1). This page's own .pagination, .page-item and
   .results-count rules went with the markup they styled. */

/* Uniform mobile page rhythm. */
@media (max-width: 991px) {
  .content[b-69e48radg2] {
    padding: var(--space-10) 0 var(--space-28);
  }
}

/* A 1fr grid item does not shrink below its content's natural width on its
   own; the toolbar and the nowrap product-card .name text pushed this column
   past the grid track at narrow widths, past the viewport, past 390px — and
   body's own overflow-x:hidden silently clipped it instead of showing a
   horizontal scrollbar, so the page just looked narrower than it should until
   measured. Search.razor.css carried this fix already; Categories and
   BrandSpec did not. */
.products[b-69e48radg2] {
  min-width: 0;
}

/* With no facets to offer the panel renders nothing at all, and the grid would
   otherwise keep an empty 240px column beside the results. :only-child is the
   test, because "the panel rendered nothing" and "the products are the only
   child" are the same fact. */
.products:only-child[b-69e48radg2] {
  grid-column: 1 / -1;
}
/* _content/Odel.Web/Pages/Error.razor.rz.scp.css */
.error-page[b-ki98tlbmw9] {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--space-40) var(--space-20);
}

.error-title[b-ki98tlbmw9] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-1-6);
  font-weight: 700;
  color: var(--primary-black);
  margin: 0;
}

.error-actions[b-ki98tlbmw9] {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-12);
}

.error-action[b-ki98tlbmw9] {
  background: var(--primary-black);
  color: var(--white);
  border-radius: 25px;
  padding: var(--space-9) var(--space-24);
  font-size: var(--text-0-82);
  font-weight: 600;
  text-decoration: none;
  transition: var(--transition);
}
.error-action:hover[b-ki98tlbmw9] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

/* The secondary way out: the same shape, stated quietly, so the two actions
   do not read as one decision made twice. */
.error-action-quiet[b-ki98tlbmw9] {
  background: transparent;
  color: var(--primary-black);
  border: 1px solid var(--primary-black);
}

.error-reference[b-ki98tlbmw9] {
  margin-top: var(--space-24);
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-8);
  color: var(--text-muted);
}
/* _content/Odel.Web/Pages/ForgotPassword.razor.rz.scp.css */
/* The card itself is the shared one (`account-card` in wwwroot/css/app.css),
   so this file holds only what belongs to this page — which, on a form with one
   input, is nothing yet. It is kept rather than deleted because a page's scoped
   stylesheet is where a page-specific rule goes, and having to create the file
   is how a rule ends up somewhere shared instead. */
/* _content/Odel.Web/Pages/GuestOrderLookup.razor.rz.scp.css */
/* The Guest order lookup (Story 7.6). Tokens only, one centred column, and no
   breakpoint of its own — a form of two inputs and a page of sentences read at
   every width without one, which is what makes 360px free of horizontal scroll
   rather than something to tune.

   THE TWO FAILING ANSWERS HAVE NO STYLING BETWEEN THEM, and that is a rule
   rather than an omission. An unknown reference and a wrong second factor
   render the same element with the same class, so there is nothing here that
   could give them different colours — which is one more way the two could have
   been told apart by somebody reading the page rather than the HTML.

   The unavailable notice IS styled apart, because it is not an answer about an
   order and must not read as one.

   The order's own sections are styled in
   Components/Orders/OrderSummary.razor.css, where their markup lives. */

.order-lookup[b-x0isb4szag] {
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
  padding-top: var(--space-40);
  padding-bottom: var(--space-40);
}

.order-lookup-heading[b-x0isb4szag] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-1-6);
  line-height: var(--leading-1-2);
  color: var(--primary-black);
  margin-bottom: var(--space-16);
}

.order-lookup-intro[b-x0isb4szag] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-95);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: var(--space-28);
}

/* Set apart from the error summary below it, deliberately. A Shopper has to be
   able to see at a glance that this is us failing rather than an answer about
   their order. */
.order-lookup-unavailable[b-x0isb4szag] {
  border: 1px solid var(--neutral-200);
  border-left-width: 3px;
  padding: var(--space-16);
  margin-bottom: var(--space-24);
  background: var(--neutral-100);
}

.order-lookup-unavailable-body[b-x0isb4szag] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: 0;
}

.order-lookup[b-x0isb4szag]  .hint {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-8);
  line-height: var(--leading-1-5);
  color: var(--text-muted);
  margin-bottom: var(--space-8);
}

.order-lookup-gap[b-x0isb4szag] {
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-20);
  margin-bottom: var(--space-24);
}

.order-lookup-label[b-x0isb4szag] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-75);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-8);
}

.order-lookup-gap-body[b-x0isb4szag] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-muted);
  margin-bottom: 0;
}

.order-lookup-away[b-x0isb4szag] {
  margin-top: var(--space-32);
  margin-bottom: 0;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  font-weight: 500;
}

.order-lookup-away a[b-x0isb4szag] {
  color: var(--text-muted);
  text-decoration: underline;
}

.order-lookup-away a:hover[b-x0isb4szag] {
  color: var(--primary-black);
}
/* _content/Odel.Web/Pages/Home.razor.rz.scp.css */
.top-sections[b-zlikf2ltpr] {
  display: flex;
  flex-direction: column;
}

/* Mobile reorder: tabs, then category, then mid-banner, then the SAME landing
   banner the desktop shows, then the rotating offer line — with a small
   consistent gap between the stacked sections.

   The hero used to be `display: none` here, so a mobile shopper never saw the
   landing banner at all. It is now shown, in the same carousel with the same
   slides as the desktop, placed between the mid-banner and the line banner. On
   a tab whose mid-banner slot is unpublished — which is the common case — that
   puts it exactly between the category slot and the line banner.

   Every slot carries an explicit order rather than leaning on the line
   banner's default of 0. The line banner is the only child of this container
   with no order of its own, and inserting the hero "before" it by giving the
   hero a negative order silently depended on that; an order added to the line
   banner later would have moved the hero without anyone touching the hero. */
@media (max-width: 991px) {
  .top-sections[b-zlikf2ltpr] {
    gap: var(--space-10);
  }
  .tabs-slot[b-zlikf2ltpr] {
    order: -4;
  }
  .cat-slot[b-zlikf2ltpr] {
    order: -3;
  }
  .mid-slot[b-zlikf2ltpr] {
    order: -2;
  }
  .hero-slot[b-zlikf2ltpr] {
    order: -1;
  }
  /* ::deep because .line-banner is LineBanner.razor's own root element and
     therefore carries that component's scope attribute, not this page's. A
     bare `.line-banner` here compiles without complaint and matches nothing,
     which is the quiet way a scoped-CSS rule fails. */
  .top-sections[b-zlikf2ltpr]  .line-banner {
    order: 0;
  }
}

/* Per-tab minimal gradient themes (mobile home). The active tab paints a
   soft tint behind the top sections.

   This was a body.tab-theme-* class applied from OnAfterRenderAsync, which
   never runs under static server rendering — so the theme was never painted
   for anyone. It is a data-tab-theme attribute on .top-sections now,
   rendered by the server on the same pass as the tab's content, which also
   means the two can never disagree about which tab is active. */
@media (max-width: 991px) {
  .top-sections[data-tab-theme='all'][b-zlikf2ltpr] {
    background: linear-gradient(180deg, var(--yellow-a06) 0%, var(--yellow-a00) 90%);
  }
  .top-sections[data-tab-theme='men'][b-zlikf2ltpr] {
    background: linear-gradient(180deg, var(--blue-a07) 0%, var(--blue-a00) 90%);
  }
  .top-sections[data-tab-theme='women'][b-zlikf2ltpr] {
    background: linear-gradient(180deg, var(--pink-a07) 0%, var(--pink-a00) 90%);
  }
  .top-sections[data-tab-theme='kids'][b-zlikf2ltpr] {
    /* Light creamy tint (the --cream token). */
    background: linear-gradient(180deg, var(--cream-a40) 0%, var(--cream-a00) 90%);
  }
  .top-sections[data-tab-theme='sports'][b-zlikf2ltpr] {
    /* Light green tint (the --success-light token). */
    background: linear-gradient(180deg, var(--success-light-a30) 0%, var(--success-light-a00) 90%);
  }
}
/* _content/Odel.Web/Pages/HomeSections/AllProducts.razor.rz.scp.css */
.section[b-32nofjy0th] {
  padding: var(--space-40) 0 var(--space-50);
  background: var(--off-white);
}

/* The filter bar's rules went with the bar itself (Story 2.5): .filter,
   .toggle, .chevron, .body, .group, .label, .chips, .chip, .range, .sort and
   .apply all styled markup this component no longer emits, and a scoped rule
   for markup a component does not render is dead CSS. Refinement and ordering
   live on the listing surfaces, with their own rules. */

/* Uniform mobile section rhythm; slightly deeper tail as the last section. */
@media (max-width: 991px) {
  .section[b-32nofjy0th] {
    padding: var(--space-18) 0 var(--space-28);
  }
}

/* The "Load more" control (Story 12.5) — same pill treatment as the listing
   pager's own links (ListingPager.razor.css's .paging a), because this is the
   same idea: a real link to a real page, not a bespoke button. Centred on its
   own row below the grid rather than beside a position label, since there is
   only ever one of it. */
.load-more[b-32nofjy0th] {
  display: flex;
  justify-content: center;
  margin-top: var(--space-28);
}

.load-more-link[b-32nofjy0th] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  border: 1px solid var(--neutral-400);
  border-radius: var(--radius-pill);
  padding: var(--space-10) var(--space-24);
  background: var(--white);
  color: var(--text-dark);
  text-decoration: none;
  font-weight: 600;
  font-size: var(--text-0-85);
  transition: var(--transition);
}

.load-more-link:hover[b-32nofjy0th],
.load-more-link:focus-visible[b-32nofjy0th] {
  background: var(--primary-black);
  border-color: var(--primary-black);
  color: var(--white);
}

/* Six cards a row from 1200 (Product Owner, 2026-10-23), so a card is about
   195px and a 200w photo is not stretched. The card's own .item flex-basis is
   scoped to ProductCard and gives five from 992, which the listing pages keep
   (their sidebar leaves them about 190px); this is the homepage grid's override,
   reached through ::deep and heavier than the card's own rule. 16.666% less the
   same 8px the card's own bases leave for the 8px gaps. */
@media (min-width: 1200px) {
  .all-products-grid[b-32nofjy0th]  .item {
    flex: 0 0 calc(16.666% - 8px);
  }
}

/* The sort beside the "All products" button (Product Owner, 2026-10-23). The
   select itself is the listing sort's own pill (.sort-select, app.css); this
   only lays the form out. The unscripted Sort button is hidden by
   home-sort.js once it is driving the form. */
.home-sort[b-32nofjy0th] {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  margin: 0;
  min-width: 0;
}

.home-sort [hidden][b-32nofjy0th] {
  display: none;
}

/* On a phone the pair shares one row under the title: the select takes what
   the button leaves, so 360px never scrolls sideways. */
@media (max-width: 575px) {
  .home-sort[b-32nofjy0th] {
    flex: 1 1 auto;
  }
  .home-sort .sort-select[b-32nofjy0th] {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 36px;
    font-size: var(--text-0-75);
  }
}
/* _content/Odel.Web/Pages/HomeSections/DepartmentBlocks.razor.rz.scp.css */
.section[b-opsnehglc1] {
  padding: var(--space-40) 0 var(--space-20);
  background: var(--white);
}

.card[b-opsnehglc1] {
  position: relative;
  display: block;
  overflow: hidden;
  /* The card owns the tile's box; the brand-tile slot's 1:1 supply ratio is
     cropped to this landscape band, and to a shorter one on small screens. */
  min-height: 150px;
  height: 150px;
  border-radius: var(--radius-minimal);
  background: var(--off-white);
  color: var(--white);
  cursor: pointer;
  text-decoration: none;
  transition: var(--transition);
}
.card[b-opsnehglc1]  img {
  transition: var(--transition);
  filter: saturate(0.95);
}
.card[b-opsnehglc1]::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--black-a05), var(--black-a60));
}
.card:hover[b-opsnehglc1] {
  box-shadow: var(--shadow-md);
  transform: translateY(-3px);
}
.card:hover[b-opsnehglc1]  img {
  transform: scale(1.06);
}
.card:hover .name[b-opsnehglc1] {
  opacity: 1;
  color: var(--primary-black);
}
@media (max-width: 575px) {
  .card[b-opsnehglc1] {
    min-height: 118px;
    height: 118px;
  }
}

.name[b-opsnehglc1] {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 12px;
  z-index: 1;
  font-weight: 700;
  font-size: var(--text-0-9);
  letter-spacing: 0.5px;
  color: var(--white);
  text-transform: uppercase;
  transition: var(--transition);
}
@media (max-width: 575px) {
  .name[b-opsnehglc1] {
    left: 8px;
    right: 8px;
    bottom: 8px;
    font-size: var(--text-0-68);
    letter-spacing: 0.3px;
  }
}

/* Uniform mobile section rhythm (18px band, matches flash sale). */
@media (max-width: 991px) {
  .section[b-opsnehglc1] {
    padding: var(--space-18) 0;
  }
}

/* The heading row and its "View all" link are the shared SectionHeaderLink
   component's (Components/Cards), styled there once for this section, "Shop by
   Category" and the brand page. */
/* _content/Odel.Web/Pages/HomeSections/FlashSale.razor.rz.scp.css */
/* White with black headings (Product Owner, 2026-10-27); the countdown pill
   keeps its black fill, which reads on white. */
.section[b-y11dbf0m0l] {
  background: var(--white);
  padding: var(--space-28) 0;
}
@media (max-width: 991px) {
  .section[b-y11dbf0m0l] {
    /* Small breathing room after the highlight cards (mobile). */
    margin-top: var(--space-10);
  }
}
@media (max-width: 767px) {
  .section[b-y11dbf0m0l] {
    padding: var(--space-18) 0;
  }
}

.header[b-y11dbf0m0l] {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  margin-bottom: var(--space-16);
}
.header .section-header[b-y11dbf0m0l] {
  margin-bottom: 0;
}
@media (max-width: 767px) {
  .header[b-y11dbf0m0l] {
    gap: var(--space-8);
    margin-bottom: var(--space-12);
  }
}

.title[b-y11dbf0m0l] {
  font-size: var(--text-2);
  font-weight: 800;
  letter-spacing: 0.3px;
  margin-bottom: 0;
  color: var(--primary-black);
}
@media (max-width: 767px) {
  .title[b-y11dbf0m0l] {
    font-size: var(--text-1-3);
  }
}
.header .section-subtitle[b-y11dbf0m0l] {
  color: var(--primary-black);
}

/* Keep the emoji its natural colour + minimal flash animation. */
.bolt[b-y11dbf0m0l] {
  display: inline-block;
  transform-origin: center;
  animation: boltFlash-b-y11dbf0m0l var(--duration-2-2) ease-in-out infinite;
}

@keyframes boltFlash-b-y11dbf0m0l {
  0%, 100% { opacity: 1; transform: scale(1) rotate(0deg); filter: none; }
  44% { opacity: 0.55; }
  50% { opacity: 1; transform: scale(1.18) rotate(-6deg); filter: drop-shadow(0 0 4px var(--yellow-a85)); }
  56% { opacity: 0.8; }
}

.countdown[b-y11dbf0m0l] {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  background: var(--primary-black);
  padding: var(--space-10) var(--space-20);
  border-radius: 50px;
  box-shadow: 0 4px 14px var(--black-a28);
  color: var(--white);
}
@media (max-width: 767px) {
  .countdown[b-y11dbf0m0l] {
    padding: var(--space-8) var(--space-14);
    gap: var(--space-4);
  }
}

.timer-group[b-y11dbf0m0l] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
}
@media (max-width: 767px) {
  .timer-group[b-y11dbf0m0l] {
    gap: var(--space-4);
  }
}

.block[b-y11dbf0m0l] {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 36px;
}
@media (max-width: 767px) {
  .block[b-y11dbf0m0l] {
    min-width: 30px;
  }
}

.value[b-y11dbf0m0l] {
  font-weight: 800;
  font-size: var(--text-1-4);
  line-height: var(--leading-1);
  color: var(--white);
}
@media (max-width: 767px) {
  .value[b-y11dbf0m0l] {
    font-size: var(--text-1-2);
  }
}

.label[b-y11dbf0m0l] {
  font-size: var(--text-0-55);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--white);
  opacity: 0.85;
}
@media (max-width: 767px) {
  .label[b-y11dbf0m0l] {
    font-size: var(--text-0-52);
  }
}

.separator[b-y11dbf0m0l] {
  font-size: var(--text-1-2);
  font-weight: 700;
  color: var(--white);
  margin: 0 var(--space-1);
}
@media (max-width: 767px) {
  .separator[b-y11dbf0m0l] {
    font-size: var(--text-1);
  }
}

/* Story 32.22: shown by js/flash-deal-countdown.js at zero, in place of the digits. */
.ended[b-y11dbf0m0l] {
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
.ended[hidden][b-y11dbf0m0l] {
  display: none;
}
.timer-group[hidden][b-y11dbf0m0l] {
  display: none;
}

/* Inside the flash deal only (Product Owner, 2026-10-27): every card's
   discount strip is up and sliding along the top of its photo without a
   hover, on any device, and the pill steps aside as it does on hover.
   Everywhere else the strip still waits for a hover (ProductCard.razor.css). */
.section[b-y11dbf0m0l]  .promo-strip {
  transform: none;
  opacity: 1;
}
.section[b-y11dbf0m0l]  .promo-strip-track {
  animation-play-state: running;
}
.section[b-y11dbf0m0l]  .tag.has-strip {
  opacity: 0;
}
/* _content/Odel.Web/Pages/HomeSections/HotDiscounts.razor.rz.scp.css */
.section[b-51eqg7q5tt] {
  padding: var(--space-36) 0;
  background: var(--off-white);
}

/* A black tile with the percentage in the yellow accent, like the card's
   discount strip. The whole tile is the link. */
.tile[b-51eqg7q5tt] {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  height: 100%;
  padding: var(--space-20) var(--space-16);
  background: var(--primary-black);
  border-radius: var(--radius-minimal);
  text-align: center;
  text-decoration: none;
  transition: var(--transition);
}
.tile:hover[b-51eqg7q5tt],
.tile:focus-visible[b-51eqg7q5tt] {
  box-shadow: var(--shadow-lg);
  transform: translateY(-4px);
}
.tile:focus-visible[b-51eqg7q5tt] {
  outline: 2px solid var(--yellow-accent);
  outline-offset: 2px;
}

.percent[b-51eqg7q5tt] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-2);
  font-weight: 800;
  /* White on the black tile (Product Owner, 2026-10-27). */
  color: var(--white);
  line-height: var(--leading-1);
}

.cta[b-51eqg7q5tt] {
  font-size: var(--text-0-8);
  font-weight: 500;
  letter-spacing: 0.5px;
  color: var(--white);
}

/* Uniform mobile section rhythm (matches the other homepage bands). */
@media (max-width: 991px) {
  .section[b-51eqg7q5tt] {
    padding: var(--space-18) 0;
  }
}
/* _content/Odel.Web/Pages/HomeSections/LineBanner.razor.rz.scp.css */
.line-banner[b-siozlb10oh] {
  display: none;
}
@media (max-width: 991px) {
  .line-banner[b-siozlb10oh] {
    display: block;
    position: relative;
    height: 25px;
    overflow: hidden;
    background: var(--primary-black);
  }
  .item[b-siozlb10oh] {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Poppins', sans-serif;
    font-size: var(--text-0-68);
    font-weight: 600;
    letter-spacing: 0.4px;
    color: var(--white);
    opacity: 0;
    transition: opacity var(--duration-0-6) ease;
    padding: 0 var(--space-12);
    white-space: nowrap;
  }
  .item.active[b-siozlb10oh] {
    opacity: 1;
  }
}
/* _content/Odel.Web/Pages/HomeSections/MobileCatTabs.razor.rz.scp.css */
.tabs[b-gtfues7jxb] {
  display: none;
}
@media (max-width: 991px) {
  .tabs[b-gtfues7jxb] {
    display: flex;
    align-items: flex-end;
    gap: var(--space-4);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: var(--space-8) var(--space-12) 0;
    background: var(--white);
    /* Baseline bar the active tab fuses into (Chrome-tab look). */
    border-bottom: 2px solid var(--primary-black);
  }
  .tabs[b-gtfues7jxb]::-webkit-scrollbar {
    display: none;
  }
  /* Let the per-tab gradient theme show through. The theme used to be a
     body class set from OnAfterRenderAsync, which never runs under static
     SSR; it is a data-tab-theme attribute rendered on the .top-sections
     wrapper by Home.razor now, so the ancestor is in the server response. */
  [data-tab-theme] .tabs[b-gtfues7jxb] {
    background: transparent;
  }
}

/* Chrome-browser-style tabs: rounded top, flat bottom sitting on the
   baseline bar. Inactive tabs are quiet chips; the active tab fills
   black, lifts its label and flares outward at the bottom corners. */
.tab[b-gtfues7jxb] {
  position: relative;
  flex: 0 0 auto;
  border: none;
  background: var(--ink-a05);
  color: var(--text-muted);
  font-family: 'Poppins', sans-serif;
  font-weight: 500;
  font-size: var(--text-0-85);
  padding: var(--space-8) var(--space-16) var(--space-9);
  border-radius: 10px 10px 0 0;
  cursor: pointer;
  /* Links, not buttons, since static SSR binds no handler — so the anchor
     defaults a button did not have are reset here. */
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--duration-0-25) ease, background var(--duration-0-25) ease;
}

/* Only the active tab is re-coloured to its theme. --tab-active / -fg are
   set inline on the active button (see MobileCatTabs.razor). The row
   background and inactive tabs keep their defaults; falls back to
   black/yellow if the variables are ever absent. */
.active[b-gtfues7jxb] {
  background: var(--tab-active, var(--primary-black));
  color: var(--tab-active-fg, var(--yellow-accent));
  font-weight: 600;
}
/* Outward-curving bottom corners, like the front tab in Chrome — matched
   to the active tab's own colour. */
.active[b-gtfues7jxb]::before,
.active[b-gtfues7jxb]::after {
  content: '';
  position: absolute;
  bottom: 0;
  width: 10px;
  height: 10px;
}
.active[b-gtfues7jxb]::before {
  left: -10px;
  background: radial-gradient(circle at 0 0, transparent 10px, var(--tab-active, var(--primary-black)) 10.5px);
}
.active[b-gtfues7jxb]::after {
  right: -10px;
  background: radial-gradient(circle at 100% 0, transparent 10px, var(--tab-active, var(--primary-black)) 10.5px);
}
/* _content/Odel.Web/Pages/HomeSections/MobileSearchBar.razor.rz.scp.css */
/* Sticky, not fixed. Fixed took the bar out of flow, so the space it
   occupies had to be paid for by a body.mobile-has-search class that
   Home.razor set through OnAfterRenderAsync — which never runs under
   static SSR, so the class was never applied and the bar sat on top of
   the first row of homepage content on every phone. Sticky keeps the
   same behaviour (it pins below the fixed header once the page
   scrolls) while occupying its own height in normal flow, so no
   JavaScript and no body class is involved in it being visible. */
.bar[b-2wswwd9yke] {
  display: none;
  position: sticky;
  top: var(--header-height);
  left: 0;
  right: 0;
  z-index: 1040;
  background: var(--white);
  border-bottom: 1px solid var(--glass-border);
  padding: var(--space-8) var(--space-16);
  box-shadow: 0 2px 10px var(--black-a06);
}
@media (max-width: 991px) {
  .bar[b-2wswwd9yke] {
    display: block;
  }
}

/* The shared look of the field, its controls and the panel is in app.css, once.
   What is here is the phone bar's own: the pale chrome against a white bar, and
   the stacking context the panel drops out of. ::deep is how a scoped stylesheet
   reaches a child component's markup — the island lives in Odel.Web.Client and
   carries none of this project's scope attribute. */
.bar[b-2wswwd9yke]  .typeahead-field {
  background: var(--off-white);
  border: 1.5px solid var(--neutral-300);
  padding: var(--space-9) var(--space-16);
}
.bar[b-2wswwd9yke]  .typeahead-field:focus-within {
  border-color: var(--yellow-accent);
  box-shadow: 0 0 0 3px var(--yellow-a10);
  background: var(--white);
}
.bar[b-2wswwd9yke]  .typeahead-field i {
  color: var(--text-muted);
  font-size: var(--text-0-9);
}
.bar[b-2wswwd9yke]  .typeahead-input {
  font-size: var(--text-0-85);
  color: var(--text-dark);
}
.bar[b-2wswwd9yke]  .typeahead-input::placeholder {
  color: var(--text-muted);
}
.bar[b-2wswwd9yke]  .typeahead-clear,
.bar[b-2wswwd9yke]  .typeahead-submit {
  color: var(--text-muted);
  font-size: var(--text-0-85);
}
.bar[b-2wswwd9yke]  .typeahead-clear:hover,
.bar[b-2wswwd9yke]  .typeahead-submit:hover {
  color: var(--text-dark);
}
.bar[b-2wswwd9yke]  .typeahead-panel {
  z-index: 1060;
}
/* _content/Odel.Web/Pages/HomeSections/NewArrivals.razor.rz.scp.css */
.section[b-599qeilu8n] {
  padding: var(--space-36) 0;
  background: var(--white);
}

/* Uniform mobile section rhythm (18px band, matches flash sale). */
@media (max-width: 991px) {
  .section[b-599qeilu8n] {
    padding: var(--space-18) 0;
  }
}

/* The Product Owner wants THIS row's card button to read "Add"; every other
   rail (Flash Sale, Similar Items, Customers Also Buy) keeps lowercase "add"
   (Story 15.2). ProductCard.razor.css sets
   `.card .add-btn { text-transform: lowercase; }`, so passing AddLabel="Add"
   from NewArrivals.razor alone is not enough — the card's own rule still
   lowercases it visually. ProductCard is shared by every listing, rail and
   grid and is not touched here (see the epic's "change only what is asked").

   `::deep` reaches into the ProductCard the carousel renders using an element
   NewArrivals itself emits — `.section` — so the scope attribute lands on
   `.section` and nothing after `::deep` is scoped at all. Compiled, this rule
   is `.section[b-xxxx] .card .add-btn` — four classes/attributes — against the
   card's own compiled `.card .add-btn[b-yyyy]` — three. Higher specificity
   wins outright, so this does not depend on which stylesheet the bundle
   concatenates last (the failure mode Epic 13 hit three times). */
.section[b-599qeilu8n]  .card .add-btn {
  text-transform: none;
}
/* _content/Odel.Web/Pages/HomeSections/PromoBanner.razor.rz.scp.css */
.section[b-fuuy2vjc76] {
  padding: var(--space-24) 0;
  background: var(--white);
}

/* Uniform mobile section rhythm (18px band, matches flash sale). */
@media (max-width: 991px) {
  .section[b-fuuy2vjc76] {
    padding: var(--space-18) 0;
  }
}
/* _content/Odel.Web/Pages/HomeSections/ShopByCategory.razor.rz.scp.css */
.section[b-vniq3e3ibw] {
  padding: var(--space-24) 0 var(--space-20);
  background: var(--white);
}

/* Let the per-tab gradient theme show through (mobile home). The theme is
   the data-tab-theme attribute Home.razor renders on .top-sections; it used
   to be a body class that static SSR never applied. */
@media (max-width: 991px) {
  [data-tab-theme] .section[b-vniq3e3ibw] {
    background: transparent;
  }
}

/* Inside the mobile top stack the 10px slot gap does the separating. */
@media (max-width: 991px) {
  .section[b-vniq3e3ibw] {
    padding: var(--space-8) 0 var(--space-4);
  }
}

/* The heading row and its "All categories" link are the shared
   SectionHeaderLink component's (Components/Cards), styled there once for
   this section, the department blocks and the brand page. */
/* _content/Odel.Web/Pages/ItemDetails.razor.rz.scp.css */
/* Story 16.2: the Back control, above the breadcrumb at every width. A real
   <a href> (works with no JS); app-interop.js's [data-history-back] handler
   intercepts the click and steps back into history instead, when that is
   possible. .breadcrumb-line's own top padding is app.css's (24px desktop,
   14px mobile, both meant to clear the fixed header on a page with nothing
   above the breadcrumb); the adjacent-sibling rule (now in app.css, Epic 30 Story
   30-3: the nav is the Breadcrumbs child component's, so a scoped rule here no
   longer matches it) only trims that
   shared space now that the Back control sits above it, so the pair reads as
   one band of whitespace rather than two stacked ones. */
.back-link[b-uqdrro0y11] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  color: var(--text-muted);
  text-decoration: none;
  font-size: var(--text-0-84);
  font-weight: 600;
  margin-top: var(--space-10);
}
.back-link:hover[b-uqdrro0y11] {
  color: var(--primary-black);
}
.back-link:focus-visible[b-uqdrro0y11] {
  outline: 2px solid var(--primary-black);
  outline-offset: 2px;
  border-radius: var(--radius-minimal);
}

.layout[b-uqdrro0y11] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-28);
  padding: var(--space-8) 0 var(--space-36);
}
@media (min-width: 992px) {
  .layout[b-uqdrro0y11] {
    grid-template-columns: minmax(0, 1.02fr) minmax(360px, 0.98fr);
    align-items: start;
    gap: var(--space-42);
  }
}

/* Story 17.3 replaced the 2-column image grid with a hero + thumbnail rail
   (Product Owner, 2026-10-01). `.gallery` is now just the outer wrapper —
   either the flat branch's single `.gallery-rail-layout`, or (grouped mode)
   the set of per-colour `.gallery-group`s, each holding its own rail-layout.
   It carries no layout of its own any more; see `.gallery-rail-layout`
   below for the actual hero/rail mechanics, mobile-first (the Product
   Owner's own explicit split puts the rail BELOW the hero on a phone and
   beside it, on the left, at 992px and up — see the min-width block). */

/* Story 16.11: colour-grouped gallery. A group defaults to `display: none`
   and only `:not([hidden])` turns it on — never the reverse (a bare
   `.gallery-group { display: block }` plus a `[hidden]` override). Review
   correction (Story 16.11): the reason is CASCADE ORIGIN, not a specificity
   tie — an author stylesheet's normal declarations outrank the user-agent
   stylesheet's at equal importance REGARDLESS of selector specificity, so an
   unconditional `display: block` here would have won against the UA's
   `[hidden] { display: none }` even if one selector were far more specific
   than the other. Defaulting to none and only opting a visible group IN
   sidesteps that origin precedence entirely, rather than relying on getting
   a tie-break right. A product with no colour assignment never gets
   `.is-grouped` and renders its one rail-layout directly inside `.gallery`,
   with no group wrapper at all. */
.gallery.is-grouped[b-uqdrro0y11] {
  display: block;
}
.gallery-group[b-uqdrro0y11] {
  display: none;
}
.gallery-group:not([hidden])[b-uqdrro0y11] {
  display: block;
}

/* ---- hero + thumbnail rail (Story 17.3) -------------------------------- */

/* Mobile-first: one column, hero above, rail below. RenderGalleryRail
   (ItemDetails.razor) renders this unconditionally — even for a single-image
   group, which simply gets no `.gallery-rail` child, so this still lays out
   correctly with nothing to reflow. */
.gallery-rail-layout[b-uqdrro0y11] {
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}
.gallery-hero[b-uqdrro0y11] {
  position: relative;
  width: 100%;
}
/* The hero stack's own images are a child component's output (SlotImage), so
   reaching them needs ::deep, same as the old grid's own img rule did. Only
   one slide is ever active at a time on desktop — the server renders index 0
   that way, and pdp-variant.js's gallery-pick handler moves `data-active`
   from then on (Story 28.1; see RenderGalleryRail's own remarks for why that
   swap is JS rather than pure CSS). */
.gallery-hero[b-uqdrro0y11]  img {
  display: block;
  width: 100%;
  border-radius: var(--radius-minimal);
}
/* A guard now, not a live rule: Story 28.1 moved visibility from `hidden` to
   `data-active`, so nothing sets `hidden` on a gallery image any more. Kept so
   a future `hidden` image can never reappear. Review fix (Story 17.3): the `display: block` above is an AUTHOR normal
   declaration and therefore beats the user-agent stylesheet's own
   `[hidden] { display: none }` on cascade ORIGIN, regardless of specificity —
   the same trap `.gallery-group` is written to sidestep (see its remarks) and
   the one `.carousel-nav[hidden]` is stated for. Without this rule EVERY
   image of the selected colour rendered full size, stacked down the page, and
   a thumbnail click changed nothing a shopper could see. Restated here so the
   hero swap works at all, and so a later display declaration cannot silently
   reveal the whole stack again. */
.gallery-hero[b-uqdrro0y11]  img[hidden] {
  display: none;
}
.gallery-rail[b-uqdrro0y11] {
  display: flex;
  flex-direction: row;
  gap: var(--space-8);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* So the strip never clips a focus ring drawn on its first/last square. */
  padding: var(--space-2);
}
/* 67x100, a 2:3 portrait box (Story 30-24, Epic 30, 2026-10-23; it was 71x100 at
   3:4 from 2026-10-09 on the Product Owner's request). It was a 56px square,
   which cropped a portrait garment to its middle at the one size where a
   shopper is choosing BETWEEN pictures -- the thumbnail has to show which shot
   it is, and a square crop of a full-length shot shows a waist. The ratio now
   matches ImageSlot.ProductGallery's own 2:3, so a thumbnail is the same shape
   as the picture it opens. */
.gallery-thumb[b-uqdrro0y11] {
  flex: 0 0 auto;
  display: block;
  width: 67px;
  height: 100px;
  border-radius: var(--radius-minimal);
  overflow: hidden;
  cursor: pointer;
  position: relative;
}
/* No object-fit here: SlotImage's own inline style already sets it, from the
   SLOT's NeverCropped flag, and MerchandisingImageRoutingTests polices exactly
   this — cropping is the slot's decision, never a component stylesheet's second
   copy of it. ProductGalleryThumb was flipped to NeverCropped (contain) in
   ImageSlot.cs for this change; that is where to go to alter it, not here.
   This rule only sizes the box the slot's own aspect-ratio already shapes. */
.gallery-thumb[b-uqdrro0y11]  img {
  width: 100%;
  height: 100%;
  display: block;
}
/* The radio is a real DESCENDANT of its own label here (nested, not the
   adjacent-sibling pattern `.gallery-toggle`/`.facet-check` both need — see
   RenderGalleryRail's own remarks for why this control can use the simpler
   relationship and those two cannot), so :focus-within reaches it with no
   extra wiring: a keyboard user tabbing or arrow-keying through the rail
   gets a visible ring on whichever square currently has the browser's own
   native radio-group focus, independent of which one is checked. */
.gallery-thumb:focus-within[b-uqdrro0y11] {
  outline: 2px solid var(--primary-black);
  outline-offset: 2px;
}
.gallery-thumb:hover[b-uqdrro0y11] {
  outline: 2px solid var(--yellow-accent);
}
/* The checked ring is the sighted half of a fact assistive tech already has:
   a radio's own `:checked` state is what a screen reader announces as "the
   one currently shown" — this is not a second source of truth, only its
   visual twin. `+ ::deep img`, not `:has()`: the radio and the thumbnail img
   it rings ARE true adjacent DOM siblings (SlotImage's root element IS the
   `<img>`, with no wrapper div around it — see SlotImage.razor), so the plain
   adjacent-sibling combinator already used elsewhere on this page reaches it
   without needing a newer selector. */
.gallery-pick:checked[b-uqdrro0y11] +  img {
  outline: 3px solid var(--primary-black);
  outline-offset: -3px;
}

/* ---- Story 28.1: the hero is a track of slides -------------------------- */

/* Only the active slide shows by DEFAULT (top level, no media query), so the
   fractional width between 991px and 992px, which matches neither block, can
   never stack every slide down the page (the Story 17.3 symptom). The
   attribute is `data-active`, not `hidden`, so the max-width block below can
   re-show every slide in the phone's one swipeable track. */
.gallery-slide:not([data-active])[b-uqdrro0y11] {
  display: none;
}
.gallery-count[b-uqdrro0y11] {
  display: none;
}

@media (max-width: 991px) {
  /* A block, not a grid, below 992px: a sticky item can only travel inside its
     containing block, and a grid item's is its own row -- which is exactly as
     tall as the gallery, so it could never pin. In a block layout the
     gallery's containing block is the whole section, so it stays pinned under
     the header until the section ends, with the details card sliding over it. */
  .layout[b-uqdrro0y11] {
    display: block;
  }
  .gallery[b-uqdrro0y11] {
    position: sticky;
    top: var(--header-height);
    z-index: 0;
    /* Equal to the details card's overlap (the .info-panel negative margin), so
       the card only ever slides over empty space and never over the rail. */
    padding-bottom: var(--space-20);
  }
  .gallery-hero[b-uqdrro0y11] {
    position: relative;
  }
  .gallery-track[b-uqdrro0y11] {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }
  .gallery-track[b-uqdrro0y11]::-webkit-scrollbar {
    display: none;
  }
  .gallery-slide:not([data-active])[b-uqdrro0y11] {
    display: block;
  }
  /* The pinned gallery must fit on screen with its rail, or the rail is lost
     below the fold for good. The cap is the viewport less the header, the
     bottom nav, the rail (100px thumb + 2 x 2px padding), the gap above it and
     the card overlap. The slot is object-fit: cover (since 2026-10-23), so the
     box must keep SlotImage's 2:3 ratio (Story 30-24, 2026-10-23) or cover would
     crop heads and feet: the width is capped at two thirds of the height cap, so the ratio
     gives a height that never exceeds it, and the box is centred in its slide. */
  .gallery-slide[b-uqdrro0y11]  img {
    max-height: calc(100svh - var(--header-height) - var(--bottom-nav-height) - 104px - var(--space-10) - var(--space-20));
    max-width: calc((100svh - var(--header-height) - var(--bottom-nav-height) - 104px - var(--space-10) - var(--space-20)) * 2 / 3);
    margin-inline: auto;
  }
  .gallery-slide[b-uqdrro0y11] {
    flex: 0 0 100%;
    min-width: 0;
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }
  .gallery-count[b-uqdrro0y11] {
    display: block;
    position: absolute;
    top: var(--space-10);
    right: var(--space-10);
    padding: var(--space-3) var(--space-10);
    border-radius: var(--radius-pill);
    background: var(--black-a60);
    color: var(--white);
    font-size: var(--text-0-76);
    font-weight: 600;
    pointer-events: none;
  }
  .gallery-rail[b-uqdrro0y11] {
    scroll-snap-type: x proximity;
  }
  .gallery-thumb[b-uqdrro0y11] {
    scroll-snap-align: start;
  }
}

/* Story 29.1 (Product Owner, 2026-10-21, with a screenshot): on a PHONE
   (max-width 767px, the width SlotImage and the brand banners already treat as
   mobile; a tablet keeps the 28.1 one-slide track above) a colour's gallery is
   two photos side by side, swiped one photo per snap step, with no thumbnail
   strip. This only changes the slide's width, the gap and what is hidden: the
   28.1 pinned gallery, the 100svh cap, scroll-snap and the data-gallery-colour
   groups are all untouched. A one-image group renders no `.gallery-track` at
   all (see RenderGalleryRail), so it keeps its single full-width hero. */
@media (max-width: 767px) {
  /* Product Owner, 2026-10-23: the pair is FULL-BLEED. It touches the left and
     right screen edges and each other: the gallery cancels the page
     container's phone side padding (.container-custom, app.css, var(--space-16))
     with a negative margin of the same size, rather than 100vw, which counts
     the scrollbar and would add a horizontal page scroll. No other wrapper on
     the way down (.layout, .gallery-group, .gallery-hero) carries horizontal
     padding or margin. */
  .gallery[b-uqdrro0y11] {
    margin-inline: calc(-1 * var(--space-16));
  }
  .gallery-track[b-uqdrro0y11] {
    gap: 0;
  }
  .gallery-slide[b-uqdrro0y11] {
    /* Exactly half the track each, no gap. SlotImage's own 2:3 ratio and
       object-fit: cover apply inside each half. */
    flex: 0 0 50%;
  }
  /* Edge to edge, so no rounded corners against the screen edge or each other. */
  .gallery-hero[b-uqdrro0y11]  img {
    border-radius: 0;
  }
  /* The thumbnails are not needed here: the photos themselves are the control. */
  .gallery-rail[b-uqdrro0y11] {
    display: none;
  }
  /* "n / N" counts one slide, which a pair makes ambiguous. */
  .gallery-count[b-uqdrro0y11] {
    display: none;
  }
  /* The 28.1 cap reserved the rail's 104px and the gap above it; neither is
     there now, so the image may use that room. */
  /* Same specificity as the 991px rule above and later in the file, so the pair
     takes these values: the full half-width, with the width cap lifted. */
  .gallery-slide[b-uqdrro0y11]  img {
    max-height: calc(100svh - var(--header-height) - var(--bottom-nav-height) - var(--space-20));
    max-width: none;
    margin-inline: 0;
  }
}

@media (min-width: 992px) {
  /* The Product Owner's own split: beside the hero, on the left, as a
     narrow vertical column — not the mobile strip reflowed sideways. */
  .gallery-rail-layout[b-uqdrro0y11] {
    flex-direction: row;
    align-items: flex-start;
  }
  .gallery-rail[b-uqdrro0y11] {
    order: -1;
    flex: 0 0 auto;
    flex-direction: column;
    overflow-x: visible;
    overflow-y: auto;
    max-height: 640px;
  }
  .gallery-hero[b-uqdrro0y11] {
    flex: 1 1 auto;
    min-width: 0;
  }
}

/* prefers-reduced-motion: the hero swap itself is instant (a `hidden`
   attribute flip, not an animation) and stays that way regardless of this
   query — there is nothing here to turn off. The only motion this page adds
   is the hover/focus outline above, which is not a transition at all. */

/* Story 28.1: the details card. Below 992px it is the sheet that slides up
   OVER the pinned gallery (the gallery's own sticky rule is in the max-width
   block above); only at 992px and up is it the sticky column beside the
   gallery it was always meant to be -- the old unconditional sticky did
   nothing useful in a single column. */
.info-panel[b-uqdrro0y11] {
  position: relative;
  z-index: 1;
  background: var(--white);
  /* The largest radius token, so the top reads as a sheet. */
  border-radius: var(--radius-pill) var(--radius-pill) 0 0;
  margin-top: calc(-1 * var(--space-20));
  padding-top: var(--space-18);
  /* Hand-rolled: --shadow-sm/md/lg all fall downward, and a sheet needs one
     that falls upward. Same blur and colour token as --shadow-md. */
  box-shadow: 0 -8px 32px var(--black-a08);
  /* Contain the last child's bottom margin inside the card. Otherwise it
     collapses out below the card's white background, the layout ends below
     the card, and the pinned gallery shows through in that gap. */
  display: flow-root;
}
/* Below 992px the sheet runs the full viewport width, out of .container-custom's
   side padding (calc(50% - 50vw) cancels whatever that padding is at each
   width), and carries its own padding so its content never meets the edge. */
@media (max-width: 991px) {
  .info-panel[b-uqdrro0y11] {
    width: 100vw;
    margin-inline: calc(50% - 50vw);
    padding-inline: var(--space-16);
  }
}
@media (min-width: 992px) {
  .info-panel[b-uqdrro0y11] {
    position: sticky;
    z-index: auto;
    top: calc(var(--header-height) + var(--space-18));
    background: none;
    border-radius: 0;
    margin-top: 0;
    padding-top: 0;
    box-shadow: none;
  }
}

.more-link[b-uqdrro0y11] {
  display: inline-block;
  color: var(--text-muted);
  text-decoration: none;
  font-size: var(--text-0-84);
  font-weight: 500;
  margin-bottom: var(--space-4);
}

.brand[b-uqdrro0y11] {
  font-size: var(--text-1);
  font-weight: 800;
  letter-spacing: 1.5px;
  margin-bottom: var(--space-6);
}

.title[b-uqdrro0y11] {
  font-size: var(--text-1-25);
  line-height: var(--leading-1-35);
  font-weight: 600;
  color: var(--text-dark);
  margin-bottom: var(--space-10);
}

/* The product code under the title (Story 28.5, Product Owner, 2026-10-15):
   small and muted. When the line follows the title, the title gives up its
   bottom margin to a small gap and the line carries the original gap, so the
   space below the whole block is unchanged and a product with no code lays
   out exactly as before. */
.title:has(+ .product-code)[b-uqdrro0y11] {
  margin-bottom: var(--space-4);
}

.product-code[b-uqdrro0y11] {
  margin: 0 0 var(--space-10);
  font-size: var(--text-0-78);
  color: var(--text-muted);
}

/* The rating pill's rules are gone with the pill. Ratings and reviews are out
   of scope for this release, and a stylesheet that still knows how to draw one
   is how the markup comes back (Story 3.1). */

/* .price-row, .price, .mrp, .discount, .sale-badge and .tax-note moved to the
   global wwwroot/css/app.css (Story 16.10), for the same reason Story 16.8
   moved the swatch/size-btn rules there: this component's own scoped CSS is
   attributed only to markup THIS component renders directly, and that price
   row now renders inside ProductPurchaseIsland — a WASM island in a separate
   project — for every dimensioned product. A scoped rule here would silently
   stop applying to it while still (confusingly) applying to a
   non-dimensioned product's own directly-rendered PriceRow. One global
   definition applies identically to both. */

.block[b-uqdrro0y11] {
  border-top: 1px solid var(--neutral-200);
  padding: var(--space-18) 0;
}
.block h2[b-uqdrro0y11] {
  font-size: var(--text-0-88);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  margin-bottom: var(--space-12);
}
/* .mt and .spec-grid went with the Size & Fit, Material & Care and
   Specifications blocks in Story 3.3: the gateway has no field that could fill
   them, so the rules have nothing left to style. .price-detail-list went the
   same way when the Price Details block itself was removed (2026-09-30
   request): it repeated the hero price row's own two figures. */

.actions[b-uqdrro0y11] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-10);
  margin: var(--space-18) 0;
}
@media (min-width: 576px) {
  .actions[b-uqdrro0y11] {
    grid-template-columns: 1fr 1fr;
  }
}

.primary-btn[b-uqdrro0y11],
.secondary-btn[b-uqdrro0y11] {
  border: none;
  border-radius: 4px;
  padding: var(--space-14) var(--space-18);
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  transition: var(--transition);
  cursor: pointer;
}
.primary-btn[b-uqdrro0y11] {
  background: var(--primary-black);
  color: var(--white);
}
.primary-btn:hover[b-uqdrro0y11] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}
.secondary-btn[b-uqdrro0y11] {
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-400);
}

/* The wishlist-toggle form's own status line (code review finding A9/house
   rules: this class shipped with no rules anywhere, so the message rendered
   unstyled). Design tokens only, matching the returns-note copy below in
   size and colour. */
.wishlist-toggle-message[b-uqdrro0y11] {
  margin-top: var(--space-8);
  font-size: var(--text-0-82);
  color: var(--text-dark);
}

.copy-list[b-uqdrro0y11] {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--space-9);
  font-size: var(--text-0-84);
  color: var(--text-dark);
}
/* The assurance list moved to PurchaseAssurances.razor.css with the markup it
   styles (Story 3.6): scoped CSS is scoped to the component that renders the
   element, so a rule left behind here would stop applying rather than fail
   loudly. */

/* The size chart (Story 17.5), which replaced the returns-guidance link this
   rule used to style (same shape — an icon plus one line of text — hence the
   rename rather than a second near-identical rule). It reads as a note rather
   than as an assurance because it is not one — it opens the size guide, it
   does not promise anything about the product. */
.size-chart-note[b-uqdrro0y11] {
  margin: var(--space-9) 0 0;
  font-size: var(--text-0-84);
  color: var(--text-dark);
}
.size-chart-note i[b-uqdrro0y11] {
  color: var(--yellow-accent);
  margin-right: var(--space-8);
}
.size-chart-trigger[b-uqdrro0y11] {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-weight: 700;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.size-chart-trigger:hover[b-uqdrro0y11],
.size-chart-trigger:focus-visible[b-uqdrro0y11] {
  color: var(--yellow-accent);
}

/* The size-guide dialog (Story 17.5). A native <dialog>, opened with
   showModal() by the trigger above: that call is what supplies the backdrop,
   the focus trap and Escape-to-close, none of which a hand-rolled overlay div
   gets without script written here to reimplement them. */
.size-guide-dialog[b-uqdrro0y11] {
  border: none;
  border-radius: var(--radius-xs);
  padding: var(--space-18);
  max-width: min(92vw, 520px);
  max-height: 90vh;
}
/* Phones: the size guide fills the screen (Epic 32, Product Owner request). */
@media (max-width: 767px) {
  .size-guide-dialog[b-uqdrro0y11] {
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    max-width: 100vw;
    max-height: 100vh;
    max-height: 100dvh;
    margin: 0;
    inset: 0;
    border-radius: 0;
  }
}
.size-guide-dialog[b-uqdrro0y11]::backdrop {
  /* Colour belongs to the token layer (ComponentStylesheetTests), not a
     literal here — the closest of the shared black-alpha scale to the 60%
     scrim a modal backdrop wants. */
  background: var(--black-a58);
}
.size-guide-dialog__close-row[b-uqdrro0y11] {
  display: flex;
  justify-content: flex-end;
  margin: 0 0 var(--space-9);
}
.size-guide-dialog__close[b-uqdrro0y11] {
  background: none;
  border: none;
  font-size: var(--text-1-5);
  line-height: 1;
  cursor: pointer;
  color: var(--text-dark);
}
.size-guide-dialog img[b-uqdrro0y11] {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
}

.columns[b-uqdrro0y11] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-20);
}
/* One track at every width, because there is one section left to render. A
   repeat(3, 1fr) reserved a track for every section whether it rendered or not,
   so a product carrying only a description got a third-width block beside two
   empty columns. */

/* The rating summary, the histogram, the customer-photo strip, the review cards
   and the comment box all went with the regions they styled. Ratings, reviews
   and comments are out of scope for this release and are gone from the markup,
   the contract and this stylesheet together. */

/* A hierarchy name the live hierarchy does not route renders as a label rather
   than a link — the same state, and the same marker, the mega menu uses. */
.is-unmatched[b-uqdrro0y11] {
  color: var(--text-muted);
  cursor: default;
}

.rec-section[b-uqdrro0y11] {
  padding: var(--space-30) 0;
  background: var(--white);
}

/* The Product Owner wants Similar Items and Customers Also Buy to read "Add"
   (Story 16.3), the same treatment New Arrivals got in Story 15.2.
   ProductCard.razor.css sets `.card .add-btn { text-transform: lowercase; }`,
   so passing AddLabel="Add" from ItemDetails.razor alone is not enough — the
   card's own rule still lowercases it visually. ProductCard is shared by
   every listing, rail and grid and is not touched here.

   `::deep` reaches into the ProductCard each carousel renders using an
   element ItemDetails itself emits for both rails — `.rec-section` — so the
   scope attribute lands on `.rec-section` and nothing after `::deep` is
   scoped at all. Compiled, this rule is `.rec-section[b-xxxx] .card .add-btn`
   — four classes/attributes — against the card's own compiled
   `.card .add-btn[b-yyyy]` — three. Higher specificity wins outright, so this
   does not depend on which stylesheet the bundle concatenates last. Flash
   Sale is untouched and keeps lowercase "add". */
.rec-section[b-uqdrro0y11]  .card .add-btn {
  text-transform: none;
}

/* Uniform mobile page rhythm. */
@media (max-width: 991px) {
  .layout[b-uqdrro0y11] {
    padding: var(--space-8) 0 var(--space-28);
  }
  .rec-section[b-uqdrro0y11] {
    padding: var(--space-18) 0;
  }

  /* Story 16.2: the same tightened top space the Back control's own
     .back-link + .breadcrumb-line rule (app.css) gives desktop, scaled to this page's
     mobile rhythm. */
  .back-link[b-uqdrro0y11] {
    margin-top: var(--space-8);
  }

  /* Story 15.4's "see more images" checkbox disclosure lived in this block —
     .gallery-more, .gallery-toggle, .gallery-toggle-label, .gallery-extra —
     and is gone as of Story 17.3: every image now gets its own thumbnail in
     the rail (see `.gallery-rail-layout` above), so there is no
     primary/extra split left for a disclosure to collapse.
     MobileGallerySeeMoreImagesTests.cs, which asserted this block, is
     deleted with it rather than left asserting removed behaviour. */
}

/* Story 26.7: the "not available in your country" page. One calm sentence where
   the price and the purchase area would be; no price, no buttons. */
.country-unavailable[b-uqdrro0y11] {
  margin-top: var(--space-16);
  padding: var(--space-16);
  border: 1px solid var(--subtle-gray);
  border-radius: var(--radius-minimal);
  background: var(--neutral-100);
  font-size: var(--text-1);
}
/* _content/Odel.Web/Pages/Leaving.razor.rz.scp.css */
/* The departure interstitial (Story 1.15). Tokens only, and no breakpoint of
   its own: one centred column that already reads at every width. */

.leaving[b-a85guuvdvx] {
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
  padding-top: var(--space-60);
  padding-bottom: var(--space-60);
  text-align: center;
}

.leaving-title[b-a85guuvdvx] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-1-9);
  line-height: var(--leading-1-2);
  color: var(--primary-black);
  margin-bottom: var(--space-16);
}

.leaving-body[b-a85guuvdvx] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-95);
  line-height: var(--leading-1-6);
  color: var(--text-muted);
  margin-bottom: var(--space-28);
}

.leaving-loss[b-a85guuvdvx] {
  background: var(--yellow-light);
  border: 1px solid var(--yellow-a30);
  border-radius: var(--radius-minimal);
  padding: var(--space-20) var(--space-24);
  margin-bottom: var(--space-32);
  text-align: left;
}

.leaving-loss-title[b-a85guuvdvx] {
  display: block;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-75);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold-dark);
  margin-bottom: var(--space-8);
}

.leaving-loss-body[b-a85guuvdvx] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: 0;
}

.leaving-actions[b-a85guuvdvx] {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--space-16);
}

.leaving-go[b-a85guuvdvx] {
  background: var(--primary-black);
  color: var(--white);
  border-radius: 25px;
  padding: var(--space-12) var(--space-28);
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  font-weight: 600;
  text-decoration: none;
  transition: var(--transition);
}

.leaving-go:hover[b-a85guuvdvx] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

.leaving-stay[b-a85guuvdvx] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  font-weight: 500;
  color: var(--text-muted);
  text-decoration: underline;
}

.leaving-stay:hover[b-a85guuvdvx] {
  color: var(--primary-black);
}
/* _content/Odel.Web/Pages/Login.razor.rz.scp.css */
/* The card is the shared one (`account-card` in wwwroot/css/app.css). What is
   left here is what belongs to these two forms alone: the tab strip that
   chooses between them, the "check your email" panel, the recovery link, the
   terms checkbox, and the two places this page genuinely differs from the
   shared card. */

.tabs[b-ynkkr19llu] {
  display: flex;
  margin-bottom: var(--space-24);
  border-bottom: 1px solid var(--neutral-200);
}

.tab[b-ynkkr19llu] {
  /* Anchors now, so the two forms are reachable without script. */
  display: block;
  text-decoration: none;
  flex: 1;
  text-align: center;
  padding: var(--space-12) var(--space-8);
  font-weight: 600;
  font-size: var(--text-0-9);
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-muted);
  cursor: pointer;
  transition: var(--transition);
  border-bottom: 3px solid transparent;
}
.active[b-ynkkr19llu] {
  color: var(--primary-black);
  border-bottom-color: var(--yellow-accent);
}

/* The one surface a registration submission ends on, whether the gateway
   created an account or refused one. It is styled as an ordinary panel rather
   than as a success banner: "we sent it if we could" is not a confirmation, and
   a tick would be the page claiming something it does not know. */
.check-email h2[b-ynkkr19llu] {
  margin-bottom: var(--space-8);
}
.check-email p[b-ynkkr19llu] {
  margin-bottom: var(--space-12);
}

.forgot[b-ynkkr19llu] {
  font-size: var(--text-0-78);
  color: var(--primary-black);
  text-decoration: underline;
  font-weight: 500;
  float: right;
  margin-top: var(--space-4);
}

/* This form's button follows a field group rather than a paragraph, so it needs
   the gap the shared card does not. */
.submit-btn[b-ynkkr19llu] {
  margin-top: var(--space-12);
}

.terms-check[b-ynkkr19llu] {
  display: flex;
  align-items: flex-start;
  gap: var(--space-8);
  font-size: var(--text-0-78);
  color: var(--text-muted);
  margin: var(--space-12) 0;
}
.terms-check a[b-ynkkr19llu] {
  color: var(--primary-black);
  font-weight: 600;
}
.terms-check label[b-ynkkr19llu] {
  margin: 0;
}
/* "Remember me" sits under the floated forgot-password link; clearing it keeps
   the flex row at full width instead of squeezed beside the float. */
.remember-check[b-ynkkr19llu] {
  clear: both;
}
/* _content/Odel.Web/Pages/OrderConfirmation.razor.rz.scp.css */
/* The order confirmation (Story 7.5). Tokens only, one centred column, and no
   breakpoint of its own — it is a page of sentences and figures and it reads
   at every width without one, which is what makes 360px free of horizontal
   scroll rather than something to tune.

   Deliberately NOT styled as a celebration. No tick, no green panel, no
   confetti — and, per the epic, no points badge, no rewards strip and no
   wheel. Today's gateway states no resolution, so the payment section on this
   page almost always reads "we cannot confirm this payment yet"; a page
   dressed as a success over that sentence would say with its styling what its
   words carefully do not.

   The order's own sections are styled in Components/Orders/OrderSummary.razor.css,
   which is where their markup lives. */

.order-confirmation[b-lfr6s5nmlf] {
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
  padding-top: var(--space-40);
  padding-bottom: var(--space-40);
}

.order-confirmation-heading[b-lfr6s5nmlf] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-1-6);
  line-height: var(--leading-1-2);
  color: var(--primary-black);
  margin-bottom: var(--space-16);
}

.order-confirmation-body[b-lfr6s5nmlf] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-95);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: var(--space-28);
}

.order-confirmation-payment[b-lfr6s5nmlf],
.order-confirmation-gap[b-lfr6s5nmlf] {
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-20);
  margin-bottom: var(--space-24);
}

.order-confirmation-label[b-lfr6s5nmlf] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-75);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-8);
}

/* The payment claim is the densest thing on the page and is set as plain
   emphasis rather than as a status chip: a chip has a colour, and a colour
   here would be a verdict the wording refuses to give. */
.order-confirmation-payment-heading[b-lfr6s5nmlf] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-1-1);
  font-weight: 600;
  color: var(--primary-black);
  margin-bottom: var(--space-8);
}

.order-confirmation-payment-body[b-lfr6s5nmlf],
.order-confirmation-gap-body[b-lfr6s5nmlf] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: 0;
}

.order-confirmation-gap-body[b-lfr6s5nmlf] {
  color: var(--text-muted);
}

.order-confirmation-away[b-lfr6s5nmlf] {
  margin-top: var(--space-32);
  margin-bottom: 0;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  font-weight: 500;
}

.order-confirmation-away a[b-lfr6s5nmlf] {
  color: var(--text-muted);
  text-decoration: underline;
}

.order-confirmation-away a:hover[b-lfr6s5nmlf] {
  color: var(--primary-black);
}
/* _content/Odel.Web/Pages/OrderDetail.razor.rz.scp.css */
/* The order detail (Story 8.2). Tokens only — the same design system as the
   confirmation's (OrderConfirmation.razor.css), which this page mirrors
   deliberately: both draw the same PlacedOrder through the same OrderSummary,
   and a Shopper moving between "just paid" and "looking it up later" should
   not read two different visual languages for one concept.

   The order's own sections are styled in
   Components/Orders/OrderSummary.razor.css, which is where their markup
   lives — this file only styles what the page itself draws around it. */

.order-detail[b-tahl1tjqaa] {
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
  padding-top: var(--space-24);
  padding-bottom: var(--space-40);
}

.order-detail-heading[b-tahl1tjqaa] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-1-6);
  line-height: var(--leading-1-2);
  color: var(--primary-black);
  margin-bottom: var(--space-16);
}

.order-detail-body[b-tahl1tjqaa] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-95);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: var(--space-28);
}

.order-detail-gap[b-tahl1tjqaa] {
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-20);
  margin-bottom: var(--space-24);
}

.order-detail-label[b-tahl1tjqaa] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-75);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-8);
}

.order-detail-gap-body[b-tahl1tjqaa] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-muted);
  margin-bottom: 0;
}

.routes[b-tahl1tjqaa] {
  margin-top: var(--space-20);
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
}


/* Where the order has got to (Story 8.3). Deliberately the same visual
   language as .order-detail-gap above, and for a reason worth stating: this
   section is mostly an admission too. It carries one status the gateway
   worded, and a sentence saying there is no history behind it. Styling it as
   a progress surface — a rail, a row of dots, a bar — would imply a sequence
   this system has never been observed to produce. */

.order-detail-tracking[b-tahl1tjqaa] {
  border-top: 1px solid var(--neutral-200);
  padding-top: var(--space-20);
  margin-bottom: var(--space-24);
}

.order-detail-tracking-state[b-tahl1tjqaa] {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  align-items: baseline;
  font-family: 'Poppins', sans-serif;
  margin-bottom: var(--space-12);
}

.order-detail-tracking-state-label[b-tahl1tjqaa] {
  font-size: var(--text-0-75);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The gateway's own wording, shown as sent. No state-derived colour and no
   state-derived class: the vocabulary is open (register §A-23), so a palette
   keyed to "Delivered" / "Cancelled" / "Proceeded" would re-encode a closed
   set in styling that the adapter deliberately refuses to encode in code. */
.order-detail-tracking-state-value[b-tahl1tjqaa] {
  font-size: var(--text-1);
  line-height: var(--leading-1-4);
  color: var(--primary-black);
}
/* _content/Odel.Web/Pages/OrderHistory.razor.rz.scp.css */
/* Story 8.1: "See my orders". Tokens only, same design system as
   Wishlist.razor.css's account cards — no new colour, spacing or radius
   value is introduced here. */

.empty-orders[b-3ze56d3go6] {
  text-align: center;
  padding: var(--space-60) var(--space-20);
  color: var(--text-muted);
}
.empty-orders i[b-3ze56d3go6] {
  font-size: var(--text-3);
  margin-bottom: var(--space-16);
  opacity: 0.4;
}
.empty-orders p[b-3ze56d3go6] {
  margin-bottom: var(--space-18);
}
.discover-btn[b-3ze56d3go6] {
  display: inline-block;
  background: var(--primary-black);
  color: var(--white);
  text-decoration: none;
  padding: var(--space-11) var(--space-28);
  border-radius: 25px;
  font-size: var(--text-0-82);
  font-weight: 600;
  transition: var(--transition);
}
.discover-btn:hover[b-3ze56d3go6] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}

.orders-group[b-3ze56d3go6] {
  margin-bottom: var(--space-28);
}
.orders-group-label[b-3ze56d3go6] {
  font-weight: 800;
  font-size: var(--text-0-9);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  margin-bottom: var(--space-16);
  color: var(--text-dark);
}
.orders-group-list[b-3ze56d3go6] {
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}
.order-row[b-3ze56d3go6] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  flex-wrap: wrap;
  background: var(--white);
  border: 1px solid var(--subtle-gray);
  border-radius: var(--radius-sm);
  padding: var(--space-12) var(--space-14);
  /* A row is an <a> when it opens something (Story 8.2) and a <div> when it
     does not (no readable reference to open it by) — same box either way. */
  text-decoration: none;
  color: inherit;
}
a.order-row:hover[b-3ze56d3go6] {
  border-color: var(--primary-black);
}
.order-reference[b-3ze56d3go6] {
  font-weight: 700;
  font-size: var(--text-0-85);
}
.order-date[b-3ze56d3go6] {
  font-size: var(--text-0-75);
  color: var(--text-muted);
}
.order-total[b-3ze56d3go6] {
  font-weight: 700;
  font-size: var(--text-0-85);
}

.routes[b-3ze56d3go6] {
  margin-top: var(--space-20);
}
/* _content/Odel.Web/Pages/Payment.razor.rz.scp.css */
/* The payment return (Story 7.4). Tokens only, one centred column, and no
   breakpoint of its own — it is a page of sentences and figures, and it reads
   at every width without one.

   Deliberately NOT styled as a success. There is no tick, no green panel and
   no celebratory anything, because the resolution this page almost always
   carries is "we cannot confirm this yet" and a surface that looked like a
   confirmation would undo the wording above it. The one state that IS a defect
   to act on — figures that do not sum, an order with nothing on it — is the
   only thing here drawn in the danger tokens.

   The reference, figures and line rules left this file with the markup they
   style, into Components/Orders/OrderSummary.razor.css (Story 7.5). Blazor's
   scoped CSS is per-component, so a rule here would no longer reach them. */

.payment-return[b-kiuc1se59f] {
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
  padding-top: var(--space-40);
  padding-bottom: var(--space-40);
}

.payment-return-heading[b-kiuc1se59f] {
  font-family: 'Playfair Display', serif;
  font-size: var(--text-1-6);
  line-height: var(--leading-1-2);
  color: var(--primary-black);
  margin-bottom: var(--space-16);
}

.payment-return-body[b-kiuc1se59f] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-95);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: var(--space-28);
}

.payment-return-unread[b-kiuc1se59f] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--danger);
  background: var(--danger-bg);
  border: 1px solid var(--danger-a12);
  border-radius: var(--radius-minimal);
  padding: var(--space-12) var(--space-16);
  margin-bottom: var(--space-28);
}

.payment-return-away[b-kiuc1se59f] {
  margin-top: var(--space-32);
  margin-bottom: 0;
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-85);
  font-weight: 500;
}

.payment-return-away a[b-kiuc1se59f] {
  color: var(--text-muted);
  text-decoration: underline;
}

.payment-return-away a:hover[b-kiuc1se59f] {
  color: var(--primary-black);
}
/* _content/Odel.Web/Pages/PrivacyPolicy.razor.rz.scp.css */
/* The Privacy Policy's introduction (Epic 29 story 29-7). The accordion panels
   carry their own styling (Components/Info/InfoAccordion.razor.css). */
.privacy-intro[b-h6xx6187yg] {
  font-family: 'Poppins', sans-serif;
  font-size: var(--text-0-9);
  line-height: var(--leading-1-6);
  color: var(--text-dark);
  margin-bottom: var(--space-24);
}

.privacy-intro p[b-h6xx6187yg] {
  margin-bottom: var(--space-12);
}
/* _content/Odel.Web/Pages/RegisterConfirm.razor.rz.scp.css */
/* The card is the shared one (`account-card` in wwwroot/css/app.css): the same
   card the sign-in and register forms live in, so a shopper who lands here from
   their mailbox is on a page that plainly belongs to the flow they started.
   That sentence used to sit above a copy of the card; it now sits above the
   reason there is only one. */
/* _content/Odel.Web/Pages/ResetPassword.razor.rz.scp.css */
/* The card itself is the shared one (`account-card` in wwwroot/css/app.css),
   including the hint that states the password policy — which is shared because
   the register form states the same rule in the same place. What is left here
   is what belongs to this page alone. */
/* _content/Odel.Web/Pages/Search.razor.rz.scp.css */
/* The search surface's own layout, and nothing else. The pager, the count and
   the empty state moved into Components/Listing with their rules (Story 2.1);
   a scoped rule for markup this page no longer emits is dead CSS. */
.search-results[b-ox4lq54pof] {
    padding: var(--space-24) 0 var(--space-60);
    min-height: 60vh;
}

/* The strip above the results: the count and the sort control, both shared
   components (Stories 2.1 and 2.5). The same slot the category and brand
   listings use. */
.toolbar[b-ox4lq54pof] {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-10);
    margin-bottom: var(--space-16);
}

/* The two-column listing layout the search surface gained with the facet panel
   (Story 2.4). One column on a phone, where the panel is a sheet rather than a
   sidebar; the same proportions the category and brand listings use, so the
   three surfaces read as one storefront. */
.content[b-ox4lq54pof] {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-24);
}
@media (min-width: 992px) {
  .content[b-ox4lq54pof] {
    grid-template-columns: 240px 1fr;
    gap: var(--space-30);
  }
}

.products[b-ox4lq54pof] {
  min-width: 0;
}

/* With no facets to offer the panel renders nothing at all, and the grid would
   otherwise keep an empty 240px column beside the results. :only-child is the
   test, because "the panel rendered nothing" and "the products are the only
   child" are the same fact. */
.products:only-child[b-ox4lq54pof] {
  grid-column: 1 / -1;
}
/* _content/Odel.Web/Pages/VerifyMobile.razor.rz.scp.css */
/* The card is the shared one (`account-card` in wwwroot/css/app.css). What is
   left here is what belongs to this step alone: the code input's letter
   spacing, the secondary "send me a code" button, and the resend countdown. */

/* A one-time code is read back a character at a time, so it is set apart. */
.group input[b-4vd09mk4tc] {
  letter-spacing: 2px;
}

/* Asking for a code is the secondary action on this page — the primary one is
   entering the code the shopper already has — so it does not compete with it. */
.submit-btn.secondary[b-4vd09mk4tc] {
  background: var(--white);
  color: var(--primary-black);
  border: 1px solid var(--neutral-400);
}
.submit-btn.secondary:hover[b-4vd09mk4tc] {
  background: var(--off-white);
  color: var(--primary-black);
}

.send[b-4vd09mk4tc] {
  margin-bottom: var(--space-12);
}

.resend[b-4vd09mk4tc] {
  font-size: var(--text-0-78);
  color: var(--text-muted);
  margin-bottom: var(--space-16);
}
/* _content/Odel.Web/Pages/Wishlist.razor.rz.scp.css */
/* The wishlist page. Adapted from acc-wishlist.html's inline styles. */
.container[b-cqne7iy59m] {
  padding: var(--space-24) 0 var(--space-40);
}

/* The tab strip (My Account / Wishlist), the badge it carried, the Profile
   Information card and the Orders link all stood here until Story 13.18
   deleted the account-cards half of this page — AccountTabs is the
   navigation now, and /account and /orders are each one click away, so a
   second copy of either was drift the moment the tab strip existed. Their
   rules went with the markup: a rule with nothing left to match is exactly
   the dead code the removal was for. See Wishlist.razor's LoadData remarks. */

/* ===== Wishlist grid ===== */
.wishlist-grid[b-cqne7iy59m] {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-12);
}
@media (min-width: 768px) {
  .wishlist-grid[b-cqne7iy59m] {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 992px) {
  .wishlist-grid[b-cqne7iy59m] {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* Story 32.20: the card is the shared ProductCard, so the grid only places it.
   The wrapper takes no box of its own: the card is the grid item. */
.wishlist-item[b-cqne7iy59m] {
  display: contents;
}

/* The remove button, a round white X at the top-left of the photo, clear of the
   discount badge (top-right) and the hover strip. It is rendered by this page
   but sits inside ProductCard's photo box; z-index 3 puts it above the card link
   and below the size panel. */
.remove-wish[b-cqne7iy59m] {
  position: absolute;
  top: var(--space-8);
  left: var(--space-8);
  z-index: 3;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--white);
  color: var(--primary-black);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--shadow-md);
  transition: var(--transition);
}
.remove-wish:hover[b-cqne7iy59m] {
  background: var(--yellow-accent);
}

/* The removal form's own error line (house rules: this class shipped with no
   rules anywhere, so it rendered unstyled). Design tokens only, matching the
   danger vocabulary Checkout.razor.css and Payment.razor.css already use. */
.wishlist-remove-error[b-cqne7iy59m] {
  margin-top: var(--space-12);
  font-size: var(--text-0-85);
  color: var(--danger);
}

}

.empty-wishlist[b-cqne7iy59m] {
  text-align: center;
  padding: var(--space-60) var(--space-20);
  color: var(--text-muted);
}
.empty-wishlist i[b-cqne7iy59m] {
  font-size: var(--text-3);
  margin-bottom: var(--space-16);
  opacity: 0.4;
}
.empty-wishlist p[b-cqne7iy59m] {
  margin-bottom: var(--space-18);
}
.discover-btn[b-cqne7iy59m] {
  display: inline-block;
  background: var(--primary-black);
  color: var(--white);
  text-decoration: none;
  padding: var(--space-11) var(--space-28);
  border-radius: 25px;
  font-size: var(--text-0-82);
  font-weight: 600;
  transition: var(--transition);
}
.discover-btn:hover[b-cqne7iy59m] {
  background: var(--yellow-accent);
  color: var(--primary-black);
}
/* Uniform mobile page rhythm. */
@media (max-width: 991px) {
  .container[b-cqne7iy59m] {
    padding: var(--space-14) 0 var(--space-28);
  }
}

.wishlist-country-unavailable[b-cqne7iy59m] {
  margin: var(--space-8, 8px) 0 0;
  font-size: var(--text-0-85);
  color: var(--text-primary, inherit);
}
