/* ============================================================================
   Control primitives
   ----------------------------------------------------------------------------
   Single source of truth for every interactive control in the theme: buttons,
   form fields and dropdowns. Feature stylesheets must NOT restyle a control
   from scratch — they compose these primitives and add only layout (where a
   control sits, how wide it is in that grid).

   Each primitive lists its canonical class first, then the feature class names
   that are the same control. CSS has no @extend, so grouping the selectors in
   one rule is how one definition serves every surface: change the pill here and
   it changes on the home card, the category card, the product page and the
   filter panel at once.

   Adding a control:
     1. add its class to the matching primitive's selector list below;
     2. put only its positioning in the feature stylesheet.
   See docs/style-guide.md.
   ========================================================================== */

/* ---- Buttons -------------------------------------------------------------- */

/* Pill: storefront conversion actions specifically — add to cart and cart
   checkout. These are the small set of buttons a shopper's whole visit is
   built around, so they carry the theme's loudest treatment: full-height,
   generously padded, bold, uppercase. Nothing else should join this list
   merely for sharing the amber fill — see `.button-1` below for nopCommerce's
   generic primary-action class, which shares the colour but not this size or
   weight. Applying a coupon or gift card code sits beside a field, so it uses
   the compact amber `.button-1` shape below, matching the field's height. */
.btn,
.btn--primary,
.book-card__add-to-cart,
.cart-item__add-to-cart,
.filter-drawer__apply,
.search-button,
.puzzle-die,
.book-detail__add-to-cart .add-to-cart-button,
.shopping-cart-page .checkout-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: var(--action-height);
  padding-inline: var(--space-md);
  border: 0;
  border-radius: var(--radius-pill);
  background-color: var(--color-action);
  color: var(--color-ink);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
}

/* Round step arrow: the home hero's previous and next slide, and the puzzle
   board's moves back and forward, which do the same job on another surface. */
.home-hero__nav,
.puzzle-play__step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--hero-nav-size);
  min-width: var(--tap-target);
  height: var(--hero-nav-size);
  min-height: var(--tap-target);
  border: 0;
  background-color: transparent;
  color: var(--color-ink);
}

.home-hero__nav::before,
.puzzle-play__step::before {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--hero-nav-visual-size);
  height: var(--hero-nav-visual-size);
  border: var(--stroke-hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  background-color: var(--color-canvas);
  content: "";
  transform: translate(-50%, -50%);
}

.home-hero__nav img,
.puzzle-play__step img {
  position: relative;
  z-index: 1;
  width: var(--space-md);
  height: var(--space-md);
}

/* Compact primary: every OTHER primary action sitewide.
   `.button-1` is nopCommerce's own generic primary-action class, applied by
   60 otherwise-untouched core views (contact us, GDPR tools, password
   recovery, forums, vendor application, saving an address...). It shares the
   hero action's colour, but uses a conventional technical-control shape and
   density. A "Submit" or "Save" button next to a plain form should not carry
   the same visual volume as "Add to cart". */
.button-1,
.ui-dialog .wishlist-add-custom-button,
.confirm-dialog__confirm,
.shopping-cart-page .apply-discount-coupon-code-button,
.shopping-cart-page .apply-gift-card-coupon-code-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: var(--tap-target);
  padding-inline: var(--space-sm);
  border: 0;
  border-radius: var(--radius-sm);
  background-color: var(--color-action);
  color: var(--color-ink);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  text-decoration: none;
  cursor: pointer;
}

/* Compact outlined toolbar action. The catalog's filter and sort triggers use
   it at their responsive breakpoints. */
.filter-drawer-toggle,
.catalog-sort__toggle {
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-width: 0;
  min-height: var(--tap-target);
  padding-inline: var(--space-sm);
  border: var(--stroke-hairline) solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-canvas);
  color: var(--color-ink);
  font: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  cursor: pointer;
}

/* Explicit storefront conversion controls retain the approved pill treatment
   even when nopCommerce also assigns its generic `.button-1` class. */
