/*
  Component styles. Each selector here is scoped to one ViewComponent.
  Depends on design/tokens/* and shell.css for layout chrome (sidebar/topbar).
*/

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

/* [hidden] must win over any display-setting utility/component class
   (.field, .cluster, .stack, etc. all set display: flex, which otherwise
   out-specifies the UA stylesheet's [hidden] rule) -- without this,
   toggling the hidden attribute silently fails to hide the element. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font-family: var(--font-family-base);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  background: var(--color-bg-page);
  line-height: var(--line-height-normal);
}

a { color: var(--color-primary); }

:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* ---------- Button (-> ButtonComponent) ----------
   .btn is used on both <button> and <a> (e.g. a "New sale" link styled as a
   primary action) -- text-decoration: none keeps the two visually identical,
   since only the <a> case would otherwise pick up a browser-default underline. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  font-size: var(--font-size-body);
  line-height: var(--line-height-tight);
  padding: var(--space-4) var(--space-6);
  border-radius: var(--radius-md);
  border: var(--border-width) solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.btn:disabled { cursor: not-allowed; opacity: 0.5; }
.btn:active:not(:disabled) { transform: translateY(1px); }

/* Loading state (-> ButtonComponent, state: loading): a static spinner
   swap-in, distinct from :disabled (opacity dims the whole button; loading
   keeps full color so the action still reads as "in progress," not
   "unavailable"). Real usage disables the button via the disabled attribute
   AND adds .is-loading so both states apply together during submission. */
.btn.is-loading { cursor: wait; gap: var(--space-3); }
.btn.is-loading .btn__spinner {
  width: 1em;
  height: 1em;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: btn-spin var(--duration-slow) linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .btn.is-loading .btn__spinner { animation: none; }
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

.btn--primary { background: var(--color-primary-vivid); color: var(--color-text-on-brand); box-shadow: var(--shadow-primary); }
.btn--primary:hover:not(:disabled) { background: var(--color-primary-hover); }

.btn--secondary { background: var(--color-bg-surface); color: var(--color-text-primary); border-color: var(--color-border-strong); }
.btn--secondary:hover:not(:disabled) { background: var(--color-bg-surface-sunken); }

.btn--danger { background: var(--color-danger-vivid); color: var(--color-text-on-brand); box-shadow: var(--shadow-danger); }
.btn--danger:hover:not(:disabled) { filter: brightness(0.92); }

.btn--ghost { background: transparent; color: var(--color-primary); }
.btn--ghost:hover:not(:disabled) { background: var(--color-primary-surface); }

.btn--sm { padding: var(--space-3) var(--space-5); font-size: var(--font-size-body-sm); }

/* Icon-only remove button for a repeatable row (nav item, homepage module,
   video URL, ...): neutral by default, reddens on hover to signal the
   destructive action without shouting it at rest. */
.icon-remove-btn { color: var(--color-text-secondary); background: none; border: none; cursor: pointer; padding: var(--space-2); border-radius: var(--radius-sm); flex-shrink: 0; }
.icon-remove-btn:hover { background: var(--color-danger-surface); color: var(--color-danger); }

/* ---------- Split button (-> ButtonComponent, split: true) ----------
   A .btn colored trigger with an attached menu, for a primary action that's
   actually a set of equally-weighted choices (e.g. "Create product" ->
   New product / New course / Bulk import) rather than one default action
   plus secondary alternatives. Built as <details class="btn-split"> so the
   whole control -- both the label and the caret -- opens the same menu with
   zero JS, matching the filter-popover/accordion pattern elsewhere in this
   system. The visual "split" (divider + caret segment) is styling only; if
   a future instance needs a real default action distinct from the menu,
   that's a different component (a <button> + separate <details> caret
   sharing one border-radius), not this one. */
.btn-split { position: relative; display: inline-flex; }
.btn-split > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  cursor: pointer;
}
.btn-split > summary::-webkit-details-marker { display: none; }
.btn-split__caret {
  display: inline-flex;
  align-items: center;
  padding-left: var(--space-3);
  margin-left: var(--space-3);
  /* currentColor, not a hardcoded white: the divider must read against
     white text-on-brand (primary/danger) AND dark text (secondary/ghost)
     without a per-variant override. */
  border-left: 1px solid color-mix(in srgb, currentColor 35%, transparent);
}
.btn-split__caret .icon { width: 1em; height: 1em; }
.btn-split[open] > summary .btn-split__caret { transform: scaleY(-1); }

/* Compact action menu (-> MenuComponent), e.g. the btn-split dropdown panel.
   Distinct from .filter-popover__panel: that one is sized for labeled form
   fields (space-6 padding, space-5 gap between fields), which reads as
   oversized whitespace around a plain list of one-line actions. This is a
   tighter list container: no inter-item gap (each item supplies its own
   click-target padding), and slim panel padding since there's no field
   label/hint content to breathe around. */
.menu {
  position: absolute;
  top: calc(100% + var(--space-3));
  right: 0;
  z-index: 10;
  min-width: 12rem;
  background: var(--color-bg-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--space-2);
  display: flex;
  flex-direction: column;
}
.menu-item {
  display: block;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  color: var(--color-text-primary);
  font-size: var(--font-size-body-sm);
  text-decoration: none;
  white-space: nowrap;
}
.menu-item:hover { background: var(--color-bg-surface-sunken); }

/* ---------- Status badge (-> StatusBadgeComponent) ---------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  font-size: var(--font-size-caption);
  letter-spacing: var(--letter-spacing-wide);
  text-transform: uppercase;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.badge::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.badge--success { background: var(--color-success-surface); color: var(--color-success); }
.badge--warning { background: var(--color-warning-surface); color: var(--color-warning); }
.badge--danger  { background: var(--color-danger-surface); color: var(--color-danger); }
.badge--info    { background: var(--color-info-surface); color: var(--color-info); }
.badge--neutral { background: var(--color-bg-surface-sunken); color: var(--color-text-secondary); }

/* Category pill: a badge variant with no dot, used for taxonomy not state */
.pill {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  font-size: var(--font-size-caption);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--color-bg-surface-sunken);
  color: var(--color-text-secondary);
  white-space: nowrap;
}
/* --color-primary-vivid text on --color-primary-surface is ~4.0:1, under AA
   for this text size; --color-primary-hover clears 5.7:1 on the same surface. */
.pill--accent { background: var(--color-primary-surface); color: var(--color-primary-hover); }

/* ---------- Data table panel (-> DataTableComponent) ----------
   One bordered/rounded/shadowed card: bulk-actions bar (hidden until a row
   is checked) + the scrollable table + the paginator, stacked with internal
   dividers instead of each piece carrying its own border/radius. That was
   the previous bug -- .bulk-actions-bar and .data-table each had their own
   border/radius, "attached" only by zeroing the seam between them, and the
   table's own <caption> sat in the flow between them reading as a gap. Now
   the panel owns the outer chrome once; children are plain, edge-to-edge.

   No overflow:hidden here (removed -- see git history for the prior
   version): it clipped position:absolute children that need to escape the
   panel's bounds, e.g. .tooltip-panel on a Payment-column cell. A row-level
   escape hatch (opening the tooltip downward on "the first row") doesn't
   generalize -- filters can leave a single result that's simultaneously
   the first AND last row, with no direction that has room. Corner rounding
   is achieved a different way instead: the actual edge children each carry
   their own matching radius (top corners on whichever renders first --
   the bulk-actions bar when visible, else the table header row -- bottom
   corners on the paginator, the panel's last child). */
.data-table-panel {
  background: var(--color-bg-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.data-table-panel .bulk-actions-bar { display: none; }
.data-table-panel:has(.data-table__select-input:checked) .bulk-actions-bar { display: flex; }
/* Top-left/top-right corners: rounded directly on the first/last header
   cell's OUTER corner via background-clip, not via overflow on an
   ancestor -- any overflow-y other than visible on .table-scroll (even
   just to clip this corner) would also clip .tooltip-panel, which lives
   inside the same scroll container and must be able to escape vertically.
   border-radius on <th> only rounds that cell's own background, so this
   targets just the two corner cells, only when the bulk-actions bar isn't
   the true top edge instead (see its own top-radius rule below). */
.data-table-panel:not(:has(.data-table__select-input:checked)) .data-table thead tr:first-child th:first-child {
  border-top-left-radius: calc(var(--radius-lg) - var(--border-width));
}
.data-table-panel:not(:has(.data-table__select-input:checked)) .data-table thead tr:first-child th:last-child {
  border-top-right-radius: calc(var(--radius-lg) - var(--border-width));
}

.bulk-actions-bar {
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-6);
  background: var(--color-primary-surface);
  border-bottom: var(--border-width) solid var(--color-primary-vivid);
  border-radius: calc(var(--radius-lg) - var(--border-width)) calc(var(--radius-lg) - var(--border-width)) 0 0;
}
.bulk-actions-bar__count { font-size: var(--font-size-body-sm); font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); color: var(--color-primary-hover); }

