/* ============================================================================
   Reusable layout components
   ----------------------------------------------------------------------------
   Bootstrap-style primitives for the theme, built to key off nopCommerce's OWN
   generic structural classes wherever one exists — `.page`, `.page-title`,
   `.page-body`, `.fieldset`, `.form-fields`, `.inputs`, `.buttons` — rather
   than a growing list of page-name classes. nopCommerce's core views already
   apply these consistently across dozens of otherwise-unrelated pages
   (registration, contact, forums, vendor application, GDPR tools, password
   recovery...) specifically so a theme can style the shape once. A page this
   theme has never looked at is therefore already shelled, spaced and
   button-aligned correctly the first time anyone opens it.

   `.ui-*` classes are the explicit names for new theme-authored markup that
   wants a primitive without depending on which upstream view it's in.
   A page-name class (`.login-page`, `.html-account-page`) is reserved for
   genuine shape differences — a narrower column, a two-column side-nav grid —
   never for restyling a shape the generic class already covers. Where a more
   specific rule needs to win (the login page's single-column field stack over
   the generic two-column `.form-fields` grid), it lives in that feature's own
   stylesheet, loaded after this one, exactly like any other cascade override.
   ========================================================================== */

/* ---- Page shells -----------------------------------------------------------
   Every content page, upstream or theme-authored, renders its root as
   `<div class="page some-name-page">` — nopCommerce's own convention, applied
   with zero exceptions found across the storefront. `.page` is the one
   selector the base shell needs, so a page nobody has touched yet already has
   correct padding and title spacing the first time anyone styles it further;
   no alias list to extend. `.ui-page` stays as the explicit name for new
   theme-authored markup that wants the shell without depending on that
   upstream convention. Only genuine shape differences (login's narrower
   column, the account/registration form grid) get their own class below.

   Exactly four pages are excluded: home, category, product and cart are each
   traced pixel-for-pixel against their own Figma frame (nodes 3:3, 25:414,
   25:1117, and the cart's own line-item rebuild) and already own their exact
   spacing in their feature stylesheet — home.css on `.page-body`, catalog.css
   and cart.css on the page root itself. Applying the generic padding on top
   of that double-pads them instead of composing. This set is closed: it is
   the four hero layouts this theme was designed around, not a list that
   grows as ordinary pages get touched — those get the generic shell instead,
   which is the entire point of keying off `.page`. */

.page:not(.home-page):not(.category-page):not(.shopping-cart-page):not(.product-details-page),
.ui-page {
  padding-block: var(--space-3xl) var(--space-5xl);
}

/* AJAX feedback is a toast overlay: it stays visible without reflowing the
   page beneath it. nopCommerce creates each notification dynamically. */
.bar-notification-container {
  position: fixed;
  z-index: var(--toast-z);
  top: calc(var(--header-height) + var(--space-sm));
  right: var(--gutter);
  width: min(calc(100vw - var(--gutter) - var(--gutter)), var(--toast-max-width));
  pointer-events: none;
}

.bar-notification {
  position: relative;
  display: none;
  min-height: var(--tap-target);
  padding: var(--space-sm) calc(var(--tap-target) + var(--space-2xs)) var(--space-sm) var(--space-md);
  border: 1px solid var(--color-border);
  border-left: var(--space-3xs) solid var(--color-action);
  border-radius: var(--radius-sm);
  background-color: var(--color-canvas);
  color: var(--color-ink);
  box-shadow: var(--shadow-dialog);
  pointer-events: auto;
}

.bar-notification .content {
  margin: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-snug);
}

/* A link in a notification is its follow-up action ("Go to cart"), so it
   sits on its own line after the message, underlined in the accent. */
.bar-notification .content a {
  display: block;
  width: fit-content;
  margin-top: var(--space-3xs);
  color: inherit;
  font-weight: var(--font-weight-bold);
  text-decoration-color: var(--color-action);
  text-decoration-thickness: var(--stroke-control);
  text-underline-offset: var(--space-3xs);
}

.bar-notification .close {
  position: absolute;
  top: 50%;
  right: 0;
  width: var(--tap-target);
  height: var(--tap-target);
  transform: translateY(-50%);
  cursor: pointer;
}

