/* ==========================================================================
   COMPONENTS: buttons, forms, cards, badges, tables, modals, toasts, empty states
   ========================================================================== */

/* ---- Buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--touch-target);
  padding: 0 var(--sp-5);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
  white-space: nowrap;
  line-height: 1.2;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--color-primary); color: var(--color-on-primary); }
.btn-primary:hover { background: var(--color-primary-dark); }
.btn-secondary { background: var(--color-surface); color: var(--color-text); border-color: var(--color-border-strong); }
.btn-secondary:hover { background: var(--color-surface-raised); }
.btn-danger { background: var(--color-danger-solid); color: var(--color-on-primary); }
.btn-danger:hover { background: var(--color-danger-hover); }
.btn-success { background: var(--color-success); color: var(--color-on-primary); }
.btn-ghost { background: transparent; color: var(--color-text-muted); }
.btn-ghost:hover { background: var(--color-surface-raised); color: var(--color-text); }
.btn-outline { background: transparent; color: var(--color-primary); border-color: var(--color-primary); }
.btn-outline:hover { background: var(--color-primary-50); }
.btn-block { width: 100%; }

/* "Small" means tighter padding and smaller text — NOT a smaller hit area.
   On a phone every button keeps the full 44px target; the compact 36px form
   is allowed only from 640px up, where a mouse is doing the pointing. */
.btn-sm { min-height: var(--touch-target); padding: 0 var(--sp-3); font-size: var(--fs-sm); }
@media (min-width: 640px) {
  .btn-sm { min-height: 36px; }
}
.btn-lg { min-height: var(--tap-comfortable); font-size: var(--fs-md); }
.btn-icon { min-width: var(--touch-target); padding: 0; }
.btn[disabled] { opacity: 0.55; cursor: not-allowed; }

.btn-group { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* ---- Icons (inline SVG sizing) ----
   Sized up from 20/16 so they stay legible next to the 16px body text. An
   icon that is smaller than the text beside it reads as a smudge on a phone. */
.icon { width: 22px; height: 22px; flex-shrink: 0; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.icon-sm { width: 18px; height: 18px; }
.icon-lg { width: 28px; height: 28px; }

/* ---- Cards ---- */
.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-base), border-color var(--transition-base);
}
@media (hover: hover) {
  .card:hover, .stat-card:hover, .list-card:hover { box-shadow: var(--shadow-md); border-color: var(--color-border-strong); }
}
.card-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  margin-bottom: var(--sp-4);
}
.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
  flex-wrap: wrap;
}

/* ---- Stat cards ---- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}
/* An odd card out spans the row rather than sitting beside a gap. */
.stat-grid > :last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (min-width: 640px) {
  .stat-grid { gap: var(--sp-4); margin-bottom: var(--sp-6); }
  .stat-grid > :last-child:nth-child(odd) { grid-column: auto; }
}
@media (min-width: 1024px) {
  .stat-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
  .stat-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
}
.stat-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  /* Deliberately tight on a phone. A dashboard's job is to get you to the
     content below it; six roomy cards that push "Recent Lists" off the
     bottom of a 390px screen are worse than six compact ones you can take
     in at a glance. Loosens from 640px up, where the space exists. */
  padding: var(--sp-3);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
  transition: box-shadow var(--transition-base), border-color var(--transition-base);
}
@media (min-width: 640px) {
  .stat-card { padding: var(--sp-4); }
}
.stat-icon {
  width: 38px; height: 38px;
  border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  background: var(--color-primary-50);
  color: var(--color-primary);
  flex-shrink: 0;
}
.stat-icon .icon { width: 20px; height: 20px; }
/* The text column must be allowed to shrink, or a long label forces the card
   wider than its grid track and the row overflows. */
.stat-card > div:not(.stat-icon) { min-width: 0; }
.stat-label { overflow-wrap: break-word; }
.stat-value { font-size: var(--fs-xl); font-weight: var(--fw-bold); line-height: var(--lh-tight); letter-spacing: var(--tracking-tight); font-variant-numeric: tabular-nums; }
.stat-label { font-size: var(--fs-sm); color: var(--color-text-muted); margin-top: 1px; }

/* ---- Hero stat card ---- */
/* One headline metric per dashboard — the number that represents the app's
   actual operational core (scans/lists) — rendered with real visual weight
   instead of repeating the same card six times. */