.data-table { width: 100%; border-collapse: collapse; font-size: var(--font-size-body); }
.data-table th {
  text-align: left;
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  font-size: var(--font-size-caption);
  letter-spacing: var(--letter-spacing-wide);
  text-transform: uppercase;
  color: var(--color-text-secondary);
  padding: var(--space-4) var(--space-6);
  border-bottom: var(--border-width) solid var(--color-border);
  background: var(--color-bg-surface-sunken);
}
.data-table td {
  padding: var(--space-5) var(--space-6);
  border-bottom: var(--border-width) solid var(--color-border);
  vertical-align: middle;
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover { background: var(--color-primary-surface); }
.data-table td.is-numeric, .data-table th.is-numeric { text-align: right; font-variant-numeric: tabular-nums; }
.data-table td.is-center, .data-table th.is-center { text-align: center; }

/* Row selection column. The checkbox's own onclick="event.stopPropagation()"
   (same convention as inner row links elsewhere in this system) keeps a
   click from also firing the row's onclick="location.href=...". A selected
   row gets a persistent tint (not just :hover) so selection stays visible
   without hovering every row. */
.data-table th.data-table__select-col, .data-table td.data-table__select-col { width: 1px; padding-right: 0; }
.data-table__select-input { accent-color: var(--color-primary-vivid); width: 1.125rem; height: 1.125rem; cursor: pointer; }
.data-table tbody tr:has(.data-table__select-input:checked) { background: var(--color-primary-surface); }

/* Paginator: last child of the panel, no bottom border since the panel
   itself ends here. Record-count label replaces the table's old <caption>. */
.paginator {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-6);
  border-top: var(--border-width) solid var(--color-border);
  flex-wrap: wrap;
}
.paginator__count { font-size: var(--font-size-body-sm); color: var(--color-text-secondary); }
.paginator__pages { display: flex; align-items: center; gap: var(--space-2); }
.paginator__btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2rem; height: 2rem; padding: 0 var(--space-3);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  font-size: var(--font-size-body-sm);
  text-decoration: none;
  cursor: pointer;
}
.paginator__btn:hover:not(:disabled) { background: var(--color-bg-surface-sunken); }
.paginator__btn:disabled { color: var(--color-text-disabled); cursor: not-allowed; }
.paginator__btn.is-active { background: var(--color-primary-vivid); border-color: var(--color-primary-vivid); color: var(--color-text-on-brand); }
.paginator__btn .icon { width: 1.1em; height: 1.1em; }
.paginator__ellipsis { color: var(--color-text-secondary); padding: 0 var(--space-1); }

/* Row identity: thumbnail/icon swatch + title + meta, for the leading column */
.row-identity { display: flex; align-items: center; gap: var(--space-4); }
.row-identity__thumb {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius-md);
  background: var(--color-bg-surface-sunken);
  border: var(--border-width) solid var(--color-border);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-secondary);
}
/* Larger variant: a media-picker swatch (e.g. hero image upload), bigger
   than the table-row default but still icon-centered, not a full image
   preview. --empty marks the "add new" affordance, same dashed-border
   convention as .image-slot__box--empty. */
.row-identity__thumb--lg { width: 4.5rem; height: 4.5rem; }
.row-identity__thumb--empty { border-style: dashed; }
.row-identity__title {
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  display: block;
  color: var(--color-text-primary);
  text-decoration: none;
}
.row-identity__title:hover { color: var(--color-primary-vivid); text-decoration: underline; }
/* Plain-weight variant: a linked name in a column with no leading thumb
   (e.g. the Customer column's name-to-profile link), where the row's own
   icon/thumbnail column already carries the visual weight -- a second bold
   title reads as competing hierarchy on the same row. */
.row-identity__title--plain { font-family: var(--font-family-base); font-weight: normal; }

/* Reference link + external-storefront-link affix: a table-cell link that
   isn't the row's own row-identity (e.g. the Sale a row's Order refers to),
   paired with a small "open on the storefront" icon link on the same line.
   Pairs with .cluster--tight.cluster--nowrap on the wrapping element. */
.cell-link { font-size: var(--font-size-body-sm); }
.cell-link__external {
  display: inline-flex;
  color: var(--color-text-secondary);
}
.cell-link__external:hover { color: var(--color-primary-vivid); }
.cell-link__external .icon { width: 0.9rem; height: 0.9rem; }

.row-identity__meta { font-size: var(--font-size-caption); color: var(--color-text-secondary); display: block; }
/* --color-text-secondary is verified >=4.5:1 on white only (see tokens/colors.css);
   a hovered or selected row tints to --color-primary-surface, where it drops
   under AA -- swap to the -on-tint variant in both states. */
.data-table tbody tr:hover .row-identity__meta,
.data-table tbody tr:has(.data-table__select-input:checked) .row-identity__meta {
  color: var(--color-text-secondary-on-tint);
}

/* Whole-row click affordance: the first column's title link is the default
   "open" action, so the row as a whole reads as clickable even though the
   <a> only wraps the title text -- this cursor + hover cue signals that. */
.data-table tbody tr { cursor: pointer; }

/* Inline progress bar, e.g. margin %, payout progress */
.meter { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); }
.meter__track { width: 6rem; height: 6px; border-radius: var(--radius-pill); background: var(--color-bg-surface-sunken); overflow: hidden; }
.meter__fill { height: 100%; background: var(--color-primary-vivid); }
.meter__fill.is-low { background: var(--color-danger-vivid); }
.meter__label { font-size: var(--font-size-caption); font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); color: var(--color-primary-vivid); }
.meter__label.is-low { color: var(--color-danger); }

/* ---------- State tabs (-> StateTabsComponent) ----------
   Mutually-exclusive view-scope selector (e.g. Live/Upcoming/Expired/All).
   Distinct from Chip: tabs are single-select (pick one view), chips are
   multi-select (toggle independent boolean filters). Always positioned
   directly above the Search Bar on a listing page -- the two are read
   top-to-bottom as "which view, then search/filter within it." */
.state-tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-5); list-style: none; margin-left: 0; padding-left: 0; }
.state-tabs--flush { margin-bottom: 0; }
.state-tab {
  display: inline-flex;
  align-items: center;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-pill);
  font-size: var(--font-size-body-sm);
  font-family: var(--font-family-medium); font-weight: var(--font-weight-medium);
  border: var(--border-width) solid var(--color-border-strong);
  color: var(--color-text-secondary);
  background: var(--color-bg-surface);
  cursor: pointer;
  text-decoration: none;
  user-select: none;
}
.state-tab.is-active {
  background: var(--color-primary-surface);
  border-color: var(--color-primary-vivid);
  color: var(--color-primary-vivid);
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
}

/* ---------- Uber-search bar (-> SearchBarComponent) ----------
   Primary control for listing pages: one prominent search input that matches
   across the identifier-ish fields (order #, email, name, etc.), paired with
   a "More filters" popover for the rest and toggle chips for boolean filters.
   Replaces the old pattern of a tall sidebar of individually-labeled fields.

   Listing-page layout convention: [State Tabs (optional)] -> [Search Bar] ->
   [Chips (optional)] -> [Data Table]. Both quick-scope controls (tabs, chips)
   sit adjacent to the search bar, never separated from it by other content. */
.search-bar {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-6);
  background: var(--color-bg-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  margin-bottom: var(--space-5);
}
.search-bar__input-wrap { position: relative; flex: 1; }
.search-bar__input-wrap .icon { position: absolute; left: var(--space-4); top: 50%; transform: translateY(-50%); color: var(--color-text-secondary); }
.search-bar__input-wrap input {
  width: 100%;
  font-family: var(--font-family-base);
  font-size: var(--font-size-body);
  padding: var(--space-4) var(--space-4) var(--space-4) 2.5rem;
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
}
.search-bar__input-wrap input:focus {
  border-color: var(--color-primary-vivid);
  box-shadow: 0 0 0 3px var(--color-primary-surface);
  outline: none;
}
.search-bar__hint { font-size: var(--font-size-caption); color: var(--color-text-secondary); margin: var(--space-2) 0 0; }