/* ---- Chevron ---------------------------------------------------------------
   One arrowhead, drawn from two adjacent borders of a square rather than an
   icon file, so it inherits the colour of whatever control carries it and costs
   no request. Every "step to the next thing" control in the theme uses it: the
   pager's previous/next links and the product lightbox's photo navigation.

   The pager's markup is generated by nopCommerce's Pager tag helper, so there
   is no class of ours to put on it and it is matched here by the classes the
   helper emits — the same approach this file takes to jQuery UI's dialog and
   the notification bar. Theme-authored markup uses `.ui-chevron` instead.
   Its size is the shared `--chevron-size` token, which a caller may override;
   the caller chooses a direction. */
.ui-chevron::after,
.pager .previous-page a::after,
.pager .next-page a::after {
  width: var(--chevron-size);
  height: var(--chevron-size);
  border-top: var(--stroke-hairline) solid currentColor;
  border-right: var(--stroke-hairline) solid currentColor;
  content: "";
}

.ui-chevron--next::after,
.pager .next-page a::after {
  transform: rotate(45deg);
}

.ui-chevron--previous::after,
.pager .previous-page a::after {
  transform: rotate(-135deg);
}

/* ---- Close affordance ------------------------------------------------------
   One ×, built from two rotated bars rather than a design asset, shared by
   every "dismiss this" control in the theme: the toast above, jQuery UI's
   dialog title bar below, and the product lightbox. Each of those keeps its own
   box and placement; only the mark itself is defined here, once, so dismissing
   looks the same wherever it appears. A new one joins by adding its class to
   this list. The bars are sized in rem so a host that zeroes font-size to hide
   an icon font (jQuery UI does) cannot collapse them. */
.bar-notification .close::before,
.bar-notification .close::after,
.ui-dialog-titlebar-close::before,
.ui-dialog-titlebar-close::after,
.photo-lightbox__close::before,
.photo-lightbox__close::after {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--space-md);
  height: 1px;
  content: "";
  background-color: var(--color-ink);
}

.bar-notification .close::before,
.ui-dialog-titlebar-close::before,
.photo-lightbox__close::before {
  transform: translate(-50%, -50%) rotate(45deg);
}

.bar-notification .close::after,
.ui-dialog-titlebar-close::after,
.photo-lightbox__close::after {
  transform: translate(-50%, -50%) rotate(-45deg);
}

/* ---- Page backdrop ---------------------------------------------------------
   Dims the page behind an open overlay (the phone sort panel, the header
   search). Each overlay places its own backdrop element in its stacking context
   and sets its z-index there; shared/page-backdrop.js shows it with the
   `hidden` attribute and locks scroll. */
.page-backdrop {
  position: fixed;
  inset: 0;

  /* A floor under the height, rather than leaning on `inset`'s `bottom: 0`
     alone. WebKit lays a fixed box out against its own fixed-position rect,
     and iOS leaves that rect stale after the on-screen keyboard goes: the dim
     was left short and a band of undimmed page opened at the foot of the phone
     search, above the browser's bar. A length is not measured against that
     rect. `min-height` rather than `height` so `inset` keeps its meaning and
     the box can only ever grow - over-constraining it would drop `bottom` and
     leave it unable to follow a fixed rect that is TALLER than `lvh`. */
  min-height: 100lvh;
  background-color: var(--color-dialog-backdrop);
}

/* ---- Bottom action bar -----------------------------------------------------
   Below the desktop breakpoint, the band a panel's actions stand on at the
   foot of the screen: the primary pill with its partner beside it on one
   row, ruled off from the content above and clear of the phone's home
   indicator. The filter drawer's Apply and Clear (an outline pill), the
   puzzle page's die and solution (quiet text). How the band is held there (the drawer's flex frame, the puzzle's
   sticky bottom) stays with the page. */
@media (max-width: 61.9375rem) {
  .ui-action-bar,
  .filter-drawer__actions,
  .puzzle-actions {
    padding: var(--space-sm) var(--gutter)
      calc(var(--space-sm) + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--color-border);
    background-color: var(--color-canvas);
  }
}

/* ---- Busy indicator --------------------------------------------------------
   One spinner for the whole theme: the Figma reload glyph (node 65:1048,
   ant-design reload-outlined) turning at the shared switch duration. Every
   surface that waits on a request shows this same mark — the home page's
   "load more", the catalog listing while filters, sorting or paging refresh —
   so waiting looks identical wherever it happens.

   The primitive owns the turn and nothing else. Size is placement, so it stays
   with the surface that uses it, which also keeps this rule from competing with
   a feature stylesheet's on equal specificity. */