.stat-grid-wrap { margin-bottom: var(--sp-5); }
.stat-hero-row { margin-bottom: var(--sp-3); }
@media (min-width: 640px) {
  .stat-grid-wrap { margin-bottom: var(--sp-6); }
  .stat-hero-row { margin-bottom: var(--sp-4); }
}
.stat-card.hero {
  background: linear-gradient(135deg, var(--color-primary-50) 0%, var(--color-surface) 65%);
  border: 1px solid var(--color-primary-light);
  border-radius: var(--radius-lg);
  padding: var(--sp-4);
  gap: var(--sp-4);
}
@media (min-width: 640px) {
  .stat-card.hero { padding: var(--sp-6) var(--sp-5); }
}
.stat-card.hero .stat-icon {
  width: 46px; height: 46px;
  background: var(--color-primary-emphasis);
  color: var(--color-on-primary);
}
.stat-card.hero .stat-icon .icon { width: 24px; height: 24px; }
.stat-card.hero .stat-value { font-size: var(--fs-2xl); color: var(--color-primary-dark); }
.stat-card.hero .stat-label { font-size: var(--fs-base); color: var(--color-text); font-weight: var(--fw-medium); }
.stat-grid.cols-secondary { margin-bottom: 0; grid-template-columns: repeat(2, 1fr); gap: var(--sp-3); }
/* An odd number of stats would otherwise leave a lone card next to a gap.
   Letting the last one span the row reads as deliberate instead of broken —
   which matters here, because the Super Admin dashboard has five. */
.stat-grid.cols-secondary > :last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (min-width: 640px) {
  .stat-grid.cols-secondary { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-4); }
  .stat-grid.cols-secondary > :last-child:nth-child(odd) { grid-column: auto; }
}

/* ---- Badges ---- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px var(--sp-3);
  border-radius: var(--radius-full);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.6;
  white-space: nowrap;
}
.badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge-success { background: var(--color-success-bg); color: var(--color-success); }
.badge-danger { background: var(--color-danger-bg); color: var(--color-danger); }
.badge-warning { background: var(--color-warning-bg); color: var(--color-warning); }
.badge-info { background: var(--color-info-bg); color: var(--color-info); }
.badge-neutral { background: var(--color-surface-raised); color: var(--color-text-muted); }

/* ---- Forms ---- */
.form-group { margin-bottom: var(--sp-5); }
.form-label {
  display: block;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--color-text);
  margin-bottom: var(--sp-2);
}
.form-label .required { color: var(--color-danger); margin-left: 2px; }
.form-hint { font-size: var(--fs-xs); color: var(--color-text-muted); margin-top: var(--sp-1); }
.form-error {
  font-size: var(--fs-xs);
  color: var(--color-danger);
  margin-top: var(--sp-1);
  display: none;
  align-items: center;
  gap: 4px;
}
.form-error.show { display: flex; }

.form-control {
  width: 100%;
  /* Taller than the 44px floor: a field is a target you aim at with a thumb
     while holding the phone, and the extra height also gives the label and
     the value room to breathe. Matches .btn-lg so a form and its submit
     button line up. */
  min-height: var(--tap-comfortable);
  padding: 0 var(--sp-4);
  font-size: var(--fs-base);
  line-height: var(--lh-snug);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
textarea.form-control { padding: var(--sp-3) var(--sp-4); min-height: 88px; resize: vertical; }
.form-control:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-light);
  outline: none;
}
.form-control.invalid { border-color: var(--color-danger); }
.form-control.invalid:focus { box-shadow: 0 0 0 3px var(--color-danger-bg); }
.form-control:disabled { background: var(--color-bg); color: var(--color-text-faint); }

select.form-control {
  appearance: none;
  -webkit-appearance: none;
  background-image: var(--select-caret);
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  padding-right: var(--sp-8);
}

.form-row { display: grid; grid-template-columns: 1fr; gap: 0 var(--sp-4); }
@media (min-width: 640px) {
  .form-row.cols-2 { grid-template-columns: 1fr 1fr; }
}

.checkbox-row { display: flex; align-items: center; gap: var(--sp-3); min-height: var(--touch-target); cursor: pointer; }
.checkbox-row input[type="checkbox"] { width: 22px; height: 22px; accent-color: var(--color-primary); flex-shrink: 0; }
.checkbox-row label, .checkbox-row span { font-size: var(--fs-base); color: var(--color-text); }