/* Boolean/toggle filter chips -- e.g. "Hide freebies". Distinct from Pill
   (taxonomy) and Badge (status): chips are interactive filters, checked = active. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-body-sm);
  font-family: var(--font-family-medium); font-weight: var(--font-weight-medium);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-pill);
  border: var(--border-width) solid var(--color-border-strong);
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  cursor: pointer;
  user-select: none;
}
.chip input[type="checkbox"] { accent-color: var(--color-primary-vivid); }
/* A lone chip with no .cluster sibling, sitting directly in a block-level
   parent -- .chip's inline-flex still stretches to the parent's full width
   without something to shrink-wrap it. */
.chip--standalone { width: fit-content; }
.chip--standalone-spaced { width: fit-content; margin-bottom: var(--space-4); }
.chip:has(input:checked) {
  background: var(--color-primary-surface);
  border-color: var(--color-primary-vivid);
  /* --color-primary-vivid text on --color-primary-surface is ~4.0:1, under
     AA for this text size; --color-primary-hover clears 5.7:1 on the same
     surface. */
  color: var(--color-primary-hover);
}

/* "More filters" popover -- collapsed by default (details/summary needs no JS) */
.filter-popover { position: relative; }
.filter-popover > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  border: var(--border-width) solid var(--color-border-strong);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  font-family: var(--font-family-medium); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-body-sm);
  cursor: pointer;
}
.filter-popover > summary::-webkit-details-marker { display: none; }
.filter-popover[open] > summary { border-color: var(--color-primary-vivid); }
.filter-popover__badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.25rem; height: 1.25rem; border-radius: var(--radius-pill);
  background: var(--color-primary-vivid); color: var(--color-text-on-brand);
  font-size: var(--font-size-caption); font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); padding: 0 var(--space-2);
}
.filter-popover__panel {
  position: absolute;
  top: calc(100% + var(--space-3));
  right: 0;
  z-index: 10;
  width: 22rem;
  background: var(--color-bg-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.filter-popover__panel .field__label {
  font-size: var(--font-size-caption);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-wide);
  color: var(--color-text-secondary);
  display: block;
  margin-bottom: var(--space-2);
}
.filter-popover__panel input,
.filter-popover__panel select {
  width: 100%;
  font-family: var(--font-family-base);
  font-size: var(--font-size-body-sm);
  padding: var(--space-3) var(--space-4);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
}
.filter-popover__footer {
  display: flex;
  justify-content: space-between;
  padding-top: var(--space-4);
  border-top: var(--border-width) solid var(--color-border);
}

/* ---------- Modal (-> ModalComponent) ----------
   Real usage is a native <dialog class="modal"> (see confirm-dialog.js):
   showModal() gives a free focus trap, Esc-to-close, and ::backdrop, so no
   framework/JS beyond wiring the trigger is needed. .modal-backdrop remains
   for the static, non-overlaying doc-page rendering on components/modal.html. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(26, 26, 26, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
}
dialog.modal {
  border: none;
  padding: 0;
  margin: auto;
}
dialog.modal::backdrop {
  background: rgba(26, 26, 26, 0.5);
}
.modal {
  background: var(--color-bg-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  max-width: 480px;
  width: 100%;
}
.modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-6);
  border-bottom: var(--border-width) solid var(--color-border);
}
.modal__title { font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); font-size: var(--font-size-heading-sm); margin: 0; }
.modal__body { padding: var(--space-6); color: var(--color-text-secondary); }
.modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-4);
  padding: var(--space-6);
  border-top: var(--border-width) solid var(--color-border);
}
.modal__close {
  background: none; border: none; cursor: pointer;
  color: var(--color-text-secondary); line-height: 1;
  display: flex; padding: var(--space-2); border-radius: var(--radius-sm);
}
.modal__close:hover { background: var(--color-bg-surface-sunken); }

/* ---------- Section header: icon chip + title (-> SectionHeaderComponent) ---------- */
.section-header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.section-header__icon {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-md);
  background: var(--color-primary-surface);
  color: var(--color-primary-vivid);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.section-header__icon.is-danger { background: var(--color-danger-surface); color: var(--color-danger); }
.section-header__icon.is-warning { background: var(--color-warning-surface); color: var(--color-warning); }
.section-header__title {
  font-size: var(--font-size-heading-md);
  font-family: var(--font-family-bold);
  font-weight: var(--font-weight-bold);
  margin: 0;
}
.section-header__subtitle {
  margin: var(--space-1) 0 0;
  font-size: var(--font-size-caption);
  color: var(--color-text-secondary);
}
.section-header__subtitle .badge { vertical-align: middle; }
.section-header__title .pill { margin-left: var(--space-3); vertical-align: middle; }
.section-header__subtitle a { color: inherit; }

/* Subsection title: a heading inside a panel/console-section, one tier below
   the page's own <h1>/.section-header__title. Used at both <h2> and <h4>
   depending on how deep the panel is nested -- the class carries the size,
   not the tag. */
.subsection-title { font-size: var(--font-size-body-lg); margin: 0 0 var(--space-4); }
.subsection-title--flush { margin: 0; }
/* Tighter bottom margin: an intro paragraph or note follows immediately,
   needing less gap than the default before a table/field-grid. */
.subsection-title--tight { margin-bottom: var(--space-2); }
.subsection-title--tight-3 { margin-bottom: var(--space-3); }
/* Minor tier: a divider heading between field groups within the same panel,
   not the panel's own leading title -- has top spacing since it's never first. */
.subsection-title--minor { font-size: var(--font-size-body); margin: var(--space-6) 0 var(--space-4); }
/* Minor tier, but as the panel's own first/leading heading: no top margin,
   tighter bottom gap since an intro paragraph follows immediately. */
.subsection-title--minor-lead { font-size: var(--font-size-body); margin: 0 0 var(--space-2); }

/* ---------- Tab bar: horizontal section nav (-> TabBarComponent) ----------
   Replaces BOTH the old circular-stepper (which implied a one-way sequence
   that doesn't reflect how these flows are actually used -- users jump
   between sections and save/resume in any order, on Sale edit same as
   Product/Publisher edit) and the old vertical .console-nav column (which
   wasted a fixed 240px of horizontal width). One shared horizontal tab
   bar for all edit surfaces: full-width, no dedicated nav column, content
   gets the space back. Each tab carries an optional attention dot (e.g.
   "this section has missing/flagged data") -- the detailed breakdown of
   what's missing still lives in the Completeness sidebar, not here. */
.tab-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  border-bottom: var(--border-width) solid var(--color-border);
  margin: 0 0 var(--space-6) 0;
  padding: 0;
  list-style: none;
}
.tab-bar__tab {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  color: var(--color-text-secondary);
  text-decoration: none;
  font-size: var(--font-size-body-sm);
  font-family: var(--font-family-medium); font-weight: var(--font-weight-medium);
  cursor: pointer;
}
.tab-bar__tab:hover { color: var(--color-text-primary); background: var(--color-bg-surface-sunken); border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.tab-bar__tab.is-active { color: var(--color-primary-vivid); border-bottom-color: var(--color-primary-vivid); font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); }
.tab-bar__tab .icon { flex-shrink: 0; }
.tab-bar__tab-flag {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-warning-vivid);
  flex-shrink: 0;
}

/* ---------- Accordion (-> AccordionComponent) ----------
   Standalone, composable: any group of <details class="accordion"
   name="..."> siblings sharing the same name attribute keeps only one open
   at a time, natively, with no JS. Not specific to any one surface --
   Builder + Preview (see .builder-preview above) composes this component
   for its multi-section builder column (Navigation, Hero banner, Page
   modules in flows/publisher-edit/homepage.html), but nothing here depends
   on .builder-preview, so it can be reused anywhere a same-page,
   single-open-section pattern is needed. See components/accordion.html. */
.accordion + .accordion { margin-top: var(--space-6); padding-top: var(--space-6); border-top: var(--border-width) solid var(--color-border); }
.accordion__trigger {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--font-size-body-lg); font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  margin: 0; cursor: pointer; list-style: none;
}
.accordion__trigger::-webkit-details-marker { display: none; }
.accordion__trigger::after {
  content: "";
  width: 0.5rem; height: 0.5rem;
  border-right: 2px solid var(--color-text-secondary); border-bottom: 2px solid var(--color-text-secondary);
  transform: rotate(45deg);
  margin-left: auto;
  transition: transform var(--duration-fast) var(--ease-standard);
}
.accordion[open] > .accordion__trigger::after { transform: rotate(225deg); }
.accordion[open] > .accordion__trigger { margin-bottom: var(--space-4); }