.btn,
.btn--primary,
.book-card__add-to-cart,
.cart-item__add-to-cart,
.filter-drawer__apply,
.search-button,
.puzzle-die,
.book-detail__add-to-cart .add-to-cart-button,
.shopping-cart-page .checkout-button {
  min-height: var(--action-height);
  padding-inline: var(--space-md);
  border-radius: var(--radius-pill);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
}

/* The card add-to-cart action is the cart icon alone in an amber circle, at
   every width and on every card surface (home, category, search) and on each
   favourites row. The label stays in the markup, visually hidden, so it
   remains the accessible name. */
.book-card__add-to-cart,
.cart-item__add-to-cart {
  width: var(--action-height);
  min-width: 0;
  height: var(--action-height);
  padding: 0;
}

.book-card__cart-icon {
  flex-shrink: 0;
}

.book-card__add-to-cart-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

/* Shared asynchronous state for the same add-to-cart action on cards, the
   favourites and the product page. The control keeps its width while its contents yield to a
   spinner, then a checkmark. */
.book-card__add-to-cart.is-adding,
.cart-item__add-to-cart.is-adding,
.book-card__add-to-cart.is-added,
.cart-item__add-to-cart.is-added,
.book-detail__add-to-cart .add-to-cart-button.is-adding,
.book-detail__add-to-cart .add-to-cart-button.is-added {
  color: transparent;
}

.book-card__add-to-cart.is-adding > *,
.cart-item__add-to-cart.is-adding > *,
.book-card__add-to-cart.is-added > * {
  opacity: 0;
}

.book-card__add-to-cart.is-adding::after,
.cart-item__add-to-cart.is-adding::after,
.book-card__add-to-cart.is-added::after,
.cart-item__add-to-cart.is-added::after,
.book-detail__add-to-cart .add-to-cart-button.is-adding::after,
.book-detail__add-to-cart .add-to-cart-button.is-added::after {
  position: absolute;
  content: "";
  color: var(--color-ink);
}

.book-card__add-to-cart.is-adding::after,
.cart-item__add-to-cart.is-adding::after,
.book-detail__add-to-cart .add-to-cart-button.is-adding::after {
  width: var(--status-icon-size);
  height: var(--status-icon-size);
  border: var(--stroke-control) solid currentColor;
  border-right-color: transparent;
  border-radius: var(--radius-pill);
  animation: cart-spin var(--motion-switch) linear infinite;
}

.book-card__add-to-cart.is-added,
.cart-item__add-to-cart.is-added,
.book-detail__add-to-cart .add-to-cart-button.is-added {
  animation: cart-confirm var(--motion-tile) var(--ease-out);
}

.book-card__add-to-cart.is-added::after,
.cart-item__add-to-cart.is-added::after,
.book-detail__add-to-cart .add-to-cart-button.is-added::after {
  width: var(--status-icon-size);
  height: calc(var(--status-icon-size) / 2);
  border-bottom: var(--stroke-control) solid currentColor;
  border-left: var(--stroke-control) solid currentColor;
  transform: rotate(-45deg);
}

@keyframes cart-spin {
  to {
    transform: rotate(1turn);
  }
}

@keyframes cart-confirm {
  50% {
    opacity: 0.72;
  }
}

/* Outline: supporting actions that must remain visible without competing with
   the primary conversion action.

   `.button-2` is nopCommerce's generic secondary-action counterpart to
   `.button-1` above — compact Edit/Delete/Cancel controls across technical
   views. A page that wants a `.button-2` to read as quiet text instead
   overrides it below with a more specific selector. */
.btn--outline,
.button-2,
.page-notice .button,
.shopping-cart-page .rfq-button-2,
.confirm-dialog__cancel,
.puzzle-promotion__choice {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: var(--tap-target);
  padding-inline: var(--space-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-canvas);
  color: var(--color-ink);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  text-decoration: none;
  cursor: pointer;
}

.confirm-dialog--danger .confirm-dialog__confirm {
  background-color: var(--color-danger);
  color: var(--color-footer-ink);
}