/* An input with a TRAILING action button — currently the password field and
   its show/hide toggle.

   This used to also carry `padding-left: var(--sp-9)` and absolutely position
   a leading icon, copied from the search field pattern. There is no leading
   icon here, so all that did was indent the password text 48px while the
   email field directly above it started at 16px — two fields in the same form
   that did not line up. The leading-icon case is handled by .search-field,
   which is where it belongs. */
.input-with-icon { position: relative; }
/* Keeps typed text clear of the toggle button instead of running under it. */
.input-with-icon .form-control { padding-right: var(--sp-9); }
.input-with-icon .icon-toggle {
  position: absolute; right: var(--sp-1); top: 50%; transform: translateY(-50%);
  background: none; border: none; padding: 0; cursor: pointer; color: var(--color-text-muted);
  /* Full touch target, like every other control. */
  min-height: var(--touch-target); min-width: var(--touch-target);
  border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
}
.input-with-icon .icon-toggle:hover { color: var(--color-text); background: var(--color-surface-raised); }

.form-actions {
  display: flex;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}
.form-actions .btn { flex: 1; }

/* Pending password-reset requests panel on the Users page. */
.reset-requests-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.reset-requests-title {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-md);
}

/* One-time temporary-password display (admin password reset). Monospace and
   user-select:all so the admin can read/copy it exactly; readable in both
   themes via design tokens. */
.temp-password {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
  user-select: all;
  word-break: break-all;
  text-align: center;
  padding: var(--sp-4);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-raised);
  color: var(--color-text);
}

/* ---- Tables (desktop) ---- */
.table-wrap {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  /* Desktop data tables collapse to cards on mobile, but a table that is shown
     directly on a phone (the Password Reset Requests list) can be wider than
     the screen. Scroll it INSIDE this container instead of letting it push the
     whole page sideways; the rounded corners still clip the content. */
  overflow-x: auto;
  overflow-y: hidden;
  box-shadow: var(--shadow-sm);
}
table.data-table { width: 100%; border-collapse: collapse; }
.data-table th {
  text-align: left;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  font-weight: var(--fw-semibold);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg);
}
.data-table td {
  padding: var(--sp-4);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--fs-sm);
  vertical-align: middle;
}
.data-table tr:last-child td { border-bottom: none; }
.data-table tr:hover td { background: var(--color-primary-50); }
.cell-primary { font-weight: var(--fw-semibold); color: var(--color-text); }
.cell-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; }

/* ---- Mobile list-cards (replace tables) ---- */
.list-cards { display: flex; flex-direction: column; gap: var(--sp-3); }
.list-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--sp-4) var(--sp-4) var(--sp-3);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-base), border-color var(--transition-base);
}
.list-card-top { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.list-card-title { font-weight: var(--fw-semibold); font-size: var(--fs-md); line-height: var(--lh-snug); overflow-wrap: break-word; }
.list-card-sub { font-size: var(--fs-sm); color: var(--color-text-muted); margin-top: 2px; }
.list-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-5);
  margin: var(--sp-3) 0;
  font-size: var(--fs-sm);
}
.list-card-meta-item { display: flex; flex-direction: column; }
.list-card-meta-label { font-size: var(--fs-xs); color: var(--color-text-faint); text-transform: uppercase; letter-spacing: 0.03em; font-weight: var(--fw-medium); }
.list-card-meta-value { color: var(--color-text); font-weight: var(--fw-medium); }
/* Action buttons under a mobile card. They share the row when they fit, but a
   card with several buttons (e.g. View / Reset Password / Edit / Delete on the
   Users page) would otherwise overflow: flex items default to min-width:auto,
   so they cannot shrink below their label and, with no wrapping, the last
   button spilled past the card and the viewport edge. Allowing the row to wrap
   and the buttons to shrink keeps every button on-screen and tappable. */
.list-card-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }
.list-card-actions .btn { flex: 1 1 auto; min-width: 0; }

/* Show table on desktop, cards on mobile */
.desktop-table { display: none; }
.mobile-cards { display: block; }
@media (min-width: 900px) {
  .desktop-table { display: block; }
  .mobile-cards { display: none; }
}