/* ---------- Settings console (-> SettingsConsoleComponent) ----------
   Edit surfaces organized as independent sections rather than a linear
   sequence (Sale edit, Product edit, Publisher edit all use this now).
   Navigation between sections is the shared .tab-bar above (one page per
   section, sections visitable/savable in any order) -- this block is just
   the content-column layout and per-section panel styling. */
.console-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--space-6);
  align-items: start;
}
@media (max-width: 1100px) {
  .console-layout { grid-template-columns: minmax(0, 1fr); }
}
.console-section {
  scroll-margin-top: var(--space-6);
}
.console-section + .console-section { margin-top: var(--space-8); }
.console-section__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
.console-section__title { font-size: var(--font-size-heading-sm); font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); margin: 0; }
.console-section__hint { font-size: var(--font-size-body-sm); color: var(--color-text-secondary); margin: var(--space-2) 0 0; max-width: 65ch; }

/* ---------- Grouped dual-list (-> GroupedDualListComponent) ----------
   Modernized included/excluded picker for large, categorized item sets
   (e.g. publishers). Keeps the familiar two-column move-between-lists
   mental model but replaces Shift/Cmd-click multi-select with: a search
   box per column (filters visible items + auto-expands matching groups),
   collapsible category headers, and per-item/per-category move buttons
   (click, no modifier keys) -- faster and more accessible at scale. */
.grouped-dual-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}
.grouped-dual-list__column { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
/* Middle column: a static filter icon between the two lists, offset down
   past the search boxes so it centers on the list boxes below them. */
.grouped-dual-list__divider { flex-direction: column; gap: var(--space-2); padding-top: 3rem; color: var(--color-text-secondary); }
.grouped-dual-list__title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: var(--font-size-body-sm);
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  margin: 0;
}
.grouped-dual-list__count { font-family: var(--font-family-base); color: var(--color-text-secondary); font-weight: normal; }
.grouped-dual-list__search { position: relative; }
.grouped-dual-list__search .icon { position: absolute; left: var(--space-3); top: 50%; transform: translateY(-50%); color: var(--color-text-secondary); width: 1rem; height: 1rem; }
.grouped-dual-list__search input {
  width: 100%;
  font-family: var(--font-family-base);
  font-size: var(--font-size-body-sm);
  padding: var(--space-3) var(--space-3) var(--space-3) 2rem;
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
}
/* Icon-affixed search field with no card wrapper of its own -- same shape as
   .grouped-dual-list__search above, generalized for anywhere a filter input
   sits directly inside an existing .panel (nesting .search-bar's own
   bordered/shadowed surface in there would be a card inside a card). */
.inline-search { position: relative; }
.inline-search .icon { position: absolute; left: var(--space-3); top: 50%; transform: translateY(-50%); color: var(--color-text-secondary); width: 1rem; height: 1rem; }
.inline-search input {
  width: 100%;
  font-family: var(--font-family-base);
  font-size: var(--font-size-body-sm);
  padding: var(--space-3) var(--space-3) var(--space-3) 2rem;
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
}
.grouped-dual-list__box {
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  max-height: 320px;
  overflow-y: auto;
}
.grouped-dual-list__group + .grouped-dual-list__group { border-top: var(--border-width) solid var(--color-border); }
.grouped-dual-list__group-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-surface-sunken);
  font-size: var(--font-size-caption);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-wide);
  color: var(--color-text-secondary);
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  position: sticky;
  top: 0;
}
.grouped-dual-list__group-move {
  background: none; border: none; cursor: pointer; padding: var(--space-1) var(--space-2);
  font-size: var(--font-size-caption); color: var(--color-primary-vivid); font-family: var(--font-family-medium); font-weight: var(--font-weight-medium);
  text-transform: none; letter-spacing: normal; border-radius: var(--radius-sm);
}
.grouped-dual-list__group-move:hover { background: var(--color-primary-surface); }
.grouped-dual-list__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-size-body-sm);
}
.grouped-dual-list__item:hover { background: var(--color-bg-surface-sunken); }
.grouped-dual-list__item-move {
  background: none; border: none; cursor: pointer; color: var(--color-text-secondary);
  display: flex; padding: var(--space-1); border-radius: var(--radius-sm); flex-shrink: 0;
}
.grouped-dual-list__item-move:hover { background: var(--color-primary-surface); color: var(--color-primary-vivid); }
.grouped-dual-list__item-move .icon { width: 1rem; height: 1rem; }
.grouped-dual-list__hint { font-size: var(--font-size-caption); color: var(--color-text-secondary); margin: var(--space-4) 0 0; }
.grouped-dual-list__hint--tight { margin-top: var(--space-3); }

@media (max-width: 780px) {
  .grouped-dual-list { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Flow/console sidebar shell (-> FlowSidebarComponent) ----------
   Shared right-hand sidebar shape used by BOTH wizard flows (sale edit) and
   settings consoles (product/publisher edit): a sticky column holding a
   completeness checklist panel + a contextual tips panel below it. Moved
   here (not duplicated per flow.css) once 3+ flows needed the identical
   pattern -- promoting a cross-cutting shared component, not a violation
   of the "per-flow-dir CSS" convention used for page-shell-specific rules. */
.flow-side { position: sticky; top: var(--space-6); display: flex; flex-direction: column; gap: var(--space-5); }
.flow-summary__title { font-size: var(--font-size-body); text-transform: uppercase; letter-spacing: var(--letter-spacing-wide); color: var(--color-text-secondary); margin: 0 0 var(--space-5); }
.flow-summary__note { font-size: var(--font-size-body-sm); color: var(--color-text-secondary); margin-top: var(--space-6); }

/* Completeness checklist -- NOT a second stepper/nav. It shows DATA STATE
   (which fields are filled vs. missing per step/section) regardless of
   visit order, so a user working out of order sees gaps anywhere, not just
   nearby. Each entry is a <details>, collapsed by default to a single line
   (link + filled/missing badge); expand to see specific fields. In a
   wizard (sale edit) entries map to steps and support .is-current/
   .is-skipped; in a settings console (product/publisher edit) entries map
   to sections and typically only use .is-complete/.is-missing since
   console sections aren't sequential or skippable. */
.wizard-checklist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.wizard-checklist__step { border-radius: var(--radius-sm); }
.wizard-checklist__step > summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-body-sm);
  cursor: pointer;
  list-style: none;
}
.wizard-checklist__step > summary::-webkit-details-marker { display: none; }
.wizard-checklist__step > summary:hover { background: var(--color-bg-surface-sunken); }
.wizard-checklist__step[open] > summary { font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); }
.wizard-checklist__link {
  flex: 1;
  min-width: 0;
  color: var(--color-text-primary);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wizard-checklist__link:hover { text-decoration: underline; }
.wizard-checklist__step.is-current > summary .wizard-checklist__link { color: var(--color-primary-vivid); }
.wizard-checklist__step.is-skipped > summary .wizard-checklist__link { color: var(--color-text-disabled); }

.wizard-checklist__badge {
  flex-shrink: 0;
  font-size: var(--font-size-caption);
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
}
.wizard-checklist__badge.is-complete { background: var(--color-success-surface); color: var(--color-success); }
.wizard-checklist__badge.is-missing { background: var(--color-warning-surface); color: var(--color-warning); }
.wizard-checklist__badge.is-skipped { background: var(--color-bg-surface-sunken); color: var(--color-text-secondary); }

.wizard-checklist__fields { list-style: none; margin: var(--space-2) 0 var(--space-3); padding: 0 0 0 var(--space-6); display: flex; flex-direction: column; gap: var(--space-2); }
.wizard-checklist__field { display: flex; align-items: center; gap: var(--space-2); font-size: var(--font-size-caption); }
.wizard-checklist__field .icon { width: 0.9rem; height: 0.9rem; flex-shrink: 0; }
.wizard-checklist__field.is-filled { color: var(--color-text-secondary); }
.wizard-checklist__field.is-filled .icon { color: var(--color-success-vivid); }
.wizard-checklist__field.is-missing { color: var(--color-text-primary); }
.wizard-checklist__field.is-missing .icon { color: var(--color-warning); }

/* Contextual tips panel -- second sidebar block below the checklist,
   changes copy per step/section. */
.tips-panel { background: var(--color-info-surface); border-color: var(--color-info); }
.tips-panel__title { display: flex; align-items: center; gap: var(--space-2); font-size: var(--font-size-body); text-transform: uppercase; letter-spacing: var(--letter-spacing-wide); color: var(--color-info); margin: 0 0 var(--space-4); }
.tips-panel__title .icon { width: 1em; height: 1em; }
.tips-panel p { font-size: var(--font-size-body-sm); color: var(--color-text-primary); margin: 0 0 var(--space-3); }
.tips-panel p:last-child { margin-bottom: 0; }
.tips-panel ul { font-size: var(--font-size-body-sm); color: var(--color-text-primary); margin: 0 0 var(--space-3); padding-left: var(--space-6); }
.tips-panel ul:last-child { margin-bottom: 0; }
.tips-panel li + li { margin-top: var(--space-2); }

/* ---------- Form field (-> FieldComponent) ----------
   Promoted from per-flow flow.css (was duplicated 3x and drifted -- one copy
   had the textarea fix, the others didn't, and a later sync overwrote the
   fix). Single source of truth now. Covers labeled fields, the field grid
   layout, and shared input/select/textarea chrome -- textarea explicitly
   included so it matches text inputs (border, radius, padding, font)
   instead of falling back to browser UA styles. */
.field-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-6);
}
/* Spaced: a .field-grid/.field following an unrelated preceding element
   (e.g. a .chip toggle) rather than a sibling in the same gap-based
   container -- needs its own top margin since nothing else provides it. */