.loading-icon {
  animation: loading-spin var(--motion-switch) linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .loading-icon {
    animation: none;
  }
}

/* ---- Confirm dialog ---------------------------------------------------------
   The one "are you sure?" question: the cart's removal and the puzzle page's
   solution are the same native <dialog>
   (Views/Shared/Components/ChessStore/ConfirmDialog, driven by
   Content/js/shared/confirm-dialog.js). Its buttons are the compact primary
   and outline primitives in controls.css. The tone modifier only picks the
   icon and, for "danger", the red confirm button. On phones it becomes a
   bottom sheet. */
.confirm-dialog {
  width: min(calc(100% - var(--gutter) - var(--gutter)), var(--dialog-max-width));
  max-width: var(--dialog-max-width);
  padding: 0;
  border: var(--stroke-hairline) solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-canvas);
  color: var(--color-ink);
  box-shadow: var(--shadow-dialog);
}

.confirm-dialog::backdrop {
  background-color: var(--color-dialog-backdrop);
}

.confirm-dialog__content {
  display: grid;
  grid-template-columns: var(--tap-target) minmax(0, 1fr);
  gap: var(--space-2xs) var(--space-md);
  padding: var(--space-xl);
}

.confirm-dialog__icon {
  grid-row: 1 / span 3;
  width: var(--tap-target);
  height: var(--tap-target);
  border-radius: var(--radius-pill);
  background-color: var(--color-brand-soft);
}

.confirm-dialog__icon::before {
  display: block;
  width: var(--space-md);
  height: var(--space-md);
  margin: var(--space-xs);
  content: "";
  background-color: var(--color-ink);
  -webkit-mask: no-repeat center / contain;
  mask: no-repeat center / contain;
}

.confirm-dialog--danger .confirm-dialog__icon::before {
  background-color: var(--color-danger);
  -webkit-mask-image: url("../../images/figma/icon-trash.svg");
  mask-image: url("../../images/figma/icon-trash.svg");
}

.confirm-dialog--reveal .confirm-dialog__icon::before {
  -webkit-mask-image: url("../../images/figma/icon-eye.svg");
  mask-image: url("../../images/figma/icon-eye.svg");
}

.confirm-dialog__title,
.confirm-dialog__body,
.confirm-dialog__detail {
  grid-column: 2;
  margin: 0;
}

.confirm-dialog__title {
  font-size: var(--font-size-xl);
  line-height: var(--line-height-tight);
}

.confirm-dialog__body {
  color: var(--color-muted);
  line-height: var(--line-height-normal);
}

.confirm-dialog__detail {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.confirm-dialog__actions {
  grid-column: 1 / -1;
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2xs);
  margin-top: var(--space-md);
}

@media (max-width: 36.25rem) {
  .confirm-dialog {
    width: 100%;
    max-width: none;
    margin: auto 0 0;
    border-right: 0;
    border-bottom: 0;
    border-left: 0;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
  }

  .confirm-dialog__content {
    grid-template-columns: minmax(0, 1fr);
    padding: var(--space-lg) var(--gutter);
  }

  .confirm-dialog__icon,
  .confirm-dialog__title,
  .confirm-dialog__body,
  .confirm-dialog__detail,
  .confirm-dialog__actions {
    grid-column: 1;
  }

  .confirm-dialog__icon {
    grid-row: auto;
  }

  .confirm-dialog__actions {
    flex-direction: column-reverse;
  }

  .confirm-dialog__cancel,
  .confirm-dialog__confirm {
    width: 100%;
  }
}

/* ---- Dialogs (jQuery UI) ---------------------------------------------------
   nopCommerce core (public.common.js's displayPopupContentFromUrl, and this
   theme's own terms-of-service warning in OrderSummary/Default.cshtml) pops
   a jQuery UI `.dialog()` for the "please accept the terms" warning and the
   "read terms" popup. jquery-ui.min.js is loaded sitewide but its stylesheet
   never is, so every one of these rendered with no position at all (the
   widget's own generated markup, unlike everything else in this file, isn't
   ours to add a class to), no border and an unstyled close button whose icon
   silently failed to render, leaving its fallback label text floating loose
   next to the title. Targeting the classes jQuery UI itself generates fixes
   every current and future `.dialog()` call at once.

   A real modal reads as an overlay on top of the page, not a block sitting
   inside it, so this is `position: fixed` and centred with a transform, not
   jQuery UI's own default `position: absolute`. jQuery UI's Position utility
   computes `of: window` centering as document-relative (scroll-inclusive)
   pixel coordinates to match ITS default of `absolute` — those would fight a
   fixed, viewport-relative element the moment the page is scrolled. Rather
   than replicate jQuery UI's math, `Content/js/shared/dialog-position.js`
   stops jQuery UI from writing an inline top/left at all, so this
   fixed/centred rule is the only placement — a percentage + transform stays
   centred in the viewport by itself, with nothing to recompute on scroll or
   resize.

   It is the same object as the native confirm dialog (`.confirm-dialog`,
   below), so it shares that dialog's padding, title
   size and — on phones, below — its bottom-sheet geometry. */
