/* ── BH-USA Wholesale Portal - shell styles on top of the approved
      design system (tokens.css / styles.css). Tokens only, no raw values
      except where a token genuinely doesn't exist. ─────────────────── */

:root {
  --portal-ease: cubic-bezier(0.32, 0.72, 0, 1);
}

/* The approved styles position the sticky sidebar below the SITE's tall
   header (--catalog-filter-top: 247px). The portal topbar is slim - retune.
   Measured, not guessed: this is the topbar's own height, and it has drifted
   twice already (90 -> 162 -> 139) because it is a hand-copied duplicate of a
   rendered height. The category nav that pushed it to 139 is gone. */
.portal-app {
  --catalog-filter-top: 79px;
}
.portal-app .dashboard-shell {
  padding-top: var(--space-700);
}

/* List view's action column is flex:0 0 144px with 28px of padding, leaving
   116px for the button - measured, "Choose Options" needs 118px and clips.
   The approved design never had that label here; our catalogue is full of
   variable products, so it does. Widened just enough to sit comfortably.
   Selector matches the approved one exactly - it doubles up .catalog-grid
   (0,4,0), so a three-class override loses on specificity however late it loads. */
.catalog-grid.catalog-grid--list .catalog-card .product-actions {
  flex: 0 0 176px;
  width: 176px;
}

/* ── Top bar (replaces the site header: no site navbar) ─────── */
.portal-topbar {
  display: flex;
  align-items: center;
  gap: var(--space-600);
  /* 15px vertical is raw px on purpose - it sits between --space-400 (12px)
     and --space-500 (16px). The horizontal half matches .shell exactly. */
  padding: 15px max(40px, calc((100% - 1920px) / 2));
  background: var(--color-bg-surface);
  border-bottom: 1px solid var(--color-border-subtle);
  /* Sticky lives here again. It moved onto a .portal-header-stack
     wrapper only so the category nav's absolutely-positioned panel had one
     positioned ancestor to measure top:100% against; the nav is gone and so
     is the wrapper. */
  position: sticky;
  top: 0;
  z-index: 40;
}
.portal-brand {
  display: flex;
  align-items: center;
  gap: var(--space-400);
  flex-shrink: 0;
}
/* The logo SVG declares width="100%" height="100%", so it has NO intrinsic
   size - `width: auto` then falls back to the replaced-element default of
   300px and ignores the height entirely. That is why the brand measured
   416px inside a 375px top bar and every portal page scrolled sideways on a
   phone. aspect-ratio restores the real 285:42 so width follows the
   height it is actually given: 271px at 40px, 217px at 32px. */
.portal-brand img { height: 40px; width: auto; aspect-ratio: 285 / 42; }
.portal-brand-tag {
  color: var(--color-text-magic);
  background: var(--color-bg-fill-magic-soft);
  border: 1px solid var(--color-border-magic-soft);
  border-radius: var(--radius-full);
  padding: var(--space-100) var(--space-400);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.portal-brand-tag--admin {
  color: var(--color-text-brand);
  background: var(--color-bg-fill-info-subtle);
  border-color: var(--color-border-brand-soft);
}
/* The search suggestion box is position: absolute; top: 100%, and
   styles.css gives .search-form no position at all - without this it would
   hang off the nearest positioned ancestor instead of the field. */
.portal-topbar-search { position: relative; flex: 1; max-width: 560px; }
.portal-topbar-actions { margin-left: auto; display: flex; align-items: center; gap: var(--space-400); }
/* .portal-topbar-menu is gone - the top-bar hamburger was a portal
   invention, and the approved mobile control is the floating Directory pill
   (.dashboard-directory-mobile-trigger) that styles.css already ships. */

/* A checkbox row is a flex container and its label text is a flex item, so it
   defaults to min-width: auto - a long label then sets a floor its column
   cannot go under. Settings' "Push orders to WooCommerce" description measured
   329px and took the admin page sideways with it at 360px. Harmless at
   any width, so it is not hidden behind a breakpoint. */
.co-checkbox-row,
.co-checkbox-row > span { min-width: 0; }

/* ── Sidebar nav extras ───────────────────────────────────────────── */
.portal-disabled {
  opacity: 0.45;
  cursor: not-allowed;
  user-select: none;
}
.portal-soon {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.sidebar-avatar--admin { background: var(--color-bg-fill-brand); color: var(--color-text-adaptive); }
.dashboard-tier-pill--admin {
  background: var(--color-bg-fill-info-subtle);
  border-color: var(--color-border-brand-soft);
  color: var(--color-text-brand);
}
/* Tier lock: sits beside the tier pill in the contractors list, so the
   two need a gap the base component never had to provide. */
.dashboard-tier-pill--locked {
  margin-left: var(--space-200);
  background: var(--color-bg-fill-warning-soft);
  border-color: var(--color-bg-fill-warning);
  color: var(--color-text-warning);
}

/* ── Flash messages ───────────────────────────────────────────────── */
.portal-flash {
  padding: var(--space-400) var(--space-500);
  border-radius: var(--radius-400);
  margin-bottom: var(--space-600);
  border: 1px solid var(--color-border-success-soft);
  background: var(--color-bg-fill-success-soft);
  color: var(--color-text-base);
  animation: portal-rise 0.5s var(--portal-ease);
}
.portal-flash--error {
  border-color: var(--color-border-critical-soft);
  background: var(--color-bg-fill-critical-soft);
}

/* ── Auth pages use the approved design layout (checkout-*/auth-*/co-*).
      Only the server-side error box is portal-specific. ─────────────── */
.portal-auth-error {
  padding: var(--space-400) var(--space-500);
  border-radius: var(--radius-400);
  margin-bottom: var(--space-500);
  border: 1px solid var(--color-border-critical-soft);
  background: var(--color-bg-fill-critical-soft);
  color: var(--color-text-base);
}
.auth-panel { animation: portal-rise 0.55s var(--portal-ease); }

/* ── Micro-interactions (transform/opacity only, token easing) ────── */
.dashboard-card,
.dashboard-stat-card,
.dashboard-tier-card {
  animation: portal-rise 0.55s var(--portal-ease) backwards;
}
.dashboard-content > *:nth-child(2) { animation-delay: 0.05s; }
.dashboard-content > *:nth-child(3) { animation-delay: 0.1s; }
.dashboard-content > *:nth-child(4) { animation-delay: 0.15s; }
.dashboard-content > *:nth-child(5) { animation-delay: 0.2s; }

.button { transition: transform 0.4s var(--portal-ease), box-shadow 0.4s var(--portal-ease); }
.button:active { transform: scale(0.98); }

/* Icon-only table actions: approved button chrome, square footprint. */
.button--icon { width: 32px; height: 32px; min-width: 32px; min-height: unset; padding: 0; justify-content: center; }

@keyframes portal-rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .portal-flash, .dashboard-card, .dashboard-stat-card, .dashboard-tier-card,
  .portal-auth-shell .co-panel { animation: none; }
}