.field-grid--spaced { margin-top: var(--space-4); }
.field { display: flex; flex-direction: column; gap: var(--space-2); }
.field--wide { grid-column: 1 / -1; }
/* A .field-grid cell with no field of its own (e.g. a lone chip toggle
   standing in for a field): end-aligned so its content sits level with the
   other cells' inputs rather than their labels. Use a <div>, not <label> --
   it doesn't itself label a control. */
.field--end { justify-content: flex-end; }
/* Read-only field value (e.g. a pill standing in for what would otherwise
   be an <input>): matches the input's own vertical padding so it lines up
   with sibling fields' inputs in the same .field-grid row. */
.field__readonly-value { padding: var(--space-4) 0; font-size: var(--font-size-body-sm); }
.field--spaced { margin-top: var(--space-4); }
.field--spaced-lg { margin-top: var(--space-5); }
/* fieldset.field: a field that groups multiple independent inputs (radios,
   checkboxes) instead of wrapping a single one. Resets the browser's default
   fieldset border/padding/margin so it lays out identically to <label>.field. */
fieldset.field { border: 0; padding: 0; margin: 0; }
.field__label { font-size: var(--font-size-body-sm); font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); }
legend.field__label { display: block; padding: 0; }
.field__label--spaced { display: block; margin-bottom: var(--space-3); }
/* Not --color-danger: required-field is a neutral marker, not a risk signal,
   and reusing danger here would dilute it as the reserved "this is genuinely
   risky" color elsewhere in the system. */
.field__required { color: var(--color-primary); }
.field__hint { font-size: var(--font-size-caption); color: var(--color-text-secondary); }
/* Standalone: a .field__hint used on its own after an action row, not as a
   sibling inside .field -- needs its own top spacing since .field's gap
   doesn't apply here. */
.field__hint--standalone { margin-top: var(--space-4); }
/* A button/action that follows a .field__hint, e.g. "Edit classification..."
   after a hint explaining why the field itself isn't directly editable. */
.field__hint-action { margin-top: var(--space-3); }
/* A .field__hint placed right after the label instead of after the input --
   pulled up to sit close to the label it clarifies, on its own line since
   .field__hint is inline by default. */
.field__hint--under-label { display: block; margin: -0.25rem 0 var(--space-2); }
.field__prefix { color: var(--color-text-secondary); font-size: var(--font-size-body-sm); display: flex; align-items: center; flex-shrink: 0; }
/* A .field__prefix paired with the input it prefixes (e.g. "/sales/" + the
   editable slug): stretches both to equal height, doesn't wrap on narrow
   viewports since the prefix has nowhere useful to wrap to. */
.field__prefix-group { align-items: stretch; flex-wrap: nowrap; }
.field__prefix-group input { flex: 1; min-width: 0; }
/* Base input chrome (-> shared with FieldComponent's inputs). Standalone
   class so a bare table-cell input (refund amount, price-drop row, revenue
   share %) can opt in without needing the .field label/hint wrapper --
   previously these duplicated the same declarations via inline style
   per-instance, or were left with zero styling, unstyled browser chrome. */
.input {
  font-family: var(--font-family-base);
  font-size: var(--font-size-body);
  padding: var(--space-4);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  box-sizing: border-box;
}
.input:focus {
  border-color: var(--color-primary-vivid);
  box-shadow: 0 0 0 3px var(--color-primary-surface);
  outline: none;
}
/* Table-cell inputs are compact and intrinsically sized, not full-width
   full-padding form fields. */
.input--sm { padding: var(--space-2) var(--space-3); font-size: var(--font-size-body-sm); width: auto; }
.input--numeric { text-align: right; }
/* A compact currency-amount input in a table cell (refund row, price-drop
   row): fits e.g. "499.00" without stretching to the cell's full width. */
.input--amount { width: 5rem; }

.field input[type="text"],
.field input[type="number"],
.field input[type="date"],
.field input[type="search"],
.field select,
.field textarea {
  font-family: var(--font-family-base);
  font-size: var(--font-size-body);
  padding: var(--space-4);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  width: 100%;
  box-sizing: border-box;
}
.field textarea { resize: vertical; font-family: inherit; line-height: var(--line-height-normal); }
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--color-primary-vivid);
  box-shadow: 0 0 0 3px var(--color-primary-surface);
  outline: none;
}

/* Field error state (-> FieldComponent, state: error). Applied to the .field
   wrapper (class="field is-error") so both the input border and the message
   swap together -- mirrors the is-current/is-skipped convention used on
   .wizard-checklist__step above. .field__error replaces .field__hint for
   the duration of the error (never shown at the same time as the hint). */
.field.is-error input, .field.is-error select, .field.is-error textarea, .input.is-error {
  border-color: var(--color-danger);
}
.field.is-error input:focus, .field.is-error select:focus, .field.is-error textarea:focus, .input.is-error:focus {
  border-color: var(--color-danger);
  box-shadow: 0 0 0 3px var(--color-danger-surface);
}
.field__error {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-caption);
  color: var(--color-danger);
}
.field__error .icon { width: 1rem; height: 1rem; flex-shrink: 0; }

.radio { display: flex; align-items: center; gap: var(--space-2); font-size: var(--font-size-body-sm); }
.checkbox { display: flex; align-items: center; gap: var(--space-2); font-size: var(--font-size-body-sm); }
.checkbox.is-disabled { color: var(--color-text-disabled); }
/* Contextual accent (table row-selection, chip toggles) -- not the global
   default, since a bare checkbox in a plain form is fine at browser default. */
.checkbox--accent input[type="checkbox"] { accent-color: var(--color-primary-vivid); }

.summary-list { margin: 0; }
.summary-list > div { display: flex; justify-content: space-between; gap: var(--space-4); padding: var(--space-3) 0; border-bottom: var(--border-width) solid var(--color-border); font-size: var(--font-size-body-sm); }
.summary-list > div:last-child { border-bottom: none; }
.summary-list dt { color: var(--color-text-secondary); margin: 0; }
.summary-list dd { margin: 0; font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); text-align: right; }
.summary-list dd.is-danger { color: var(--color-danger); }
/* The grand-total row is the pre-commit money figure the user is about to
   move -- the exact "key figure" case type.css reserves Fragment Mono for,
   matching the treatment typography.html itself demonstrates. Marked with
   .is-total explicitly (not :last-child): a list's last row is whatever
   field happens to sort last, not necessarily a total -- most .summary-list
   uses (Details, Schedule, Media & content, etc.) never have one at all. */
.summary-list > div.is-total dd { font-family: var(--font-family-mono); font-size: var(--font-size-body-lg); font-variant-numeric: tabular-nums; }

.tier-table { width: 100%; border-collapse: collapse; margin-top: var(--space-4); }
.tier-table th, .tier-table td { text-align: left; padding: var(--space-3) var(--space-4); border-bottom: var(--border-width) solid var(--color-border); font-size: var(--font-size-body-sm); }
.tier-table td.is-numeric, .tier-table th.is-numeric { text-align: right; font-variant-numeric: tabular-nums; }
/* "+ Add row" action directly under a .tier-table's .table-scroll wrapper. */
.tier-table__add-btn { margin-top: var(--space-4); }