/* Quiet: a secondary action that reads as text. */
.btn--quiet,
.filter-drawer__clear,
.puzzle-solution__summary,
.overview-buttons button,
.shopping-cart-page .continue-shopping-button,
.shopping-cart-page .estimate-shipping-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: var(--space-3xs) 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  text-transform: none;
  text-decoration: underline;
  text-underline-offset: 0.25rem;
  cursor: pointer;
}

/* Outline pill: the secondary partner of an amber pill on the same row. It
   takes the pill's height, radius and weight and drops only the fill, so the
   pair reads as one set with one primary, where quiet text beside a pill
   would read as an afterthought. Below the desktop breakpoint, the filter
   drawer's Clear beside Apply; on the desktop card, under Apply, it stays
   quiet. */
@media (max-width: 61.9375rem) {
  .filter-drawer__clear {
    min-height: var(--action-height);
    padding-inline: var(--space-sm);
    border: var(--stroke-hairline) solid var(--color-border);
    border-radius: var(--radius-pill);
    background-color: var(--color-canvas);
    color: var(--color-ink);
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    text-decoration: none;
  }
}

/* Close buttons in the mobile navigation and filter drawer share one visible,
   keyboard-sized icon control. Feature stylesheets only decide where the
   control sits; its hit area and X glyph stay identical. */
.drawer-close {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-target);
  min-width: var(--tap-target);
  height: var(--tap-target);
  min-height: var(--tap-target);
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background-color: var(--color-canvas);
  cursor: pointer;
}

.drawer-close::before,
.drawer-close::after {
  position: absolute;
  width: var(--space-md);
  height: var(--stroke-hairline);
  background-color: var(--color-ink);
  content: "";
}

.drawer-close::before {
  transform: rotate(45deg);
}

.drawer-close::after {
  transform: rotate(-45deg);
}

/* Favourite toggle: the heart on a book, identical on the home, category and
   search cards, the product page and each favourites row, always beside that
   book's add to cart. A bare outline heart, no surface of its own, in a
   tap-sized hit area; pressed, it fills with the soft favourite red.
   aria-pressed is the only state it has, so the server's first render and the
   toggle script's answer are styled by one rule. */
.favorite-toggle {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: var(--tap-target);
  min-width: var(--tap-target);
  height: var(--tap-target);
  min-height: var(--tap-target);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
}

.favorite-toggle .heart-icon {
  width: var(--favorite-icon-size);
  height: var(--favorite-icon-size);
}

.favorite-toggle[aria-pressed="true"] {
  color: var(--color-favorite);
}

.favorite-toggle[aria-pressed="true"] .heart-icon {
  fill: currentColor;
}

.favorite-toggle[aria-busy="true"] {
  cursor: progress;
}

/* Set by favorite-toggle.js once the server has confirmed a new favourite. */
.favorite-toggle.is-favorited .heart-icon {
  animation: favorite-pop var(--motion-tile) var(--ease-out);
}

@keyframes favorite-pop {
  50% {
    transform: scale(1.28);
  }
}

/* ---- Chips ----------------------------------------------------------------
   A filter toggle that is a link: its address is the page with that filter
   switched, computed on the server, so it works without JavaScript. Several
   can be selected at once; the selected state is the amber fill, the same
   accent as every primary action. The label is short notation (#2, h#3, +),
   the full name travels as visually hidden text.

   A chip is drawn smaller than the tap target, so a row of filters reads as
   settings for the action beside it rather than competing with it; its hit
   area still reaches the full target height through the ::after below. */
.chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--chip-height);
  min-height: var(--chip-height);
  padding-inline: var(--space-xs);
  border: var(--stroke-hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  background-color: var(--color-canvas);
  color: var(--color-ink);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-solid);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

/* The tap target, extended above and below only: sideways the chips sit
   closer than the extension would need, and rows are spaced by at least the
   two extensions together, so neighbouring hit areas never overlap. */
.chip::after {
  position: absolute;
  inset: calc((var(--chip-height) - var(--tap-target)) / 2) 0;
  content: "";
}