/* ── Catalog helpers ──────────────────────────────────────────────── */
/* .search-form is width:100% in the design system, where it spans the site
   header. Inside the portal's flex page-head that pushes it onto its own
   line, so it is sized to content here instead. */
.portal-page-head .search-form {
  width: auto;
  max-width: min(420px, 100%);
  flex: 1 1 260px;
}

.portal-page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-500);
  flex-wrap: wrap;
  margin-bottom: var(--space-600);
}
.portal-page-head p { color: var(--color-text-subtle); }
.portal-price-note { color: var(--color-text-subtle); }
.portal-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-500);
  padding: var(--space-700) 0 var(--space-400);
}
.portal-empty {
  text-align: center;
  padding: var(--space-1000) var(--space-500);
  color: var(--color-text-subtle);
}

/* ── Owner spacing tweaks ─────────────────────────────────────────── */
.auth-page .checkout-nav-desktop { padding: 24px 40px; }
.portal-app .dashboard-main { padding-top: 0; }

/* Cards inside a form wrapper (settings, contractor-edit) miss the content
   column's gap - mirror it. Excludes .search-form: this rule applies to every
   form in the content area, and it was turning the catalog's search box into
   a column, stacking the button under the field. */
.dashboard-content form:not(.search-form) {
  display: flex;
  flex-direction: column;
  gap: var(--space-600);
}
.dashboard-content form .dashboard-card { margin-bottom: 0; }
.dashboard-content form[hidden] { display: none; }  /* the [hidden] quirk - see FRONTEND-CONVENTIONS.md */

/* ── DataTables checkbox + status columns ─────────────────────────── */
table.dataTable th.dt-check-col,
table.dataTable td.dt-check-col { width: 32px !important; max-width: 32px; text-align: center; }
table.dataTable .dt-center { text-align: center; }
.portal-save-state {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  min-width: 70px;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-text-placeholder);
  white-space: nowrap;
  transition: color 0.3s var(--portal-ease);
}
.portal-save-state.is-saving { color: var(--color-text-warning); }
.portal-save-state.is-saved { color: var(--color-text-success); }
.portal-save-state.is-error { color: var(--color-text-critical); cursor: pointer; }
/* A website tier price that contradicts retail or the tier order:
   imported as-is, marked so the exceptions filter has something to point at. */
.portal-price-odd { color: var(--color-text-critical); font-weight: 700; }

/* ── Admin forms, filters, bulk bars ──────────────────────────────── */
.portal-filter-row {
  display: flex;
  gap: var(--space-400);
  flex-wrap: wrap;
  margin-bottom: var(--space-600);
}
.portal-filter-row .search-field { flex: 1; min-width: 240px; }
.portal-select {
  appearance: auto;
  width: auto;
  min-width: 140px;
}
/* Form selects fill their grid cell like inputs do. Toolbar filters
   (.portal-filter-row, page heads) keep .portal-select's auto width. */
.co-form-group .portal-select { width: 100%; }
.portal-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Row gap tightened on desktop by design; a
     phone keeps the token rhythm in the mobile rule below. */
  gap: 10px var(--space-600);
}
/* Three-column variant, as a class rather than the inline style those forms
   used to carry: an inline grid-template-columns beats every stylesheet rule,
   so the mobile collapse below could never reach it and the fields stayed side
   by side on a phone. It has to sit ABOVE that mobile rule - equal specificity,
   so source order decides which one a phone gets. */
.portal-form-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.portal-form-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-400);
}
.portal-checks {
  display: flex;
  flex-direction: column;
  gap: var(--space-300);
  justify-content: center;
}
.portal-checks .co-checkbox-row { margin: 0; }
/* The export picker reuses .portal-checks inside a scrollable dialog.
   The form-context centering must not apply there: once the list overflows
   its max-height, justify-content:center pushes half the overflow ABOVE the
   scroll container's start edge, where no scrollbar can reach it - the first
   rows become unclickable. */
.portal-export-cols { justify-content: flex-start; }
/* Cards have no intrinsic child gap - sections whose content is bare
   siblings stack with this. Matches the Card Payments section's rhythm. */
.portal-card-stack { display: flex; flex-direction: column; gap: 26px; }   /* desktop; phone below */
/* Settings -> Tiers: one group per tier under a .co-section-label (the
   checkout's Contact / Address / Shipping Method heading), a hairline between
   groups. Adapter only - the label itself is the approved rule. */