.ui-dialog {
  position: fixed;
  display: flex;
  flex-direction: column;
  top: 50%;
  left: 50%;
  z-index: var(--dialog-z);
  width: min(calc(100vw - var(--gutter) - var(--gutter)), var(--dialog-max-width));
  max-width: calc(100vw - var(--gutter) - var(--gutter));
  max-height: calc(100vh - var(--gutter) - var(--gutter));
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-canvas);
  box-shadow: var(--shadow-dialog);
  transform: translate(-50%, -50%);
}

.ui-dialog-titlebar {
  flex: 0 0 auto;
  position: relative;
  padding: var(--space-xl) calc(var(--tap-target) + var(--space-sm)) var(--space-2xs) var(--space-xl);
}

.ui-dialog-title {
  display: block;
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
}

/* The × itself comes from the shared close affordance above. jQuery UI's own
   close button renders an icon font (never loaded) plus a fallback text label;
   the zeroed font-size hides both. */
.ui-dialog-titlebar-close {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  width: var(--tap-target);
  height: var(--tap-target);
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  font-size: 0;
  line-height: 0;
  cursor: pointer;
}

/* jQuery UI moves focus to this button when the dialog opens; without this
   it falls back to the browser's plain blue focus ring instead of the
   theme's own focus treatment used everywhere else (form fields, dropdowns). */
.ui-dialog-titlebar-close:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

.ui-dialog-content {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 0 var(--space-xl) var(--space-xl);
  color: var(--color-ink);
  font-size: var(--font-size-md);
  line-height: var(--line-height-normal);
}

.ui-dialog-content > :last-child {
  margin-bottom: 0;
}

.ui-dialog-buttonpane {
  flex: 0 0 auto;
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border-top: 1px solid var(--color-border);
}

/* Painted by jQuery UI for modal dialogs. Theme-owned calls default to modal,
   while public.common.js calls that explicitly choose non-modal stay intact. */
.ui-widget-overlay {
  position: fixed;
  inset: 0;
  z-index: calc(var(--dialog-z) - 1);
  background-color: var(--color-dialog-backdrop);
}

.ui-dialog .add-wishlist-content,
.ui-dialog .wishlist-buttons-container {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.ui-dialog .wishlist-add-custom-button,
.ui-dialog .custom-wishlist-button {
  width: 100%;
}

/* Magnific Popup remains the native behavior for the shipping estimator and
   product lightbox. Only its generated shell is aligned with the shared
   dialog system; image sizing and gallery interaction stay untouched. */
.mfp-bg {
  background-color: var(--color-dialog-backdrop);
  opacity: 1;
}

.estimate-shipping-popup .mfp-close {
  color: var(--color-ink);
}

.estimate-shipping-popup {
  position: relative;
  width: min(calc(100vw - var(--gutter) - var(--gutter)), var(--dialog-wide-max-width));
  max-height: calc(100vh - var(--gutter) - var(--gutter));
  margin-inline: auto;
  padding: var(--space-xl);
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-canvas);
  box-shadow: var(--shadow-dialog);
}

.estimate-shipping-popup .ship-to-title,
.estimate-shipping-popup .choose-shipping-title {
  margin-bottom: var(--space-sm);
  font-size: var(--font-size-md);
}

.estimate-shipping-popup .shipping-address {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-sm);
  margin-bottom: var(--space-xl);
}

.estimate-shipping-popup .estimate-shipping-row-item,
.estimate-shipping-popup .styled-select {
  min-width: 0;
}

.estimate-shipping-popup .shipping-options {
  overflow-x: auto;
}

.estimate-shipping-popup .shipping-options-header,
.estimate-shipping-popup .shipping-options-body {
  min-width: var(--shipping-options-min-width);
}