/* Transparent rather than amber, so the darker hover fill reaches the edge. */
.chip--selected {
  border-color: transparent;
  background-color: var(--color-action);
}

/* ---- Form fields ---------------------------------------------------------- */

/* `.inputs` is nopCommerce's own universal label+field wrapper (see
   components.css) — styling fields by that ancestor, instead of by which
   page they're on, is what makes contact us, forums, vendor application,
   GDPR tools and password recovery arrive already styled despite none of
   them ever being visited individually. Checkbox/radio/hidden are excluded:
   `.inputs.reversed` in components.css gives those their own inline layout. */
.form-control,
.filter-input,
.filter-select,
.estimate-shipping-address-control,
.ui-dialog input[type="text"],
.quantity-picker__input,
.inputs input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
.inputs select,
.inputs textarea,
.shopping-cart-page .qty-input,
.shopping-cart-page .qty-dropdown,
.shopping-cart-page .discount-coupon-code,
.shopping-cart-page .gift-card-coupon-code {
  width: 100%;
  min-height: var(--tap-target);
  padding: var(--space-2xs) var(--space-xs);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-canvas);
  font-size: var(--font-size-field);
  font-family: inherit;
  color: var(--color-ink);
}

.inputs textarea {
  min-height: calc(var(--action-height) * 2);
  resize: vertical;
}

.form-control::placeholder,
.filter-input::placeholder {
  color: var(--color-subtle);
}

.form-control:focus-visible,
.filter-input:focus-visible,
.filter-select:focus-visible,
.estimate-shipping-address-control:focus-visible,
.ui-dialog input[type="text"]:focus-visible,
.quantity-picker__input:focus-visible,
.inputs input:focus-visible,
.inputs select:focus-visible,
.inputs textarea:focus-visible,
.shopping-cart-page input:focus-visible,
.shopping-cart-page select:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* Browsers draw their own spin buttons on number fields; the price range is
   driven by its slider and by typing, so the steppers are noise. */
.form-control[type="number"],
.filter-input[type="number"] {
  appearance: textfield;
  -webkit-appearance: textfield;
}

.form-control[type="number"]::-webkit-outer-spin-button,
.form-control[type="number"]::-webkit-inner-spin-button,
.filter-input[type="number"]::-webkit-outer-spin-button,
.filter-input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* ---- Dropdown -------------------------------------------------------------
   A native <select> renders its option list through the OS, so the open menu
   cannot be themed (Safari most strictly). filters.js keeps the native select
   as the value source and renders this listbox beside it. */

.styled-select {
  position: relative;
}

/* The native control stays in the DOM — it holds the value and still works
   without JS — but leaves the visual and accessibility layers once enhanced. */
.styled-select select {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* The trigger is the same object as a form field, so it inherits that look.
   The phone account menu's toggle is the same control: it picks a page. */
.styled-select__trigger,
.account-nav__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2xs);
  width: 100%;
  min-height: var(--tap-target);
  padding: var(--space-2xs) var(--space-xs);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-canvas);
  color: var(--color-ink);
  font-size: var(--font-size-field);
  text-align: left;
  cursor: pointer;
}

.styled-select__trigger:focus-visible,
.account-nav__toggle:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

.styled-select__value,
.account-nav__current {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Content-sized variation for short utility selectors such as language. */
.styled-select--content .styled-select__trigger,
.site-header__language .styled-select__trigger,
.account-nav__toggle {
  width: max-content;
  max-width: calc(100vw - var(--gutter) - var(--gutter));
}

.styled-select--content .styled-select__value,
.site-header__language .styled-select__value {
  overflow: visible;
  text-overflow: clip;
}

/* Chevron drawn from borders: a control affordance, not a design asset. One
   mark for every "there is more this way" in the theme - the select's arrow,
   the search list's hint that it carries on below - so they cannot drift
   apart. It points down; a modifier turns it. */
.chevron,
.styled-select__arrow {
  flex-shrink: 0;
  width: var(--chevron-size);
  height: var(--chevron-size);
  border-right: var(--stroke-control) solid currentColor;
  border-bottom: var(--stroke-control) solid currentColor;
  transform: rotate(45deg) translate(-0.125rem, -0.125rem);
}

.styled-select.is-open .styled-select__arrow,
.account-nav[open] .chevron {
  transform: rotate(-135deg) translate(-0.125rem, -0.125rem);
}

/* Listbox primitive: the popup surface and its options, shared by every
   rendered listbox (styled selects, header search suggestions, the phone
   account menu). The account menu's list is a side panel wherever it is not
   folded behind its toggle, so it takes the surface only while open. */
.styled-select__listbox,
.search-suggest,
.account-nav[open] ~ .listbox {
  overflow-y: auto;
  margin: 0;
  padding: var(--space-3xs);
  list-style: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-canvas);
  box-shadow: var(--shadow-card);
}