.portal-tier-group > .co-section-label {
  margin-bottom: var(--space-300);
  /* The approved label is placeholder-grey because checkout uses it as a hint;
     here it is the group's heading, so it takes the body text colour. */
  color: var(--color-text-base);
}
.portal-tier-group + .portal-tier-group { border-top: 1px solid var(--color-border-subtle); padding-top: var(--space-500); }
.portal-bulkbar {
  display: flex;
  align-items: center;
  gap: var(--space-400);
  flex-wrap: wrap;
  margin-top: var(--space-500);
  padding-top: var(--space-500);
  border-top: 1px solid var(--color-border-subtle);
}
.portal-inline-input {
  width: 100px;
  padding: var(--space-300);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-300);
  font: inherit;
  font-size: 13px;
  text-align: right;
  transition: border-color 0.25s var(--portal-ease), background 0.25s var(--portal-ease);
}
.portal-inline-input:focus { outline: 2px solid var(--color-border-focus); outline-offset: 0; }
.portal-inline-input.is-dirty { border-color: var(--color-bg-fill-magic); background: var(--color-bg-fill-magic-soft); }
.portal-row-variations > td { background: var(--color-bg-fill-strong); }
.portal-toggle { cursor: pointer; }

@media (max-width: 767px) {
  .portal-form-grid { grid-template-columns: 1fr; gap: var(--space-500) var(--space-600); }
  .portal-card-stack { gap: var(--space-500); }
}

/* ── SweetAlert2 checkbox rows ──────────────────────────────
   pSwal puts the themed input class on EVERY input type, and .co-input is a
   48px-tall single-line field. A checkbox whose label wraps therefore spills
   out of its own box, and the border crossing the text reads as a strikethrough
   - which is exactly how it was reported. A checkbox row is not a text field:
   let it grow, keep 48px as a floor rather than a ceiling, and give the label
   room. Fixed here, not in the one dialog, so the next one inherits it. */
.swal2-checkbox.co-input {
  height: auto;
  min-height: 48px;
  align-items: center;
  gap: var(--space-300);
  padding: var(--space-300) var(--space-400);
  text-align: left;
}
.swal2-checkbox.co-input input {
  flex: 0 0 auto;
  margin: 0;
}

/* ── Destructive actions ────────────────────────────────────
   The approved sheet has .button--success but no counterpart, so deleting a
   contractor looked exactly like going back to the list. This ADDS a variant -
   it changes no existing button - and uses the `critical` tokens the design
   system already defines, matching how --success is built.

   Soft by default, solid on hover/focus: these sit in table rows next to
   ordinary actions, and a row of solid red reads as an error state rather than
   a set of buttons. The intent shows on approach, which is when it matters. */
.button--critical {
  background: var(--color-bg-fill-critical-soft);
  border-color: var(--color-border-critical-soft);
  color: var(--color-text-critical);
}
.button--critical:hover,
.button--critical:focus-visible {
  background: var(--color-bg-fill-critical);
  border-color: var(--color-bg-fill-critical);
  color: var(--color-text-adaptive);
}

/* ── Wholesale application ──────────────────────────────────
   The sign-in panel is a single column of four fields, so .auth-shell caps
   at 480px. The application is a real form - two columns, three sections -
   and at 480px it reads as an endless ribbon. A MODIFIER, not a change to
   .auth-shell: login.php shares that class and must keep its width.
   Doubled class so it cannot lose to the approved rule on load order. */
.auth-shell.auth-shell--wide { max-width: 900px; }

/* A file input renders its own button inside the field, which sits taller
   than typed text and leaves the control looking cramped against the
   others in the row. */
.co-input[type="file"] { padding-top: 11px; padding-bottom: 11px; }

/* ── SweetAlert2, themed to tokens ──────────────────────────── */
.portal-swal {
  border-radius: var(--radius-600) !important;
  font-family: "Nunito Sans", sans-serif !important;
  padding: var(--space-700) !important;
  box-shadow: var(--shadow-400) !important;
}
.portal-swal-title {
  font-family: "Anybody", sans-serif !important;
  color: var(--color-text-strong) !important;
  font-size: 22px !important;
}
.portal-swal-text { color: var(--color-text-base) !important; }
.portal-swal .swal2-actions { gap: var(--space-400); }
.portal-swal-input { margin: var(--space-500) auto 0 !important; }
.swal2-icon.swal2-question { border-color: var(--color-border-brand-soft) !important; color: var(--color-text-brand) !important; }
.swal2-icon.swal2-success { border-color: var(--color-text-success) !important; }
.swal2-icon.swal2-success [class^="swal2-success-line"] { background-color: var(--color-text-success) !important; }
.swal2-icon.swal2-success .swal2-success-ring { border-color: var(--color-border-success-soft) !important; }
.swal2-timer-progress-bar { background: var(--color-bg-fill-magic) !important; }

/* Toasts: compact - the modal styling above must not apply at glance
   size. !important mirrors the base rules it overrides. */
.portal-swal.portal-toast {
  width: auto !important;
  max-width: 420px;
  padding: var(--space-300) var(--space-400) !important;
}
.portal-toast .swal2-title {
  font-family: "Nunito Sans", sans-serif !important;
  font-size: 14px !important;
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
  text-align: left;
  margin: 0;
}
.portal-toast .swal2-icon {
  transform: scale(0.65);
  margin: 0;
}