.estimate-shipping-popup .estimate-shipping-row {
  display: grid;
  grid-template-columns: var(--tap-target) repeat(3, minmax(0, 1fr));
  align-items: center;
  gap: var(--space-sm);
}

.estimate-shipping-popup .shipping-options-header {
  padding-block: var(--space-2xs);
  border-bottom: 1px solid var(--color-border);
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
}

.estimate-shipping-popup .shipping-options-body .estimate-shipping-row {
  padding-block: var(--space-sm);
  border-bottom: 1px solid var(--color-border);
}

.estimate-shipping-popup .apply-shipping-button-container {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

@media (max-width: 36.25rem) {
  .ui-dialog {
    top: auto;
    bottom: 0;
    width: 100%;
    max-width: 100%;
    max-height: calc(100vh - var(--header-height));
    border-right: 0;
    border-bottom: 0;
    border-left: 0;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    transform: translateX(-50%);
  }

  .estimate-shipping-popup {
    width: 100%;
    max-height: calc(100vh - var(--header-height));
    margin-bottom: 0;
    padding: var(--space-lg) var(--gutter);
    border-right: 0;
    border-bottom: 0;
    border-left: 0;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
  }

  .estimate-shipping-popup .shipping-address {
    grid-template-columns: minmax(0, 1fr);
  }

  .estimate-shipping-popup .apply-shipping-button-container .button-2 {
    width: 100%;
  }
}

.ui-page--narrow,
.login-page {
  max-width: var(--account-panel-max);
  margin-inline: auto;
}

.page > .page-title h1,
.ui-page__title h1 {
  margin: 0 0 var(--space-xl);
  font-size: var(--font-size-3xl);
  line-height: var(--line-height-tight);
}

/* ---- Composable layout -------------------------------------------------- */

.ui-split,
.login-layout {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: stretch;
}

.ui-stack,
.page-body > form {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.ui-form-grid,
.form-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg);
}

/* A fieldset's own submit/continue action is conventionally right-aligned;
   this is the one visual inconsistency the audit found — the login page's
   buttons had been left-default only because nothing had set this for them —
   so the fix is here at the general rule, not a login-page-only patch. */
.ui-cluster,
.buttons {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.ui-cluster--end,
.buttons {
  justify-content: flex-end;
}

/* ---- Surfaces ----------------------------------------------------------- */

.ui-panel,
.fieldset {
  padding: var(--space-xl);
  background-color: var(--color-surface);
}

.ui-panel--soft {
  background-color: var(--color-brand-soft);
}

.ui-panel--raised,
.login-layout {
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
}

.ui-panel__title,
.fieldset > .title {
  margin: 0 0 var(--space-lg);
  font-size: var(--font-size-xl);
  line-height: var(--line-height-tight);
}

/* ---- Prose ---------------------------------------------------------------
   CMS topic content (Shipping & Returns, Privacy Notice, Terms of Use) and
   the shared TopicBlock component (the contact page's intro copy, the
   login page's "register and save time" panel) both render an admin-authored
   HTML blob with no classes of its own — `.topic-page .page-body` renders it
   directly, `.topic-block-body` wraps it wherever TopicBlockViewComponent is
   invoked. One prose primitive styles both, generically, the same way a
   markdown renderer would: it can never know the content's classes, only its
   elements. */
.ui-prose,
.topic-page .page-body,
.topic-block-body {
  color: var(--color-ink);
  font-size: var(--font-size-md);
  line-height: var(--line-height-normal);
}

.ui-prose h1,
.ui-prose h2,
.topic-page .page-body h1,
.topic-page .page-body h2,
.topic-block-body h1,
.topic-block-body h2 {
  margin: var(--space-xl) 0 var(--space-sm);
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
}

.ui-prose h3,
.ui-prose h4,
.topic-page .page-body h3,
.topic-page .page-body h4,
.topic-block-body h3,
.topic-block-body h4 {
  margin: var(--space-lg) 0 var(--space-2xs);
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
}

.ui-prose > :first-child,
.topic-page .page-body > :first-child,
.topic-block-body > :first-child {
  margin-top: 0;
}

.ui-prose p,
.topic-page .page-body p,
.topic-block-body p {
  margin: 0 0 var(--space-sm);
}

.ui-prose ul,
.ui-prose ol,
.topic-page .page-body ul,
.topic-page .page-body ol,
.topic-block-body ul,
.topic-block-body ol {
  margin: 0 0 var(--space-sm);
  padding-inline-start: var(--space-lg);
}

.ui-prose li,
.topic-page .page-body li,
.topic-block-body li {
  margin-bottom: var(--space-3xs);
}

.ui-prose a,
.topic-page .page-body a,
.topic-block-body a {
  color: inherit;
  text-underline-offset: var(--space-3xs);
}

@media (hover: hover) and (pointer: fine) {
  .ui-prose a:hover,
  .topic-page .page-body a:hover,
  .topic-block-body a:hover {
    color: var(--color-ink-hover);
  }
}

.ui-prose table,
.topic-page .page-body table,
.topic-block-body table {
  width: 100%;
  margin: 0 0 var(--space-sm);
  border-collapse: collapse;
  font-size: var(--font-size-xs);
}

.ui-prose th,
.ui-prose td,
.topic-page .page-body th,
.topic-page .page-body td,
.topic-block-body th,
.topic-block-body td {
  padding: var(--space-2xs) var(--space-xs);
  border-bottom: 1px solid var(--color-border);
  text-align: left;
  vertical-align: top;
}

/* ---- Account shell ------------------------------------------------------ */

.ui-account-shell,
.html-account-page .site-main__content {
  display: grid;
  grid-template-columns: minmax(12rem, var(--filter-column-width)) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-2xl);
}