/* ---- Search / filter bar ---- */
.toolbar {
  /* Grid on a phone: the search field spans the full width, and the narrower
     filters pair up beneath it. Stacked, three filters cost ~200px before a
     single result is visible. */
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.toolbar > * { min-width: 0; }
.toolbar .search-field { grid-column: 1 / -1; }
/* Exactly one filter under the search field takes the whole row rather than
   half of one. Matched as "second AND last child" so it cannot also catch the
   third filter of three, which belongs beside the second. */
.toolbar > :nth-child(2):last-child { grid-column: 1 / -1; }
@media (min-width: 640px) {
  .toolbar { display: flex; flex-direction: row; align-items: center; }
  .toolbar .search-field { flex: 1; }
  /* A filter dropdown sits BESIDE the search field on this row. `.form-control`
     gives every field width:100%, which as a flex-basis is the whole toolbar
     width - it would starve the flex:1 search field down to zero, and its
     absolutely-positioned magnifier would then spill out over the dropdown's
     text. Capping the dropdown's width clamps that basis, so the search field
     keeps a real width and the icon stays inside it. (On a phone the toolbar is
     a grid and the dropdown spans the full row, which is what we want there.) */
  .toolbar > select.form-control { width: auto; max-width: 220px; }
}
.search-field { position: relative; }
.search-field input { padding-left: var(--sp-9); }
.search-field .icon { position: absolute; left: var(--sp-3); top: 50%; transform: translateY(-50%); color: var(--color-text-faint); }

/* Flatpickr replaces the native date input's built-in clear "x", so filter
   contexts (unlike a required list-creation date) get an explicit one back. */
.date-filter-field { position: relative; width: 100%; }
@media (min-width: 640px) { .date-filter-field { width: 170px; flex-shrink: 0; } }
.date-filter-field input { padding-right: var(--sp-8); }
.date-filter-clear {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  /* Full touch target: this sits inside a field, so it is easy to make it
     visually small and functionally unhittable. The icon stays small; the
     tappable box does not. */
  width: var(--touch-target); height: var(--touch-target);
  border: none; background: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: var(--color-text-faint); border-radius: var(--radius-md);
}
.date-filter-clear:hover { background: var(--color-surface-raised); color: var(--color-text-muted); }
.date-filter-clear .icon { width: 18px; height: 18px; }

/* ---- Empty states ---- */
.empty-state {
  text-align: center;
  padding: var(--sp-9) var(--sp-5);
  color: var(--color-text-muted);
}
.empty-state .icon-lg { margin: 0 auto var(--sp-4); color: var(--color-text-faint); width: 48px; height: 48px; }
.empty-state h3 { color: var(--color-text); font-size: var(--fs-md); margin-bottom: var(--sp-2); }
.empty-state p { max-width: 42ch; margin-left: auto; margin-right: auto; }
.empty-state p { margin-bottom: var(--sp-5); font-size: var(--fs-sm); line-height: var(--lh-base); }

/* ---- Toasts ---- */
.toast-region {
  position: fixed;
  top: var(--sp-4);
  left: 50%;
  transform: translateX(-50%);
  z-index: 900;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: calc(100% - var(--sp-6));
  max-width: 420px;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  background: var(--color-toast-bg);
  color: var(--color-toast-text);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-sm);
  animation: toast-in var(--transition-base);
  pointer-events: auto;
}
.toast.success { background: var(--color-toast-success); }
.toast.error { background: var(--color-toast-error); }
.toast.info { background: var(--color-toast-info); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ---- Modal / bottom-sheet ---- */
.modal-backdrop {
  position: fixed; inset: 0;
  background: var(--color-scrim);
  z-index: 800;
  display: none;
  align-items: flex-end;
  justify-content: center;
}
.modal-backdrop.open { display: flex; }
@media (min-width: 640px) {
  .modal-backdrop { align-items: center; }
}
.modal-sheet {
  background: var(--color-surface-overlay);
  width: 100%;
  max-width: 480px;
  max-height: 90vh;
  overflow-y: auto;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: var(--sp-5);
  box-shadow: var(--shadow-lg);
  animation: sheet-up var(--transition-base);
}
@media (min-width: 640px) {
  .modal-sheet { border-radius: var(--radius-lg); animation: modal-in var(--transition-base); }
}
@keyframes sheet-up { from { transform: translateY(24px); opacity: 0.6; } to { transform: translateY(0); opacity: 1; } }
@keyframes modal-in { from { transform: scale(0.97); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.modal-handle {
  width: 40px; height: 4px; border-radius: var(--radius-full);
  background: var(--color-border-strong);
  margin: 0 auto var(--sp-4);
}
@media (min-width: 640px) { .modal-handle { display: none; } }
.modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-4); }
.modal-title { font-size: var(--fs-lg); font-weight: var(--fw-semibold); }
.modal-close { background: none; border: none; padding: var(--sp-2); cursor: pointer; color: var(--color-text-muted); min-width: var(--touch-target); min-height: var(--touch-target); border-radius: var(--radius-md); }

/* ---- Confirm dialog ---- */
.confirm-icon {
  width: 48px; height: 48px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-danger-bg); color: var(--color-danger);
  margin-bottom: var(--sp-4);
}
.confirm-icon.neutral { background: var(--color-primary-50); color: var(--color-primary); }