/* ── DataTables, themed to tokens ───────────────────────────── */
.dt-container { font-size: 14px; }
.dt-container .dt-length select {
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-400);
  padding: var(--space-300) var(--space-400);
  font: inherit;
  background: var(--color-bg-surface);
  margin-bottom: var(--space-400);
}
.portal-dt-field { position: relative; display: inline-flex; align-items: center; }
.portal-dt-field .icon-svg {
  position: absolute; left: var(--space-400);
  top: 13px;
  color: var(--color-text-subtle); pointer-events: none;
}
.dt-container .dt-search input {
  height: 44px; width: 260px; max-width: 100%;
  padding: 0 var(--space-400) 0 44px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-300);
  background: var(--color-bg-surface);
  font: inherit;
  margin: 0 2px var(--space-400) 0; /* 2px inset: the focus ring must not be
                                       clipped by the table wrap's overflow */
}
.dt-container .dt-search input:focus { outline: 2px solid var(--color-border-focus); outline-offset: 0; }
/* The vendor's div.dt-container .dt-search input outranks the rule above on
   specificity, so its 5px padding was winning and text sat behind the
   magnifier. The .dt-input class wins it back (prescribed values). */
.dt-container .dt-search input.dt-input { padding-left: 28px; }
@media (max-width: 720px) {
  .portal-dt-field, .dt-container .dt-search input { width: 100%; }
}
.dt-container .dt-paging .dt-paging-button {
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-400);
  background: var(--color-bg-surface);
  color: var(--color-text-base) !important;
  padding: var(--space-300) var(--space-400);
  margin: 0 2px;
  transition: transform 0.3s var(--portal-ease);
}
.dt-container .dt-paging .dt-paging-button.current {
  background: var(--color-bg-fill-brand);
  border-color: var(--color-bg-fill-brand);
  color: var(--color-text-adaptive) !important;
}
.dt-container .dt-paging .dt-paging-button:hover:not(.disabled):not(.current) {
  background: var(--color-bg-fill-strong);
  border-color: var(--color-border-strong);
}
.dt-container .dt-info { color: var(--color-text-subtle); padding-top: var(--space-500); }
.dt-container .dt-processing {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-400);
  box-shadow: var(--shadow-400);
  color: var(--color-text-base);
}
table.dataTable thead th { border-bottom: 1px solid var(--color-border-base); }
table.dataTable tbody tr:hover { background: var(--overlay-brand-3); }

/* Admins table: styles.css clamps every .dashboard-table 4th column to 80px
   (authored for a different table shape) - Last Login holds a full timestamp. */
#dt-admins th:nth-child(4), #dt-admins td:nth-child(4) { width: auto; min-width: 170px; }
#dt-admins th:nth-child(5), #dt-admins td:nth-child(5) { width: auto; }

/* ── Action cells (aligned, D-fix) ────────────────────────────────── */
.portal-actions-cell {
  display: flex;
  gap: var(--space-300);
  align-items: center;
  justify-content: flex-end;
  flex-wrap: nowrap;
  white-space: nowrap;
}
.portal-child-table { width: 100%; }
.portal-child-table td { vertical-align: middle; }
.portal-topbar-cart { position: relative; }
.portal-topbar-cart .cart-counter {
  position: absolute;
  top: -6px;
  right: -8px;
}

/* ── Catalog chips, hearts, PDP ───────────────────────────────────── */
.portal-cat-chips {
  display: flex;
  gap: var(--space-300);
  flex-wrap: wrap;
  margin-bottom: var(--space-600);
}
.portal-cat-chip {
  padding: var(--space-300) var(--space-500);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-full);
  background: var(--color-bg-surface);
  color: var(--color-text-base);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transition: transform 0.3s var(--portal-ease), background 0.3s var(--portal-ease);
}
.portal-cat-chip:hover { background: var(--color-bg-fill-strong); }
.portal-cat-chip.is-active {
  background: var(--color-bg-fill-brand);
  border-color: var(--color-bg-fill-brand);
  color: var(--color-text-adaptive);
}

/* White product cards, as BH-USA prefers. The approved .product-media is
   background: var(--overlay-teal-6) (styles.css:1620) - a deliberate deviation
   from an approved component, overridden here because styles.css is the
   BH-USA's file and is never edited. Plain .product-media is enough: the
   variant selectors in styles.css set height and padding, never background,
   and portal.css loads last. --color-bg-surface is #ffffff in tokens.css. */
.product-media { background: var(--color-bg-surface); }

/* The heart sat in normal flow (pushed the image down) and under the
   card's stretched block link (z-index 1), so it was unclickable. Same
   layering the approved card gives .product-actions. */
.product-media .portal-heart {
  position: absolute;
  top: var(--space-300);
  right: var(--space-300);
  z-index: 2;
}
.portal-heart.is-saved { color: var(--color-text-critical); }
.portal-heart.is-saved .icon-svg { fill: currentColor; }

/* ── PDP inside the portal shell ──────────────────────────────────────
   The product page uses the approved pdp-* system from styles.css. Those
   rules assume the retail site's full-bleed layout - a .section/.shell
   wrapper for padding and a tall sticky header - so these few overrides
   adapt them to the dashboard content column. Nothing here restyles the
   components themselves. */
.portal-qty { width: 84px; text-align: center; }

.dashboard-content .pdp-hero {
  padding-top: 0;
  padding-bottom: var(--space-700);
  background: transparent;
}
/* Site header is ~200px tall; the portal's topbar is slim. */
.dashboard-content .pdp-gallery { top: var(--space-800); }

.dashboard-content .pdp-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-300);
  margin-bottom: var(--space-500);
  color: var(--color-text-subtle);
}