.html-account-page .side-2,
.html-account-page .center-2 {
  min-width: 0;
}

/* On a desktop the menu stays in reach down a long order list or address book.
   The column, not the menu, is what sticks: the menu's own parent ends where the
   menu does, so it would have no room to travel. It sits flush under the header,
   where it starts, so sticking never moves it. The header's height varies with
   the admin bar, so shell/header-offset.js publishes it; the token is the
   fallback. Not on phones and tablets: there the stacked tab strip stuck under
   the header misbehaved with iOS's on-screen keyboard. */
@media (min-width: 62rem) {
  .html-account-page .side-2 {
    position: sticky;
    top: var(--site-header-offset, var(--header-height));
  }
}

.inputs {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.inputs:has(.gender),
.inputs:only-child,
.contact-page__enquiry,
.add-more-external-records {
  grid-column: 1 / -1;
}

.inputs > label:first-child {
  color: var(--color-ink);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
}

.inputs > label:first-child .required {
  margin-inline-start: var(--space-3xs);
  color: var(--color-danger);
}

/* nopCommerce's own views (registration, customer info, password recovery,
   reviews, email a friend...) render the required marker after the field, as
   its sibling, where the column above set it on a line of its own under the
   input. It joins the label's line instead, as the theme's own views put it
   inside the label, so a required field reads the same on every form. */
.inputs:not(.reversed):has(> .required) {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
  column-gap: var(--space-3xs);
}

.inputs:not(.reversed):has(> .required) > * {
  grid-column: 1 / -1;
}

.inputs:not(.reversed):has(> .required) > label:first-child {
  grid-column: 1;
  grid-row: 1;
}

.inputs:not(.reversed):has(> .required) > .required {
  grid-column: 2;
  grid-row: 1;
  color: var(--color-danger);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
}

/* nopCommerce's own convention for a checkbox-led field (a single checkbox
   plus its label, or — as on the login page's "remember me" row — two
   inline controls) across search, registration, GDPR consent and checkout.
   Never had a rule anywhere in the theme; without one every one of those
   checkboxes stacked its label underneath it instead of beside it. */
.inputs.reversed {
  flex-direction: row;
  align-items: center;
  gap: var(--space-2xs);
}

.gender,
.attributes ul,
.accept-consent {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-md);
}

.attributes ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.field-validation-error,
.message-error {
  color: var(--color-danger);
  font-size: var(--font-size-xs);
}

/* Another nopCommerce-wide convention: the confirmation message a form's
   controller renders in place of itself once submitted successfully (contact
   us, GDPR export, password recovery, account activation...). The theme has
   no separate "success" color — amber/ink is the whole palette — so this
   reuses the same soft panel treatment as `.ui-panel--soft` rather than
   inventing a green that doesn't exist anywhere else in the design. */
.result {
  padding: var(--space-lg);
  background-color: var(--color-brand-soft);
  color: var(--color-ink);
  font-size: var(--font-size-md);
  line-height: var(--line-height-normal);
}

/* ---- Page notice -----------------------------------------------------------
   WooCommerce's success and info notices where it prints them in the page
   (template-parts/page-notice.php): the email confirmation prompt above the
   customer's orders. The order cards' surface, so it reads as part of the
   list it sits on rather than as the soft "no orders yet" panel under it. */