@media (max-width: 860px) {
  .flow-layout { grid-template-columns: 1fr; }
}

/* ---------- Rich text editor (-> RichTextComponent) ----------
   Static mockup of a WYSIWYG toolbar (matches the TinyMCE-style editor
   already used in production for marketing/redemption copy fields --
   Key Features, Redemption Instructions, Terms & Conditions, Meta
   Description, etc.) so those fields read as rich text inputs, not plain
   textareas. Toolbar buttons are non-functional in this static mockup. */
.rich-text { border: var(--border-width) solid var(--color-border-strong); border-radius: var(--radius-sm); overflow: hidden; background: var(--color-bg-surface); }
.rich-text:focus-within { border-color: var(--color-primary-vivid); box-shadow: 0 0 0 3px var(--color-primary-surface); }
.rich-text__toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg-surface-sunken);
  border-bottom: var(--border-width) solid var(--color-border);
}
.rich-text__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  font-size: var(--font-size-body-sm);
  cursor: pointer;
}
.rich-text__btn:hover { background: var(--color-bg-surface); color: var(--color-text-primary); }
.rich-text__btn .icon { width: 1rem; height: 1rem; }
.rich-text__sep { width: var(--border-width); align-self: stretch; background: var(--color-border); margin: 0 var(--space-2); }
.rich-text__body {
  padding: var(--space-4);
  font-family: var(--font-family-base);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  line-height: var(--line-height-normal);
  min-height: 12rem;
}
/* Compact variant for short single-line-ish fields (meta description) --
   opt in explicitly rather than have every field default to short. */
.rich-text--compact .rich-text__body { min-height: 4.5rem; }
.rich-text__body[contenteditable]:focus { outline: none; }
.rich-text__body p { margin: 0 0 var(--space-4); }
.rich-text__body p:last-child { margin-bottom: 0; }
.rich-text__footer {
  display: flex;
  justify-content: space-between;
  padding: var(--space-2) var(--space-3);
  border-top: var(--border-width) solid var(--color-border);
  font-size: var(--font-size-caption);
  color: var(--color-text-secondary);
}

/* ---------- Listing page toolbar (title + primary actions) ----------
   Promoted from 5 duplicated per-flow.css copies (order-refund, payouts,
   product-edit, publisher-edit, sale-creation) that had started to drift. */
.listing-toolbar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: var(--space-4); margin-bottom: var(--space-6); }

/* ---------- Layout helpers used across component demos & flows ---------- */
.page { max-width: 1120px; margin: 0 auto; padding: var(--space-8) var(--space-6); }
.page--wide { max-width: 1360px; }

/* Ultrawide: 1120px/1360px read comfortably on normal desktops, but on a
   2K+/ultrawide monitor they leave roughly half the screen as dead margin.
   Step the cap up at content-driven breakpoints rather than centering
   forever -- data tables, dashboard charts, and builder+preview splits all
   have real use for the extra width; body text elsewhere still wraps at
   ~70ch via its own max-width, so wider .page doesn't hurt readability. */
@media (min-width: 1600px) {
  .page { max-width: 1440px; }
  .page--wide { max-width: 1680px; }
}
@media (min-width: 2000px) {
  .page { max-width: 1720px; }
  .page--wide { max-width: 2000px; }
}
.panel {
  background: var(--color-bg-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-sm);
}
.panel p { margin: 0; font-size: var(--font-size-body-sm); color: var(--color-text-primary); }
.panel__note { color: var(--color-text-secondary); }
.panel__note--spaced { margin-bottom: var(--space-4); }
.panel__note--caption { font-size: var(--font-size-caption); margin-top: var(--space-4); }
.panel--spaced { margin-top: var(--space-6); }
.panel--spaced-sm { margin-top: var(--space-2); }

/* Inline alert/callout panel variants (-> PanelComponent, variant: warning|danger|success|info).
   Promoted from 3 duplicated inline-style instances (payouts/create-payout.html,
   order-refund/order-detail.html, sale-creation/products-pricing.html) that had
   started to drift -- one had padding overridden, the others didn't. Single
   source of truth now, same convention as .listing-toolbar/.tips-panel above.
   Also the visual base for Flash (see components/flash.html): a Flash is a
   .panel--success/--danger/--warning/--info used specifically as a page-top
   action acknowledgment, always paired with .cluster and a dismiss button
   rather than freestanding body copy. */
.panel--warning { background: var(--color-warning-surface); border-color: var(--color-warning); }
.panel--warning p { color: var(--color-text-primary); }
/* Compact: an inline warning callout inside a flow step (e.g. "Waive fee"
   toggle + conditional reason field), not a full-width standalone panel. */
.panel--compact { padding: var(--space-4); }
.panel--danger { background: var(--color-danger-surface); border-color: var(--color-danger); }
.panel--danger h2, .panel--danger h3 { color: var(--color-danger); }
.panel--success { background: var(--color-success-surface); border-color: var(--color-success); }
.panel--success h2, .panel--success h3 { color: var(--color-success); }
.panel--info { background: var(--color-info-surface); border-color: var(--color-info); }
.panel--info h2, .panel--info h3 { color: var(--color-info); }
.panel--sunken { background: var(--color-bg-surface-sunken); }

/* ---------- Flash (-> FlashComponent, variant: success|danger|warning|info) ----------
   A page-top action acknowledgment: "did my last action work," landing after
   a confirm-dialog redirect (see flows/sale-creation/sales-listing.html,
   flows/order-refund/index.html, flows/payouts/index.html). One .panel--*
   variant + .flash layout + a dismiss button; see components/flash.html for
   all 4 variants and usage guidance. */
.flash {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-6);
  animation: flash-settle var(--duration-slow) var(--ease-decelerate);
}
.flash__message { display: flex; align-items: center; gap: var(--space-3); font-size: var(--font-size-body-sm); }
.flash__icon { flex-shrink: 0; }
/* Icon color per variant, since it can't come from currentColor the way
   .badge's dot does (icon sits beside body text, not inside a colored label). */
.flash__icon.is-success { color: var(--color-success-vivid); }
.flash__icon.is-danger { color: var(--color-danger-vivid); }
.flash__icon.is-warning { color: var(--color-warning-vivid); }
.flash__icon.is-info { color: var(--color-info-vivid); }

/* Quiet arrival: a brief settle-in, not a page-load flourish -- signals
   "something just happened" without drawing attention to itself. Fires
   once on mount only (no hover/repeat trigger), matching a one-shot
   real-world acknowledgment rather than a decorative loop. */
@keyframes flash-settle {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ---------- Tooltip (-> TooltipComponent) ----------
   Rich hover/focus detail panel, e.g. a payment breakdown on the Orders
   table. NOT a `title` attribute: title can't hold structured multi-line
   content and isn't reliably reachable by keyboard. Trigger is a real
   <button> (focusable, no separate tabindex hack); the panel is always in
   the DOM with role="tooltip" + aria-hidden toggling, linked via
   aria-describedby so assistive tech gets the content on both hover AND
   keyboard focus, not just mouse hover. Shown via :hover/:focus-within on
   the wrapper so no JS is required, consistent with this system's
   no-JS-where-possible approach (.filter-popover, .accordion, etc.).
   Positioned with position:absolute (not fixed): .table-scroll only clips
   overflow-x, so a tooltip anchored above/below its trigger is never cut
   off vertically; the panel is right-aligned to its trigger to avoid the
   same horizontal clipping filter-popover__panel already accounts for. */
.tooltip-trigger { position: relative; display: inline-flex; }
.tooltip-trigger__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  cursor: default;
}
.tooltip-trigger__button:hover,
.tooltip-trigger__button:focus-visible { color: var(--color-primary-vivid); }
.tooltip-trigger__button .icon { width: 1.1rem; height: 1.1rem; }
.tooltip-panel {
  position: absolute;
  bottom: calc(100% + var(--space-3));
  right: 0;
  z-index: 10;
  width: 15rem;
  background: var(--color-gray-900);
  color: var(--color-gray-0);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--space-4);
  font-size: var(--font-size-caption);
  line-height: var(--line-height-normal);
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard);
}
.tooltip-trigger:hover .tooltip-panel,
.tooltip-trigger:focus-within .tooltip-panel {
  opacity: 1;
  transform: translateY(0);
}