/* The details band is a full-bleed tint on the site; here it is a card. */
.dashboard-content .pdp-details-band,
.dashboard-content .pdp-related-section {
  padding: var(--space-700);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-400);
  margin-bottom: var(--space-600);
}
.dashboard-content .pdp-related-section { background: var(--color-bg-surface); }
.dashboard-content .pdp-details-band .section-head,
.dashboard-content .pdp-related-section .section-head { display: flex; }

/* Price row: keep the qty box from being stretched by the flex row. */
.pdp-price-row .portal-qty { flex: 0 0 auto; }
.pdp-price-row > div:first-child { flex: 1; min-width: 0; }

/* The design mock had 8 gallery images; real catalog products carry 20+, and
   a plain flex row shrinks each thumb to a sliver. Scroll the strip instead. */
.dashboard-content .pdp-gallery-thumbs {
  overflow-x: auto;
  padding-bottom: var(--space-200);
  scrollbar-width: thin;
}
.dashboard-content .pdp-thumb { flex: 0 0 auto; }

/* Author display: rules outrank the UA stylesheet's [hidden]{display:none},
   so JS-toggled elements need this spelled out - the trap documented in
   FRONTEND-CONVENTIONS.md and already hit by .review-card / .dashboard-modal. */
.dashboard-savings-tag[hidden] { display: none; }
.co-form-group[hidden] { display: none; }
.cart-total-row[hidden] { display: none; }  /* the tax row waits for calc_tax */
.dashboard-note[hidden] { display: none; }  /* .dashboard-note is display:flex */

/* ── Bulk panel inside the SweetAlert2 dialog ──────────────── */
/* The toolbar bar became a popup, so it stacks instead of running as one
   long row. Left-aligned because the dialog centres its content and a form
   reading down the middle is hard to scan. */
.portal-bulk-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-500);
  text-align: left;
}
.portal-bulk-panel > span {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-400);
}
/* Those groups are toggled with [hidden] and now set display - the trap
   documented in FRONTEND-CONVENTIONS.md, hit here for the third time. */
.portal-bulk-panel > span[hidden] { display: none; }
.portal-bulk-panel .portal-inline-input { max-width: 120px; }

/* The reference grid is 4-up across the full-width site. The portal's content
   column is narrower (sidebar), where 4 columns clip the card buttons. */
.dashboard-content .pdp-related-grid {
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}

.pdp-save-link.is-saved .pdp-save-icon--fill { opacity: 1; transform: scale(1); }
.pdp-save-link.is-saved .pdp-save-icon--line { opacity: 0; }

/* Under-price info rows read as one card (by design) - the same
   surface recipe as the approved .pdp-return-policy panel. */
.pdp-shipping-box {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-300);
  padding: var(--space-600);
}

.portal-wp-content img { max-width: 100%; height: auto; }
.portal-wp-content p { margin-bottom: var(--space-400); }
.portal-wp-content ul, .portal-wp-content ol { padding-left: var(--space-600); margin-bottom: var(--space-400); }

@media (max-width: 900px) {
  .dashboard-content .pdp-hero-layout { grid-template-columns: 1fr; }
  .dashboard-content .pdp-gallery { position: static; }
}

/* ── Cart + checkout inside the portal shell ─────────────
   Checkout keeps the approved two-column grid (styles.css:5475) inside the
   dashboard column - the shell already supplies page padding, and the rail
   narrows slightly so the flow keeps room. */
.dashboard-content .checkout-main {
  max-width: none;
  margin: 0;
  padding: 0;
  grid-template-columns: minmax(0, 1fr) 360px;
}
/* The site hides the rail on small screens in favor of its cart drawer
   (styles.css ~6551, at ≤767px - where the full-bleed page's content column
   IS the viewport). The portal column runs ~425px narrower (sidebar + gap +
   shell padding), so the same content width arrives at ~1185px; the portal
   has no drawer, so stack the rail under the flow instead of hiding it. */
@media (max-width: 1185px) {
  /* `align-items: start` on the approved .checkout-main is a BLOCK-axis
     instruction while it is a grid, and becomes a CROSS-axis one the moment
     this rule makes it a flex column: the columns stop stretching and size to
     their content instead. .checkout-steps-col then sat at its 339px
     max-content inside a 312px dashboard column and pushed the whole page
     65px sideways on a phone. Restoring stretch is the fix. */
  .dashboard-content .checkout-main {
    display: flex;
    flex-direction: column;
    align-items: stretch;
  }
  .dashboard-content .checkout-cart-col { display: flex; position: static; }
}

.dashboard-content .confirm-hero {
  padding: var(--space-800) var(--space-600) var(--space-700);
  margin-bottom: var(--space-600);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-400);
}

/* One shipping arrangement is in force portal-wide, so the card states it
   rather than offering a choice - it must not look clickable. */
.dashboard-content .shipping-method-card { cursor: default; }
.dashboard-content .shipping-method-card input { pointer-events: none; }

/* Wholesale-only totals row: what the tier pricing saved on this order. */
.portal-total-row--save .cart-total-value { color: var(--color-text-success); }
.portal-was { text-decoration: line-through; }

/* 17 catalog products have no image in WooCommerce; keep the row aligned. */
.portal-thumb-empty {
  display: block;
  flex: 0 0 auto;
  background: var(--color-bg-fill-strong);
}

/* Cart rows are flat columns: image | info | qty | price | remove.
   The info column's flex:1 supplies the breathing space. */
.cart-item { align-items: center; }
.cart-item .cart-item-thumb { width: 80px; height: 80px; }
.cart-item .cart-qty-row { margin-top: 0; }
.cart-item .dashboard-table-remove { color: var(--color-text-critical); }

/* Checkout rail items are compact. Placed after the cart-item rules
   above: the thumb selectors tie on specificity, so source order decides. */