.styled-select__listbox {
  position: absolute;
  z-index: 20;
  top: calc(100% + var(--space-3xs));
  left: 0;
  width: max-content;
  min-width: 100%;
  max-width: calc(100vw - var(--gutter) - var(--gutter));

  /* Stops on a half option, so a list with more below it always shows a cut
     row instead of a clean edge. The listbox's own padding is added on top of
     the option run, since a scroll container's padding is part of what scrolls. */
  max-height: calc(
    var(--listbox-visible-options) * var(--listbox-option-height) + 2 *
      var(--space-3xs)
  );
}

/* Placement variation for controls sitting near the viewport's right edge. */
.styled-select--end .styled-select__listbox,
.site-header__language .styled-select__listbox {
  right: 0;
  left: auto;
}

.styled-select__option,
.search-suggest__option,
.puzzle-play__move {
  padding: var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs);
  color: var(--color-ink);
  cursor: pointer;
}

/* The listbox cap counts options, so an option states the height it was
   counted at instead of inheriting it. A label long enough to wrap still grows
   past it — the cap only has to land mid-row, not on an exact boundary. */
.styled-select__option {
  min-height: var(--listbox-option-height);
  line-height: var(--line-height-normal);
}

/* Pointer hover and keyboard focus share one highlight, so both input methods
   see the same state. */
.styled-select__option.is-active,
.search-suggest__option.is-active,
.puzzle-play__move[aria-current="step"] {
  background-color: var(--color-action);
  color: var(--color-ink);
}

.styled-select__option.is-selected {
  font-weight: var(--font-weight-bold);
}

.styled-select__option.is-selected:not(.is-active) {
  background-color: var(--color-brand-soft);
}

/* Native disclosures keep filter groups and long option lists usable without scripts. */
.product-filter__title,
.filter-more__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);
  min-height: var(--action-height);
  list-style: none;
  cursor: pointer;
  color: var(--color-ink);
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
}

.product-filter__title::-webkit-details-marker,
.filter-more__toggle::-webkit-details-marker,
.account-nav__toggle::-webkit-details-marker {
  display: none;
}

.product-filter__title::after {
  content: "";
  flex-shrink: 0;
  width: var(--space-2xs);
  height: var(--space-2xs);
  border-right: var(--stroke-control) solid currentColor;
  border-bottom: var(--stroke-control) solid currentColor;
  transform: rotate(45deg);
}

.product-filter[open] > .product-filter__title::after {
  transform: rotate(225deg);
}

.filter-option {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-height: var(--action-height);
  padding-block: var(--space-3xs);
  cursor: pointer;
  overflow-wrap: anywhere;
  font-size: var(--font-size-sm);
  line-height: var(--line-height-normal);
  color: var(--color-ink);
}

.filter-option > input {
  flex-shrink: 0;
}

/* An option no result carries stays in place, so the list does not jump while
   the shopper ticks others, but reads and behaves as inert. */
.filter-option:has(> input:disabled) {
  color: var(--color-subtle);
  cursor: default;
}

.filter-more__toggle {
  justify-content: flex-start;
  color: var(--color-muted);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
}

.filter-more__hide,
.filter-more[open] > summary .filter-more__show {
  display: none;
}

.filter-more[open] > summary .filter-more__hide {
  display: inline;
}