/* ---- Loading skeleton ---- */
.skeleton { background: linear-gradient(90deg, var(--color-border) 25%, var(--color-skeleton-sheen) 37%, var(--color-border) 63%); background-size: 400% 100%; animation: skeleton-loading 1.4s ease infinite; border-radius: var(--radius-md); }
@keyframes skeleton-loading { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* ---- Section heading ---- */
.page-heading { margin-bottom: var(--sp-5); }
.page-heading h1 { font-size: var(--fs-lg); line-height: var(--lh-tight); letter-spacing: var(--tracking-tight); }
.page-heading p { color: var(--color-text-muted); font-size: var(--fs-sm); line-height: var(--lh-base); margin: var(--sp-1) 0 0; }
.page-heading-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-5); }
.page-heading-row .page-heading { margin-bottom: 0; }

/* ---- Product combobox option content ----
   The dropdown/search chrome itself is Tom Select's (.ts-wrapper/.ts-dropdown,
   themed in vendor-theme.css) — these classes only style the option content
   Tom Select renders inside it (see the `render` config in scan.js). */
.combo-option-row { display: flex; flex-direction: column; }
.combo-option-row .opt-name { font-weight: var(--fw-medium); font-size: var(--fs-sm); }
.combo-option-row .opt-meta { font-size: var(--fs-xs); color: var(--color-text-muted); }
.combo-empty { padding: var(--sp-2); text-align: center; color: var(--color-text-muted); font-size: var(--fs-sm); }
.combo-option-add { display: flex; align-items: center; gap: var(--sp-2); color: var(--color-primary); font-weight: var(--fw-semibold); }

/* ---- Collapsible section ----
   Built on native <details>/<summary>: keyboard and screen-reader behaviour
   come free, and it works with no JavaScript at all.

   Used where a screen has fields that matter less than the one the operator
   is actually there for. On the scan screen that is the difference between
   the barcode input being above the fold or below it. */
.collapse {
  border-top: 1px solid var(--color-border);
  margin-top: var(--sp-4);
}
.collapse > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  min-height: var(--touch-target);
  padding: var(--sp-2) 0;
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--color-text-muted);
  list-style: none;
}
.collapse > summary::-webkit-details-marker { display: none; }
.collapse > summary:hover { color: var(--color-text); }
.collapse > summary::after {
  content: "";
  width: 10px; height: 10px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--transition-fast);
  flex-shrink: 0;
}
.collapse[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.collapse-body { padding-top: var(--sp-3); }
.collapse-body > .form-group:last-child { margin-bottom: 0; }

/* ---- Inline "add new location" fast path on the Scan page ---- */
.new-location-panel {
  background: var(--color-bg);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  margin-bottom: var(--sp-5);
}
.new-location-panel-title { font-weight: var(--fw-semibold); font-size: var(--fs-sm); margin-bottom: var(--sp-3); }

/* ---- Scan progress (optional expected-quantity tracking) ---- */
.scan-progress { margin: var(--sp-4) 0; }
.scan-progress-row { display: flex; justify-content: space-between; font-size: var(--fs-sm); font-weight: var(--fw-medium); margin-bottom: 6px; font-variant-numeric: tabular-nums; }
.scan-progress-track { height: 8px; border-radius: var(--radius-full); background: var(--color-bg); overflow: hidden; }
.scan-progress-fill { height: 100%; border-radius: var(--radius-full); background: var(--color-primary); transition: width 320ms cubic-bezier(0.22, 1, 0.36, 1); }
.scan-progress.is-complete .scan-progress-fill { background: var(--color-success); }
.scan-progress.is-complete .scan-progress-row { color: var(--color-success); }
/* Past the expected quantity. Warning, not danger: the count is not wrong,
   it just does not match what was expected — which is information the
   operator wants, not an error to be scolded for. */
.scan-progress.is-over .scan-progress-fill { background: var(--color-warning); }
.scan-progress.is-over .scan-progress-row { color: var(--color-warning); }
@media (prefers-reduced-motion: reduce) { .scan-progress-fill { transition: none; } }

/* ---- Shake feedback for a rejected/duplicate input ---- */
@keyframes shake-error {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-4px); }
  40%, 60% { transform: translateX(4px); }
}
.shake-error { animation: shake-error 400ms cubic-bezier(.36,.07,.19,.97) both; border-color: var(--color-danger) !important; }
@media (prefers-reduced-motion: reduce) { .shake-error { animation: none; } }