.checkout-cart-col .cart-item-thumb { width: 60px; height: 60px; }
.checkout-cart-col .cart-items-list { max-height: 50vh; overflow-y: auto; }

/* Review step: Shipping and Payment share a row on desktop. */
@media (min-width: 721px) {
  .review-blocks { display: grid; grid-template-columns: 1fr 1fr; align-items: stretch; }
}
.portal-item-qty { white-space: nowrap; color: var(--color-text-subtle); }

/* Order detail: the status stepper replaces the Status card, leaving
   two stat cards rather than the three-up the grid assumes. */
.portal-stats--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.portal-stats--single { grid-template-columns: 1fr; }
.portal-note--critical {
  border-color: var(--color-border-critical-soft);
  background: var(--color-bg-fill-critical-soft);
}
/* An order's history isn't navigable, so its nodes must not look pressable. */
.co-step-node:not(button) { cursor: default; }

/* ── Settings: sticky save bar ──────────────────────────────── */
/* Sticky rather than fixed, so it settles naturally at the end of the form
   instead of covering content on short pages. */
.portal-save-bar {
  position: sticky;
  bottom: var(--space-500);
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-500);
  margin-top: var(--space-600);
  padding: var(--space-500) var(--space-600);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-400);
  background: var(--color-bg-surface);
  box-shadow: var(--shadow-400);
}
/* Below 767px the Directory pill floats at bottom: 24px and is 48px tall, so
   it owns the bottom 72px of the screen - and it sits at z-index 150 against
   this bar's 20. Ordinary content scrolls out from under the pill; a STICKY bar
   never does, so Save Settings was permanently covered. 24 + 48 + 16
   puts the bar clear above it with the usual gap. The pill is the approved
   control and appears on every page, so the bar is the one that moves. */
@media (max-width: 767px) {
  .portal-save-bar { bottom: 88px; }
}
.portal-save-status {
  margin-right: auto;
  color: var(--color-text-subtle);
  font-size: 14px;
}
.portal-save-bar.is-dirty { border-color: var(--color-border-critical-soft); }
.portal-save-bar.is-dirty .portal-save-status {
  color: var(--color-text-critical);
  font-weight: 700;
}

/* Environment picker: two options read better side by side than stacked. */
.portal-env-toggle { margin-bottom: var(--space-600); }
/* Grid, not flex-wrap: the labels are long enough that flex drops the second
   option onto its own line at 1280px. Columns let each wrap internally and
   stay side by side, collapsing to one only on genuinely narrow screens. */
.portal-env-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: var(--space-400) var(--space-600);
  align-items: start;
}
.portal-env-row .co-checkbox-row { margin: 0; }

.portal-cred-set { margin-bottom: var(--space-600); }
/* .co-form-group sets display, which outranks the UA stylesheet's [hidden]
   - the trap in FRONTEND-CONVENTIONS.md. Without this both credential sets
   render at once. */
.portal-cred-set[hidden] { display: none; }
.portal-cred-heading { display: flex; align-items: center; gap: var(--space-300); }

/* ── Email toggles ──────────────────────────────────────────── */
.portal-mail-toggle { align-items: flex-start; }
.portal-mail-toggle > span { display: flex; flex-direction: column; gap: var(--space-100); }
.portal-mail-toggle input[type="checkbox"] { margin-top: 3px; }
.portal-mail-dupe {
  display: flex;
  align-items: flex-start;
  gap: var(--space-200);
  margin-top: var(--space-200);
  padding: var(--space-300) var(--space-400);
  border-radius: var(--radius-300);
  border: 1px solid var(--color-border-critical-soft);
  background: var(--color-bg-fill-critical-soft);
  color: var(--color-text-base);
}
.portal-mail-dupe .icon-svg { flex: 0 0 auto; margin-top: 2px; }

/* ── Quick-order autocomplete, checkout addresses, impersonation ──── */
.portal-autocomplete {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 30;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-400);
  box-shadow: var(--shadow-400);
  overflow: hidden;
  margin-top: var(--space-200);
}
.portal-ac-item {
  display: flex;
  gap: var(--space-300);
  align-items: baseline;
  width: 100%;
  text-align: left;
  padding: var(--space-400) var(--space-500);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--color-border-subtle);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.25s var(--portal-ease);
}
.portal-ac-item:hover { background: var(--color-bg-fill-strong); }
.portal-ac-item:last-child { border-bottom: 0; }
.portal-ac-sku {
  font-family: ui-monospace, Consolas, monospace;
  font-size: 12px;
  color: var(--color-text-brand);
  flex-shrink: 0;
}
.portal-ac-price { margin-left: auto; font-weight: 700; flex-shrink: 0; }
.portal-ac-none { padding: var(--space-400) var(--space-500); color: var(--color-text-subtle); font-size: 13px; }

.portal-address-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-400);
}
.portal-address-option {
  display: flex;
  gap: var(--space-400);
  align-items: flex-start;
  padding: var(--space-500);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-400);
  cursor: pointer;
  transition: border-color 0.25s var(--portal-ease), background 0.25s var(--portal-ease);
}
.portal-address-option:has(input:checked) {
  border-color: var(--color-border-focus);
  background: var(--color-bg-fill-info-subtle);
}
.portal-address-option input { margin-top: 3px; }
.portal-address-body { display: flex; flex-direction: column; gap: var(--space-100); }

.portal-impersonation {
  display: flex;
  align-items: center;
  gap: var(--space-400);
  padding: var(--space-300) max(40px, calc((100% - 1920px) / 2));
  background: var(--color-bg-fill-warning-soft);
  border-bottom: 1px solid var(--color-bg-fill-warning);
  font-size: 13px;
}
.portal-impersonation a { font-weight: 700; margin-left: auto; white-space: nowrap; }