.tooltip-panel__row { display: flex; justify-content: space-between; gap: var(--space-4); padding: var(--space-1) 0; }
.tooltip-panel__row dt { color: var(--color-gray-400); margin: 0; }
.tooltip-panel__row dd { margin: 0; font-variant-numeric: tabular-nums; }
.tooltip-panel__row.is-total { border-top: var(--border-width) solid var(--color-gray-700); margin-top: var(--space-2); padding-top: var(--space-2); font-family: var(--font-family-bold); font-weight: var(--font-weight-bold); }
.tooltip-panel dl { margin: 0; }

/* Small identifying badge inside a table cell (coupon/credit usage on the
   Payment column) -- a compact, icon-only sibling to .badge/.pill for
   binary "this order has X" signals where a full text badge would be too
   wide to sit two-up in a dense cell. */
.payment-flag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.375rem;
  height: 1.375rem;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}
.payment-flag .icon { width: 0.9rem; height: 0.9rem; }
.payment-flag.is-coupon { background: var(--color-primary-surface); color: var(--color-primary-hover); }
.payment-flag.is-credit { background: var(--color-success-surface); color: var(--color-success); }
@media (prefers-reduced-motion: reduce) {
  .flash { animation: none; }
}
.stack > * + * { margin-top: var(--space-6); }
.cluster { display: flex; gap: var(--space-4); flex-wrap: wrap; align-items: center; }
/* Tight variant: a label + one small companion element (a badge, an icon
   link) that reads as one unit, not a wrapping group of peers -- the
   default --space-4 gap and wrap behavior are tuned for toolbars/filter
   rows, not a name-plus-badge or link-plus-icon pairing in a table cell. */
.cluster--tight { gap: var(--space-2); }
.cluster--tight.cluster--nowrap { flex-wrap: nowrap; }
.cluster--nowrap { flex-wrap: nowrap; }
.cluster--between { justify-content: space-between; }
/* A status/summary text paired with an action button in a .cluster--between
   (e.g. "2 products - sale total $68.99" beside "Calculate paid revshare"). */
.cluster__status-text { font-size: var(--font-size-body-sm); color: var(--color-text-secondary); }
.cluster--spaced-lg { margin-top: var(--space-5); }
/* A .cluster of Chips directly below the search bar, separated from the
   listing panel that follows (see components/search-bar.html's layout rule). */
.cluster--chips-row { margin-bottom: var(--space-6); }
/* Connector word ("to", "and") between two inputs in a date-range .cluster. */
.date-range-sep { color: var(--color-text-secondary); font-size: var(--font-size-caption); }

/* ---------- Builder + live preview split (-> BuilderPreviewComponent) ----------
   Shared layout for "simple WYSIWYG editor with a live mockup preview, no
   real data" surfaces (Navigation tree editor, Homepage Modules editor).
   Left column is the plain-language builder (add/remove/reorder rows);
   right column is a scaled, non-interactive storefront mockup that updates
   to reflect the builder state -- illustrative structure only, never real
   inventory/content. */
.builder-preview {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}
.builder-preview__col-label {
  font-size: var(--font-size-caption);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-wide);
  color: var(--color-text-secondary);
  margin: 0 0 var(--space-4);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.builder-preview__preview-frame {
  position: sticky;
  top: var(--space-6);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-bg-surface);
  box-shadow: var(--shadow-sm);
}
.builder-preview__preview-chrome {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-surface-sunken);
  border-bottom: var(--border-width) solid var(--color-border);
}
.builder-preview__preview-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--color-border-strong); }
.builder-preview__preview-url { font-size: var(--font-size-caption); color: var(--color-text-secondary); margin-left: var(--space-2); font-family: monospace; }
.builder-preview__preview-body { padding: var(--space-6); background: var(--color-bg-page); }
.builder-preview__note { font-size: var(--font-size-caption); color: var(--color-text-secondary); margin: var(--space-3) 0 0; }

@media (max-width: 900px) {
  .builder-preview { grid-template-columns: minmax(0, 1fr); }
  /* The live preview is secondary on this content-heavy page -- below the
     shell's mobile breakpoint it's dropped entirely rather than adding a
     second scroll/toggle affordance on top of the builder. */
  .builder-preview__preview-col { display: none; }
}

/* Repeatable-row remove button (add/remove list rows: nav items, modules, video URLs, ...) */
.row-remove-btn { color: var(--color-text-secondary); background: none; border: none; cursor: pointer; padding: var(--space-2); border-radius: var(--radius-sm); flex-shrink: 0; }
.row-remove-btn:hover { background: var(--color-danger-surface); color: var(--color-danger); }

/* Text input used inline within a repeatable row (outside a .field wrapper) — same visual
   treatment as .field input[type="text"], for rows that need a horizontal (not column) layout. */
.row-input {
  font-family: var(--font-family-base);
  font-size: var(--font-size-body);
  padding: var(--space-4);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  box-sizing: border-box;
}
.row-input:focus {
  border-color: var(--color-primary-vivid);
  outline: none;
  box-shadow: 0 0 0 3px var(--color-primary-surface);
}

/* ==========================================================================
   Responsive layer
   Content-driven breakpoints (not device-specific): 1100px already governs
   .console-layout (sidebar collapse), 900px is the shell's sidebar-to-drawer
   breakpoint (see shell.css), 640px is where dense multi-column controls
   stop fitting a phone-width viewport. Per the design principles, mobile
   adapts these surfaces (tabs scroll, filters stack, tables reflow to cards)
   rather than hiding functionality outright -- the one deliberate exception
   is the Homepage builder+preview split, documented at .builder-preview
   above, where the preview is dropped entirely (display: none) below the
   mobile breakpoint because it's secondary to the builder on a
   content-heavy page and hiding it is what avoids a second scroll/toggle
   affordance stacked on top of the builder.
   ========================================================================== */

@media (max-width: 900px) {
  .page { padding: var(--space-6) var(--space-4); }

  /* Tab bar: horizontal scroll instead of wrapping into a tall multi-row
     block that pushes page content down. max-width (not just overflow-x)
     is what actually contains the scroll to this element instead of
     stretching the page's own width to fit every tab. */
  .tab-bar { flex-wrap: nowrap; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 1px; }
  .tab-bar__tab { white-space: nowrap; }

  .state-tabs { flex-wrap: nowrap; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .state-tab { white-space: nowrap; flex-shrink: 0; }

  .search-bar { flex-wrap: wrap; }
  .search-bar__input-wrap { flex-basis: 100%; }

  .filter-popover__panel { width: min(22rem, calc(100vw - var(--space-8))); right: auto; left: 0; }

  .field-grid { grid-template-columns: minmax(0, 1fr); }
  .grouped-dual-list { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 1100px) {
  /* .flow-layout and .console-layout both drop to one column at their
     breakpoints; once stacked below the main content, sticky positioning
     no longer makes sense for the side panel. */
  .flow-side { position: static; }
}

/* Horizontal-scroll fallback for smaller embedded tables (.tier-table,
   .ledger-table) that live inside a detail/edit panel rather than as a
   page's primary content -- scrolling their own container is a better fit
   than a full label/value card reflow for these secondary, denser tables.

   Do NOT wrap a .data-table-panel's own .data-table in this: it already
   has a real responsive strategy (reflow to stacked label/value cards at
   the 640px breakpoint below), so it never needs horizontal scroll, and
   overflow-x: auto here forces overflow-y: auto too per the CSS spec (an
   element can't be scrollable on one axis and visible on the other) --
   that silently reintroduces the exact vertical-clipping bug .data-table-
   panel's own overflow:hidden removal above was fixing, for anything that
   needs to escape upward/downward from inside a table cell (e.g.
   .tooltip-panel on the Payment column). 7 listing pages had drifted into
   this wrong usage; all were corrected to render <table class="data-table">
   directly, with no .table-scroll wrapper. */
.table-scroll { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }

@media (max-width: 640px) {
  /* Data tables: reflow rows into stacked label/value cards. Requires each
     td to carry a data-label attribute (added per listing page) so the
     ::before pseudo-element can render the column header inline. */
  .data-table, .data-table caption, .data-table thead, .data-table tbody, .data-table th, .data-table td, .data-table tr {
    display: block;
  }
  .data-table caption { padding: var(--space-4) var(--space-5); font-size: var(--font-size-body); white-space: nowrap; }
  .data-table thead { position: absolute; left: -9999px; }
  .data-table tbody tr {
    border-bottom: var(--border-width) solid var(--color-border);
    padding: var(--space-4) var(--space-5);
  }
  .data-table tbody tr:last-child { border-bottom: none; }
  .data-table td {
    border-bottom: none;
    padding: var(--space-2) 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-4);
    text-align: right;
  }
  .data-table td[data-label]::before {
    content: attr(data-label);
    font-size: var(--font-size-caption);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-wide);
    color: var(--color-text-secondary);
    font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
    text-align: left;
    flex-shrink: 0;
  }
  .data-table td:first-child { text-align: left; }
  .data-table td:first-child::before { display: none; }
  .data-table .row-identity { flex: 1; min-width: 0; }
}