/* ---- Scan entry rows ---- */
.entry-count-heading { display: flex; align-items: center; justify-content: space-between; margin: var(--sp-6) 0 var(--sp-3); }
.entry-count-heading h2 { font-size: var(--fs-md); margin: 0; }

/* One card per distinct product — every barcode/serial scanned for that
   product is grouped underneath it instead of repeating the product name
   per scan (see scan.js groupEntriesByItem). Each serial stays individually
   removable; only the presentation is grouped, not the underlying records. */
.entry-group {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--sp-4);
  margin-bottom: var(--sp-3);
}
.entry-group-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding-bottom: var(--sp-3);
  margin-bottom: var(--sp-2);
  border-bottom: 1px solid var(--color-border);
}
.entry-group-name { font-weight: var(--fw-semibold); font-size: var(--fs-base); min-width: 0; overflow-wrap: break-word; }
.entry-group-count {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
  background: var(--color-bg);
  padding: 3px var(--sp-3);
  border-radius: var(--radius-full);
  flex-shrink: 0;
  white-space: nowrap;
}

.entry-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.entry-group .entry-row:last-child { border-bottom: none; padding-bottom: 0; }
.entry-row .entry-index {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--color-primary-50); color: var(--color-primary-dark);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-xs); font-weight: var(--fw-semibold); flex-shrink: 0;
}
.entry-row .entry-info { flex: 1; min-width: 0; }
.entry-row .entry-item-name { font-weight: var(--fw-semibold); font-size: var(--fs-sm); }
.entry-row .entry-serial { font-size: var(--fs-sm); color: var(--color-text-muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: break-word; }
.entry-row .entry-meta { font-size: var(--fs-xs); color: var(--color-text-faint); margin-top: 2px; }
.entry-row .remove-btn { flex-shrink: 0; min-height: var(--touch-target); }
/* .entry-row-added is just a marker class — the pop-in motion itself runs
   through anime.js (see scan.js) so it shares one animation engine with the
   rest of the app's motion instead of a separate CSS keyframe. */

@media (max-width: 479px) {
  /* No horizontal scrolling on narrow phones — let the remove button drop
     to its own line rather than fight the serial number for width. */
  .entry-row { flex-wrap: wrap; }
  .entry-row .remove-btn { margin-left: calc(28px + var(--sp-3)); }
}

/* ---- Activity timeline ---- */
.timeline { display: flex; flex-direction: column; }
.timeline-item { display: flex; gap: var(--sp-3); position: relative; padding-bottom: var(--sp-5); }
.timeline-item:last-child { padding-bottom: 0; }
.timeline-item:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 15px;
  top: 34px;
  bottom: -4px;
  width: 1px;
  background: var(--color-border);
}
.timeline-dot {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--color-primary-50); color: var(--color-primary);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; z-index: 1;
}
.timeline-dot .icon { width: 16px; height: 16px; }
.timeline-content { flex: 1; min-width: 0; padding-top: 4px; }
.timeline-text { font-size: var(--fs-sm); color: var(--color-text); }
.timeline-meta { font-size: var(--fs-xs); color: var(--color-text-faint); margin-top: 2px; }