.page-notice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  margin: 0 0 var(--space-lg);
  padding: var(--space-lg);
  background-color: var(--color-surface);
  color: var(--color-ink);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-normal);
}

/* WooCommerce's script focuses a success notice so a screen reader reads it
   out; the panel is not a control, so it draws no focus ring. */
.page-notice:focus {
  outline: none;
}

/* The glyph sits on the first line's centre, not the panel's top edge. */
.page-notice > .info-icon {
  margin-block: calc((1lh - var(--status-icon-size)) / 2);
}

/* WooCommerce writes a notice's action before its words, for a float to put
   it on the right; the words come first here and the action under them,
   or beside them where the line has room. */
.page-notice__body {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm) var(--space-md);
  min-width: 0;
}

.page-notice__body > .button {
  order: 1;
}

/* ---- Side navigation ---------------------------------------------------- */

.ui-side-nav,
.block-account-navigation {
  padding: var(--space-lg);
  background-color: var(--color-brand-soft);
}

.block-account-navigation > .title {
  margin: 0 0 var(--space-md);
  font-size: var(--font-size-md);
}

.block-account-navigation .list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.block-account-navigation a {
  display: block;
  padding: var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-sm);
  color: var(--color-ink);
  font-size: var(--font-size-xs);
  text-decoration: none;
  transition: background-color var(--motion-card) var(--ease-out);
}

.block-account-navigation .active a {
  background-color: var(--color-action);
  font-weight: var(--font-weight-bold);
}

@media (hover: hover) and (pointer: fine) {
  .block-account-navigation a:hover {
    background-color: var(--color-brand-soft-hover);
  }

  .block-account-navigation .active a:hover {
    background-color: var(--color-action-hover);
  }
}