/* ---------- Sale calendar (-> SaleCalendarComponent) ----------
   Multi-month view of a sale's active window and its price drops, read-only
   summary of the Start date / End date / Price drops fields above it on the
   Schedule tab -- not a separate data-entry surface. The sale window and
   each price drop are both date ranges (PriceDropSchedule has its own
   start_at/end_at, distinct from the sale's), so both render as continuous
   bars spanning day cells -- the sale window as the wider bar, each price
   drop as a thinner, distinctly colored bar nested underneath it, with its
   price labeled once on its first day. */
.sale-calendar {
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.sale-calendar__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: var(--border-width) solid var(--color-border);
  background: var(--color-bg-surface-sunken);
}
.sale-calendar__range {
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  font-size: var(--font-size-body);
}
.sale-calendar__nav {
  display: flex;
  gap: var(--space-2);
}
.sale-calendar__nav-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  cursor: not-allowed; /* static mockup: this month pair is fixed */
}
.sale-calendar__nav-btn .icon { width: 0.875rem; height: 0.875rem; }
.sale-calendar__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  padding: var(--space-3) var(--space-5);
  border-bottom: var(--border-width) solid var(--color-border);
  font-size: var(--font-size-caption);
  color: var(--color-text-secondary);
}
.sale-calendar__legend-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.sale-calendar__legend-swatch {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}
.sale-calendar__legend-swatch--range { background: var(--color-primary-vivid); }

/* Price-drop color cycle: each drop gets a distinct hue, reused for its
   calendar range bar, its legend swatch, and its row marker in the price
   drop table below -- one indicator, three places, so a color always means
   the same drop. Primary (blue) is reserved for the sale window itself, so
   drops cycle through the other four accent hues. */
.price-drop-swatch {
  display: inline-block;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}
.price-drop-swatch--1 { background: var(--color-warning-vivid); }
.price-drop-swatch--2 { background: var(--color-info-vivid); }
.price-drop-swatch--3 { background: var(--color-success-vivid); }
.price-drop-swatch--4 { background: var(--color-danger-vivid); }

/* Same cycle, exposed as custom properties so a calendar day can carry one
   drop's full color set (dot/bar + text-safe surface + text) via a single
   modifier class matching its .price-drop-swatch--N number. */
.sale-calendar__day.drop-1 { --drop-color: var(--color-warning-vivid); --drop-color-surface: var(--color-warning-surface); --drop-color-text: var(--color-warning); }
.sale-calendar__day.drop-2 { --drop-color: var(--color-info-vivid); --drop-color-surface: var(--color-info-surface); --drop-color-text: var(--color-info); }
.sale-calendar__day.drop-3 { --drop-color: var(--color-success-vivid); --drop-color-surface: var(--color-success-surface); --drop-color-text: var(--color-success); }
.sale-calendar__day.drop-4 { --drop-color: var(--color-danger-vivid); --drop-color-surface: var(--color-danger-surface); --drop-color-text: var(--color-danger); }

.sale-calendar__months {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-4);
  padding: var(--space-4);
}
.sale-calendar__month + .sale-calendar__month {
  border-left: var(--border-width) solid var(--color-border);
  padding-left: var(--space-4);
}
.sale-calendar__month-title {
  margin: 0;
  font-size: var(--font-size-body);
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
}
/* A real <table>, not a CSS grid of divs: table layout guarantees every
   <td> in a row shares that row's height, so the range/drop bars (each
   positioned with a fixed offset from its own cell's top) stay aligned
   across a week even when one day's cell has extra content (an event
   label) that would otherwise stretch a grid row unevenly. */
.sale-calendar__grid {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  margin-top: var(--space-3);
}
.sale-calendar__grid th {
  padding: var(--space-3) 0 var(--space-2);
  font-size: var(--font-size-caption);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary);
  text-align: center;
}
.sale-calendar__day {
  position: relative;
  height: 4.5rem;
  padding: var(--space-2) var(--space-1) 0;
  font-size: var(--font-size-body-sm);
  color: var(--color-text-primary);
  text-align: center;
  vertical-align: top;
}
.sale-calendar__day-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-pill);
  position: relative;
  z-index: 2;
}
.sale-calendar__day.is-outside-month { color: var(--color-text-disabled); }
.sale-calendar__day.is-range-start .sale-calendar__day-num,
.sale-calendar__day.is-range-end .sale-calendar__day-num {
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
}
/* Continuous range bar across the week: a pseudo-element strip behind each
   in-range day, flush with both cell edges by default so it reads as one
   unbroken bar across a full row -- including at a week wrap, where the
   range continues onto the next row and the edge must stay square, not
   rounded. Nearly full cell height (10%-90%) with no border on continuing
   days; a colored border appears only on the true start/end day's own
   edge, rounded there. */
.sale-calendar__day.is-in-range::before {
  content: '';
  position: absolute;
  top: 10%;
  height: 80%;
  left: 0;
  right: 0;
  background: var(--color-primary-surface);
  z-index: 0;
}
.sale-calendar__day.is-range-start::before { border-radius: 16px 0 0 16px; border-left: 3px solid var(--color-primary-vivid); }
.sale-calendar__day.is-range-end::before { border-radius: 0 16px 16px 0; border-right: 3px solid var(--color-primary-vivid); }
.sale-calendar__day.is-range-start.is-range-end::before { border-radius: 16px; border-left: 3px solid var(--color-primary-vivid); border-right: 3px solid var(--color-primary-vivid); }

/* Price-drop range bar: a second, shorter strip vertically centered over
   the sale-window bar, on top of it in z-order, tall enough to hold its
   price label directly inside it on the drop's start day. Color comes from
   the --drop-color custom property, set per drop-N modifier class above
   (so a day carries one drop's full color set via a single class, matching
   its .price-drop-swatch--N). Top/bottom border on every day the drop
   covers; left/right border (and rounding) added only at the drop's own
   start/end day. */
.sale-calendar__day.has-drop::after {
  content: '';
  position: absolute;
  top: 50%;
  height: 1.25rem;
  left: 0;
  right: 0;
  background: var(--drop-color-surface, var(--color-warning-surface));
  border-top: var(--border-width) solid var(--drop-color, var(--color-warning-vivid));
  border-bottom: var(--border-width) solid var(--drop-color, var(--color-warning-vivid));
  z-index: 1;
}
.sale-calendar__day.is-drop-start::after { left: 0.4rem; border-radius: var(--radius-pill) 0 0 var(--radius-pill); border-left: var(--border-width) solid var(--drop-color, var(--color-warning-vivid)); }
.sale-calendar__day.is-drop-end::after { right: 0.4rem; border-radius: 0 var(--radius-pill) var(--radius-pill) 0; border-right: var(--border-width) solid var(--drop-color, var(--color-warning-vivid)); }
.sale-calendar__day.is-drop-start.is-drop-end::after { left: 0.4rem; right: 0.4rem; border-radius: var(--radius-pill); border-left: var(--border-width) solid var(--drop-color, var(--color-warning-vivid)); border-right: var(--border-width) solid var(--drop-color, var(--color-warning-vivid)); }

/* No dot inside the label -- the bar's own border color (matching its
   .price-drop-swatch--N) already identifies which drop this is, so the
   label can spend its narrow width (one day column wide) entirely on the
   price. */
.sale-calendar__event {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 50%;
  height: 1.25rem;
  left: 0.25rem;
  right: 0;
  padding: 0 var(--space-1);
  color: var(--drop-color-text, var(--color-warning));
  font-size: var(--font-size-caption);
  font-family: var(--font-family-bold); font-weight: var(--font-weight-bold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  z-index: 2;
}

@media (max-width: 520px) {
  .sale-calendar__months { grid-template-columns: 1fr; }
  .sale-calendar__month + .sale-calendar__month {
    border-left: none;
    border-top: var(--border-width) solid var(--color-border);
  }
}