/* ── Payment ────────────────────────────────────────────────── */
/* styles.css ships .co-helper--success but no warning variant; this follows
   the same one-property pattern rather than restyling the component. */
.co-helper--warn { color: var(--color-text-warning); }
/* The card form was the only form without a gap - labels sat flush on the
   field above. display:flex beats [hidden], so the override rides
   along (the documented trap - saved cards start with this form hidden). */
.portal-card-fields { display: flex; flex-direction: column; gap: var(--space-500); }
.portal-card-fields[hidden] { display: none; }
/* .co-helper is display:flex, which beats [hidden] - the pay error held its
   space while "hidden" (the documented trap). */
.co-helper[hidden] { display: none; }
/* The reference's card-brands row uses hosted logo images. The portal has no
   outbound image budget on a checkout page, so the same row carries text
   chips - same component, same spacing, no third-party request. */
.card-brands .portal-card-brand {
  padding: var(--space-100) var(--space-300);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-200);
  font-size: 12px;
  font-weight: 700;
  color: var(--color-text-subtle);
}

/* ── PDP description in the accordion ───────────────────────── */
/* The approved 7/5 description layout was designed for the full-bleed band
   where the media is an installation photo cropped with object-fit: cover.
   In the details card the media is a catalog pack shot - contain it on a
   soft field instead of cropping it. */
.pdp-spec-content .pdp-description-layout { min-height: 360px; gap: var(--space-600); }
.pdp-spec-content .pdp-description-media {
  background: var(--color-bg-fill-brand-soft);
  border-radius: var(--radius-400);
}
.pdp-spec-content .pdp-description-media img { object-fit: contain; padding: var(--space-600); }
/* No image to pair with: let the text use the room. */
.portal-desc-full { max-width: none; }

/* Quick Order row: one merged search field carries SKU *and* name, so
   the approved 206px SKU column starves it while Qty inherits the 1fr meant
   for the product-name column. Same adapter treatment as the negotiated
   form: search grows, Qty is a number. */
.portal-quick-order-form.dashboard-quick-form {
  grid-template-columns: minmax(320px, 1fr) 120px auto auto;
}

/* Negotiated-prices quick form. The approved .dashboard-quick-form
   grid (206px / 1fr / 89px / auto) is shaped for Quick Order's SKU row; here
   the PRODUCT search carries the content and the price is just a number, so
   the columns invert - adapter override, scoped by modifier. */
.portal-override-form.dashboard-quick-form {
  grid-template-columns: minmax(320px, 1fr) 160px auto;
}
.dashboard-quick-field--product .portal-autocomplete { min-width: 100%; }
.portal-override-form .button { white-space: nowrap; }

/* Webhook setup table: fixed layout so no column gets crushed. */
.portal-setup-table { table-layout: fixed; }
.portal-setup-table th:nth-child(1) { width: 22%; }
.portal-setup-table th:nth-child(2) { width: 38%; }
.portal-setup-table td { vertical-align: top; }
.portal-setup-table code { white-space: normal; word-break: break-all; }

/* ── Catalog full re-sync progress ──────────────────────────── */
/* The approved system has no progress component, so this is a new one rather
   than a restyle. `hidden` needs the override because .dashboard-note sets
   display (the FRONTEND-CONVENTIONS trap). */
.portal-sync-progress[hidden] { display: none; }
.portal-progress {
  height: 6px;
  margin-top: var(--space-300);
  border-radius: var(--radius-full);
  background: var(--color-bg-fill-brand-soft);
  overflow: hidden;
}
.portal-progress-bar {
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--color-bg-fill-brand);
  transition: width 0.4s ease;
}

/* .dashboard-content is the item sitting in the layout's content column, and
   it defaults to min-width: auto - so one wide table's min-content drags the
   column past the viewport instead of scrolling inside .dashboard-table-wrap.
   An earlier fix covered ≤767px only, which left the bug alive at 768px: the
   sidebar is back, the content column is ~430px, and the recent-orders table
   pushed the page 48px sideways on a tablet in portrait. Nothing about it was
   ever mobile-specific - a column that can shrink is right at every width. */
.portal-app .dashboard-content { min-width: 0; }

/* ── Responsive ───────────────────────────────────────────────────── */

/* Both quick-entry rows have a hard 320px floor on their first column, and
   need ~583px all told. The dashboard column only clears that around 935px
   wide, so the collapse has to cover tablets too - not just phones, which is
   as far as that earlier fix took it (quick-order scrolled 167px sideways at 768px). */