@media (max-width: 61.9375rem) {
  .page:not(.home-page):not(.category-page):not(.shopping-cart-page):not(.product-details-page),
  .ui-page {
    padding-block: var(--space-xl) var(--space-3xl);
  }

  .ui-split,
  .login-layout,
  .ui-account-shell,
  .html-account-page .site-main__content {
    grid-template-columns: minmax(0, 1fr);
  }

  .ui-form-grid,
  .form-fields {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Stacked above the page, the menu is one row: its heading and a dropdown
     named after the page being shown. A panel or a tab strip there took a
     phone's first screen before the form began, and could not stick under the
     header instead, which misbehaved with iOS's on-screen keyboard. */
  .block-account-navigation {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: 0;
    background-color: transparent;
  }

  .block-account-navigation > .title {
    margin: 0;
  }

  .account-nav {
    min-width: 0;
  }

  /* The list is the toggle's sibling, not its content (Default.cshtml), so
     the closed toggle hides it here and the toggle sits on the row's right
     edge, which the list hangs from. */
  .account-nav:not([open]) ~ .listbox {
    display: none;
  }

  .block-account-navigation > .listbox {
    position: absolute;
    z-index: 1;
    top: calc(100% + var(--space-3xs));
    right: 0;
    width: max-content;
    max-width: calc(100vw - var(--gutter) - var(--gutter));
  }

  /* While the menu is open the page dims and stops scrolling, header
     included; only the toggle and the list stay lit. */
  .block-account-navigation:has(.account-nav[open]) {
    z-index: var(--account-nav-open-z);
  }

  .account-nav__backdrop {
    z-index: 0;
  }

  .account-nav__toggle {
    position: relative;
    z-index: 1;
  }
}

/* The toggle exists only where the menu is stacked above the page; a desktop
   shows the list itself as the side panel. */
@media (min-width: 62rem) {
  .account-nav {
    display: none;
  }
}

@media (max-width: 36.25rem) {
  .ui-panel,
  .fieldset {
    padding: var(--space-lg);
  }

  /* A bottom sheet, like the cart's removal dialog. jQuery UI writes its
     `width` option (300-550px) inline, so the sheet takes the full width
     through `min-width`, which outranks an inline width without
     `!important`. */
  .ui-dialog {
    top: auto;
    bottom: 0;
    left: 0;
    min-width: 100%;
    max-width: none;
    max-height: calc(100dvh - var(--gutter));
    border-right: 0;
    border-bottom: 0;
    border-left: 0;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    transform: none;
  }

  .ui-dialog-titlebar {
    padding: var(--space-lg) calc(var(--tap-target) + var(--space-xs)) var(--space-2xs) var(--gutter);
  }

  .ui-dialog-titlebar-close {
    top: var(--space-xs);
    right: var(--space-2xs);
  }

  .ui-dialog-content {
    padding: 0 var(--gutter) var(--space-lg);
  }
}

/* ---- nopCommerce attribution ----------------------------------------------
   The nopCommerce Public License requires this line on every page, so it has
   two homes: the footer band, where footer.css places it on the copyright row,
   and the foot of a layout that renders no footer at all. The shape is shared;
   the colour belongs to whichever surface it lands on, which is why only the
   standalone placement sets one here. */

.powered-by {
  margin: 0;
  font-size: var(--font-size-3xs);
}

/* Underlined, unlike the footer's navigation links: this one is a link inside
   a sentence and has nothing else to mark it as one. */
.powered-by a {
  color: inherit;
  text-decoration: underline;
}

/* Standing alone under `.site-main`, which grows to push it to the bottom of
   the viewport on a short page (see `.site-wrapper` in base.css).

   Only the space BELOW it is its own: the page above already closes with the
   `.page` shell's bottom padding, and a minimal layout is sized to hold its
   whole flow in one fold (login, checkout-as-guest). A band of its own on top
   of that padding is what pushes the notice past the fold on a short laptop
   window, leaving a legal notice you have to scroll to read. */
.site-wrapper > .powered-by {
  padding-block-end: var(--space-lg);
  padding-inline: var(--gutter);
  color: var(--color-muted);
  text-align: center;
}

@media (hover: hover) and (pointer: fine) {
  .site-wrapper > .powered-by a:hover {
    color: var(--color-muted-hover);
  }
}

/* ---- Info icon -------------------------------------------------------------
   The one "i" glyph, drawn wherever a short explanation hangs off a figure:
   the order total's delivery note, the approximate price's disclosure and the
   puzzle filters' notation key. It
   is painted in currentColor through the mask, so it follows the colour, and
   the hover, of the text it sits in. */

.info-icon {
  display: inline-block;
  flex-shrink: 0;
  width: var(--status-icon-size);
  height: var(--status-icon-size);
  background-color: currentColor;
  -webkit-mask: url("../../images/figma/icon-info.svg") no-repeat center / contain;
  mask: url("../../images/figma/icon-info.svg") no-repeat center / contain;
}

/* ---- Info bubble -----------------------------------------------------------
   The dark bubble an info icon's explanation opens in, floating over the page
   rather than pushing it aside: the order total's delivery note, the
   product page's approximate price note, the puzzle notation key, the
   home page die's note and the puzzle board's first-visit note. Where it sits
   belongs to each. */

.order-total__shipping-note::after,
.book-detail__price .price-approx__note,
.puzzle-key__note,
.die-fab__note,
.puzzle-coach {
  padding: var(--space-xs);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-ink);
  color: var(--color-footer-ink);
  box-shadow: var(--shadow-card);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-normal);
}

/* ---- Approximate price -----------------------------------------------------
   The euro estimate beside a hryvnia price (Views/Shared/_ApproximatePrice.cshtml).
   It is an aside, never a second price: small and in the theme's neutrals, never
   the amber action colour or the sale red, whatever size the price above it is
   set in. The amount sits on a flat tag in the theme's soft cream, square like
   the cover badges, so a foreign customer finds it at a glance without it
   reading as a second price or an action. No Figma frame draws it (see CLAUDE.md, "Figma source-of-truth
   deviations"). */

.price-approx {
  margin: 0;
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-normal);
}

.price-approx__amount {
  display: inline-flex;
  align-items: center;
  padding-inline: var(--space-2xs);
  background-color: var(--color-brand-soft);
  color: var(--color-ink);
  font-weight: var(--font-weight-semibold);
  white-space: nowrap;
}

/* Where the buyer decides, the amount opens a note on how approximate it is. */
.price-approx__summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  min-height: var(--tap-target);
  list-style: none;
  cursor: pointer;
}

/* Safari draws the disclosure triangle from its own pseudo-element, which
   `list-style` does not reach. */
.price-approx__summary::-webkit-details-marker {
  display: none;
}

/* A short paragraph wherever it opens, left-aligned even under a right-aligned
   total. */
.price-approx__note {
  max-inline-size: var(--info-note-max-width);
  margin: 0;
  text-align: start;
}