/* ---- Ranked list (e.g. Most Used Products) ---- */
.ranked-list { display: flex; flex-direction: column; gap: var(--sp-3); }
.ranked-item { display: flex; align-items: center; gap: var(--sp-3); }
.ranked-item .rank-badge {
  width: 24px; height: 24px; border-radius: var(--radius-sm);
  background: var(--color-bg); color: var(--color-text-muted);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-xs); font-weight: var(--fw-semibold); flex-shrink: 0;
}
.ranked-item .rank-body { flex: 1; min-width: 0; }
.ranked-item .rank-label { display: flex; justify-content: space-between; gap: var(--sp-2); font-size: var(--fs-sm); font-weight: var(--fw-medium); margin-bottom: 4px; }
.ranked-item .rank-count { color: var(--color-text-muted); font-weight: var(--fw-regular); }
.ranked-item .rank-bar-track { height: 6px; border-radius: var(--radius-full); background: var(--color-bg); overflow: hidden; }
.ranked-item .rank-bar-fill { height: 100%; border-radius: var(--radius-full); background: var(--color-primary); }

/* ---- View-only badge (Super Admin's cross-company read-only pages) ---- */
.view-only-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: var(--radius-full);
  background: var(--color-bg); border: 1px solid var(--color-border-strong);
  color: var(--color-text-muted); font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  white-space: nowrap; flex-shrink: 0;
}
.view-only-badge .icon { width: 13px; height: 13px; }

/* ---- Section icon chip (dashboard card headers) ---- */
.section-icon {
  width: 26px; height: 26px; border-radius: var(--radius-sm);
  background: var(--color-primary-50); color: var(--color-primary);
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.section-icon .icon { width: 15px; height: 15px; }

/* ---- Two-column dashboard section (stacks on mobile) ---- */
.dash-grid-2 { display: grid; grid-template-columns: 1fr; gap: var(--sp-4); }
@media (min-width: 900px) {
  .dash-grid-2 { grid-template-columns: 1fr 1fr; align-items: start; }
}

/* The 7-day activity chart now renders on a <canvas> via Chart.js (see
   dashboard.js) instead of hand-built DOM bars, so no bar-chart-* rules are
   needed here any more. */

/* ---- Divider ---- */
.divider { height: 1px; background: var(--color-border); margin: var(--sp-5) 0; border: none; }

/* Page-entrance motion now runs via anime.js (Layout.init -> animatePageIn)
   instead of a CSS keyframe, so every "delight" animation goes through one
   engine. See assets/js/layout.js. */

/* ---- Company detail sections (Super Admin) ----
   A label/value row and a compact person row, reused across all six
   sections of the company-detail screen so they read as one page rather
   than six differently-built blocks. */
.detail-section { margin-bottom: var(--sp-4); }
.detail-count {
  display: inline-block;
  margin-left: var(--sp-1);
  padding: 1px var(--sp-2);
  border-radius: var(--radius-full);
  background: var(--color-surface-raised);
  color: var(--color-text-muted);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  vertical-align: middle;
}
.detail-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-4);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--fs-sm);
}
.detail-row:last-child { border-bottom: none; padding-bottom: 0; }
.detail-row-label { color: var(--color-text-muted); flex-shrink: 0; }
.detail-row-value {
  font-weight: var(--fw-medium);
  text-align: right;
  min-width: 0;
  overflow-wrap: break-word;
}
.detail-person {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--color-border);
}
.detail-person:last-child { border-bottom: none; padding-bottom: 0; }
.detail-person-main { min-width: 0; }
.detail-person-name { font-weight: var(--fw-semibold); font-size: var(--fs-sm); overflow-wrap: break-word; }
.detail-person-meta { font-size: var(--fs-xs); color: var(--color-text-muted); overflow-wrap: break-word; }
.detail-person-tags { display: flex; flex-wrap: wrap; gap: var(--sp-1); justify-content: flex-end; flex-shrink: 0; }

/* ---- Company status action strip ---- */
.status-action-bar {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  margin-bottom: var(--sp-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}
.status-action-bar.is-pending { border-color: var(--color-warning); background: var(--color-warning-bg); color: var(--color-warning); }
.status-action-bar.is-suspended { border-color: var(--color-danger); background: var(--color-danger-bg); color: var(--color-danger); }
.status-action-bar .btn { align-self: stretch; }
@media (min-width: 640px) {
  .status-action-bar { flex-direction: row; align-items: center; justify-content: space-between; }
  .status-action-bar .btn { align-self: auto; flex-shrink: 0; }
}