@media (max-width: 1023px) {
  .portal-quick-order-form.dashboard-quick-form,
  .portal-override-form.dashboard-quick-form {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Same story for the dashboard's card grids, and the same reasoning used
   for the checkout rail: the approved breakpoints are written against a
   full-bleed page where the content column IS the viewport, but the portal's
   column runs ~380px at 768px - phone-sized. So the cards kept their tablet
   grids (stats 3-up, benefits 2-up) and got 62px and 130px cells, which is how
   "$11,030.30" and "Discounts" pushed the page 48px sideways on an iPad in
   portrait. These are the approved ≤560px values, applied at the width where
   the COLUMN is that narrow rather than the viewport.

   Floored at 768 on purpose: below that the sidebar is gone, the column is the
   viewport again, and the approved phone rules (2-up stats) already fit - this
   block would only make them sparser than the design asks for. */
@media (min-width: 768px) and (max-width: 1023px) {
  .portal-app .dashboard-stats,
  .portal-app .dashboard-benefits { grid-template-columns: minmax(0, 1fr); }
  .portal-app .dashboard-benefit-card--active { grid-column: auto; }
  .portal-app .dashboard-tier-card-top { flex-wrap: wrap; }
}

@media (max-width: 960px) {
  .portal-topbar { padding-left: var(--space-500); padding-right: var(--space-500); }
  .portal-topbar-search { display: none; }
}
@media (max-width: 767px) {
  .portal-brand img { height: 32px; max-width: 100%; }
  /* ...and let the brand give way instead of dictating the bar's minimum. */
  .portal-brand { flex-shrink: 1; min-width: 0; }

  /* The whole page scrolled sideways on a phone. Measured, not guessed:
     425px viewport, 486px of scroll, and the widest thing on it was the recent
     orders table at 540px.

     The table was NOT the bug. It already sits in .dashboard-table-wrap, which
     is overflow-x: auto - it simply never got the chance to scroll, because the
     wrapper was being stretched instead of constrained. The approved mobile
     layout collapses to `grid-template-columns: 1fr`, and 1fr is
     minmax(AUTO, 1fr): `auto` resolves to min-content, so one wide table drags
     the entire column past the viewport. The desktop rule above it gets this
     right - it says minmax(0, 1fr) - and the mobile one does not.

     minmax(0, 1fr) lets the column shrink, and the wrap takes the scroll back
     inside itself where it belongs. (The .dashboard-content half of this pair
     is NOT mobile-only - see the unconditional rule above.) */
  .portal-app .dashboard-layout { grid-template-columns: minmax(0, 1fr); }

  /* Long unbreakable strings set a min-content width the column cannot go
     under: the shell one-liner in Settings' help text measured 329px and took
     the page with it. `anywhere` (not `break-word`) is the one that also
     shrinks min-content, which is what a grid or flex track actually reads. */
  .dashboard-content code,
  .dashboard-content .dashboard-helper,
  .dashboard-content .dashboard-table-meta { overflow-wrap: anywhere; }

  /* The approved design floats the catalog's sort toolbar as a pill at
     bottom-centre - position: fixed, bottom: 24px, left: 50%, translateX(-50%).
     That is the EXACT slot the Directory pill occupies, because the design's
     catalog had no dashboard directory to share the screen with. The portal's
     does, and the two drew on top of each other.

     Owner's call: the floating slot belongs to Directory, and the sort control
     goes back into the flow. .catalog-topbar reserves 88px of bottom margin for
     a toolbar that is no longer floating, which was the empty band above the
     category chips, so that comes back to the normal 24px too. The pill's own
     skin (border, radius, shadow) is left alone - in flow it reads as an
     ordinary control, which is what it now is. */
  .portal-app .catalog-toolbar-row {
    position: static;
    transform: none;
    width: 100%;
    justify-self: stretch;
  }
  .portal-app .catalog-sort,
  .portal-app .catalog-sort select { flex: 1; width: 100%; }
  .portal-app .catalog-topbar { margin-bottom: var(--space-500); }

  /* Checkout scrolled sideways by 65px at 360px. The approved stepper puts its
     steps on `flex: 1 0 0` - grow, but DO NOT SHRINK - and the compact mobile
     stepper that would rescue them is scoped to `.form-stack`, the design's
     dealer-application flow. The portal's checkout is not inside one, so it got
     the full-size desktop stepper on a phone: two 170px steps that refused to
     give, in a 312px column. Letting them shrink is the whole fix - the labels
     are "Shipping" and "Review" and sit comfortably in half the width each.
     (The other half of that 65px was the stretch bug fixed at ~line 674.) */
  .portal-app .checkout-stepper .co-step { flex: 1 1 0; min-width: 0; }

  /* Two admin pages scrolled sideways by exactly the amount their <h1> stuck
     out: "Contractors" is one unbreakable word and measures 362px at the 42px
     display size, in a 260px column. The contractor side's titles all have a
     space to break on, so they wrap and fit - this is admin-only. The next step
     down the approved scale (xl, 28px) puts the longest admin title at 241px.  */
  .admin-app h1.type-heading-2xl {
    font-size: var(--type-heading-xl-size);
    line-height: var(--type-heading-xl-line);
    letter-spacing: var(--type-heading-xl-track);
  }

  /* Same story on the admin dashboard: the approved rule pins the stat value at
     a flat 26px below 430px, which fits the contractor figures it was drawn for
     ("$4,120") but not the admin ones - "$36,216.32" needs 177px in a 125px
     card. The clamp is the approved system's own idiom, used a breakpoint
     higher up; it just never reached this width. */
  .admin-app .dashboard-stat-value { font-size: clamp(16px, 4.5vw, 26px); }

  /* A cart row is one non-wrapping flex line: thumb, name, stepper, price,
     remove. The name is the only flexible track, so at 360px it was handed
     about 50px and rendered one character per line. Let the row wrap - name
     beside the thumb, controls on a line of their own - and give the info
     block a basis wide enough that it cannot be the thing that wraps first. */
  .cart-item { flex-wrap: wrap; }
  .cart-item-info { flex: 1 1 60%; }
  .cart-qty-row { flex: 0 0 auto; }
  .cart-item-price { margin-left: auto; }
}

/* The brand tag ("Wholesale"/"Admin") measures 104px and the logo 217px. Add
   the gaps and the cart button and the top bar needs a 429px viewport - below
   that the tag ran straight into the cart icon and the two overlapped. The tag
   is branding, not navigation, and the logo beside it already says whose portal
   this is, so it is the part that gives way. */
@media (max-width: 479px) {
  .portal-brand-tag { display: none; }
}
