/* =============================================================================
 * ChefStack Design System - Layer 3: Components
 * =============================================================================
 * Every interactive surface in the portal, driven by the tokens in tokens.css.
 *
 * Buttons, inputs, tabs and dropdown items all share --cs-radius-control (3px)
 * and the same border width, focus ring, icon box and type ramp, so a control
 * looks identical whichever module rendered it.
 *
 * Intent mapping (fixed across the whole application):
 *   primary   -> purple, the one main action on a screen (solid fill)
 *   secondary -> blue,   supporting actions (pastel fill)
 *   tertiary  -> green,  additive / confirming actions (pastel fill)
 *   warning   -> orange, reversible but consequential actions
 *   danger    -> red,    DELETION ONLY - never for emphasis
 *   neutral   -> slate,  navigation and dismissal (back, reset, cancel)
 *
 * `!important` appears deliberately: the legacy NobleUI bundle
 * (demo_1/style.css, dashboard-style.css) declares !important on the same
 * selectors and loads first. Keeping it here is what lets one visual language
 * win over the template.
 * ========================================================================== */

/* =============================================================================
 * Buttons
 * =============================================================================
 * One geometry block, then each intent only re-points four custom properties.
 * ========================================================================== */
.btn,
.cs-btn {
  --cs-btn-bg: var(--cs-neutral-soft);
  --cs-btn-bg-hover: var(--cs-neutral-soft-hover);
  --cs-btn-fg: var(--cs-neutral-text);
  --cs-btn-border: var(--cs-neutral-border);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--cs-space-2);
  height: var(--cs-control-height-md) !important;
  min-height: var(--cs-control-height-md) !important;
  padding: 0 var(--cs-space-4) !important;
  border: var(--cs-border-width) solid var(--cs-btn-border) !important;
  border-radius: var(--cs-radius-control) !important;
  background: var(--cs-btn-bg) !important;
  color: var(--cs-btn-fg) !important;
  font-family: var(--cs-font-sans) !important;
  font-size: var(--cs-text-base) !important;
  font-weight: var(--cs-weight-medium) !important;
  line-height: var(--cs-leading-tight) !important;
  letter-spacing: var(--cs-tracking-tight);
  text-align: center;
  text-decoration: none;
  text-transform: none !important;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: none !important;
  transform: none !important;
  transition: background-color var(--cs-transition-fast),
    border-color var(--cs-transition-fast), color var(--cs-transition-fast);
}

.btn:hover,
.cs-btn:hover {
  background: var(--cs-btn-bg-hover) !important;
  color: var(--cs-btn-fg) !important;
  text-decoration: none;
}

.btn:focus-visible,
.cs-btn:focus-visible {
  outline: 2px solid var(--cs-btn-fg) !important;
  outline-offset: 2px;
  box-shadow: var(--cs-focus-ring) !important;
}

.btn:disabled,
.btn.disabled,
.cs-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* --- Intents -------------------------------------------------------------- */

/* Primary: solid purple. The single strongest affordance on a page. */
.btn-primary,
.btn-theme,
.btn-theme-small,
.cs-btn-primary,
.swal2-actions .swal2-confirm {
  --cs-btn-bg: var(--cs-primary);
  --cs-btn-bg-hover: var(--cs-primary-hover);
  --cs-btn-fg: var(--cs-on-primary);
  --cs-btn-border: var(--cs-primary);
}
.btn-primary:hover,
.btn-theme:hover,
.btn-theme-small:hover,
.cs-btn-primary:hover,
.swal2-actions .swal2-confirm:hover {
  --cs-btn-border: var(--cs-primary-hover);
}

/* Primary, quiet: pastel purple for a secondary-weight brand action. */
.btn-primary-soft,
.cs-btn-primary-soft {
  --cs-btn-bg: var(--cs-primary-soft);
  --cs-btn-bg-hover: var(--cs-primary-soft-hover);
  --cs-btn-fg: var(--cs-primary-text);
  --cs-btn-border: var(--cs-primary-border);
}

/* Secondary: pastel blue. */
.btn-secondary,
.btn-info,
.cs-btn-secondary {
  --cs-btn-bg: var(--cs-secondary-soft);
  --cs-btn-bg-hover: var(--cs-secondary-soft-hover);
  --cs-btn-fg: var(--cs-secondary-text);
  --cs-btn-border: var(--cs-secondary-border);
}

/* Tertiary: pastel green. `btn-success` is the Bootstrap alias already in use. */
.btn-success,
.btn-tertiary,
.cs-btn-tertiary {
  --cs-btn-bg: var(--cs-tertiary-soft);
  --cs-btn-bg-hover: var(--cs-tertiary-soft-hover);
  --cs-btn-fg: var(--cs-tertiary-text);
  --cs-btn-border: var(--cs-tertiary-border);
}

/* Warning: pastel orange. Consequential but reversible. */
.btn-warning,
.cs-btn-warning {
  --cs-btn-bg: var(--cs-warning-soft);
  --cs-btn-bg-hover: var(--cs-warning-soft-hover);
  --cs-btn-fg: var(--cs-warning-text);
  --cs-btn-border: var(--cs-warning-border);
}

/* Danger: pastel red. Reserved for deletion. */
.btn-danger,
.cs-btn-danger {
  --cs-btn-bg: var(--cs-danger-soft);
  --cs-btn-bg-hover: var(--cs-danger-soft-hover);
  --cs-btn-fg: var(--cs-danger-text);
  --cs-btn-border: var(--cs-danger-border);
}

/* Danger, solid: the final confirm step of a destructive flow only. */
.btn-danger-solid,
.cs-btn-danger-solid,
.swal2-actions .swal2-deny {
  --cs-btn-bg: var(--cs-danger);
  --cs-btn-bg-hover: var(--cs-danger-hover);
  --cs-btn-fg: var(--cs-on-danger);
  --cs-btn-border: var(--cs-danger);
}

/* Neutral / quiet: back, reset, cancel, close, and the legacy dark variants. */
.btn-light,
.btn-dark,
.btn-outline-dark,
.btn-outline-secondary,
.btn-neutral,
.cs-btn-neutral,
.swal2-actions .swal2-cancel {
  --cs-btn-bg: var(--cs-surface);
  --cs-btn-bg-hover: var(--cs-surface-hover);
  --cs-btn-fg: var(--cs-neutral-text);
  --cs-btn-border: var(--cs-border-strong);
}

/* Ghost: toolbar and table-row actions that must not compete with content. */
.btn-ghost,
.cs-btn-ghost,
.btn-link {
  --cs-btn-bg: transparent;
  --cs-btn-bg-hover: var(--cs-surface-hover);
  --cs-btn-fg: var(--cs-text-muted);
  --cs-btn-border: transparent;
}
.btn-ghost:hover,
.cs-btn-ghost:hover,
.btn-link:hover { --cs-btn-fg: var(--cs-text); }

/* --- Outline variants ------------------------------------------------------
 * Transparent fill, intent-coloured hairline and label. Geometry is unchanged.
 * ------------------------------------------------------------------------- */
.btn-outline-primary,
.btn-outline-theme { --cs-btn-fg: var(--cs-primary-text); --cs-btn-border: var(--cs-primary-border); --cs-btn-bg-hover: var(--cs-primary-soft); }
.btn-outline-info { --cs-btn-fg: var(--cs-secondary-text); --cs-btn-border: var(--cs-secondary-border); --cs-btn-bg-hover: var(--cs-secondary-soft); }
.btn-outline-success { --cs-btn-fg: var(--cs-tertiary-text); --cs-btn-border: var(--cs-tertiary-border); --cs-btn-bg-hover: var(--cs-tertiary-soft); }
.btn-outline-warning { --cs-btn-fg: var(--cs-warning-text); --cs-btn-border: var(--cs-warning-border); --cs-btn-bg-hover: var(--cs-warning-soft); }
.btn-outline-danger { --cs-btn-fg: var(--cs-danger-text); --cs-btn-border: var(--cs-danger-border); --cs-btn-bg-hover: var(--cs-danger-soft); }

.btn[class*="btn-outline-"] { --cs-btn-bg: transparent; }

/* --- Sizes ---------------------------------------------------------------- */
.btn-sm,
.btn-group-sm > .btn,
.btn-theme-small,
.cs-btn-sm {
  height: var(--cs-control-height-sm) !important;
  min-height: var(--cs-control-height-sm) !important;
  padding: 0 var(--cs-space-3) !important;
  font-size: var(--cs-text-sm) !important;
  gap: var(--cs-space-1);
}

.btn-lg,
.btn-group-lg > .btn,
.cs-btn-lg {
  height: var(--cs-control-height-lg) !important;
  min-height: var(--cs-control-height-lg) !important;
  padding: 0 var(--cs-space-5) !important;
  font-size: var(--cs-text-md) !important;
}

/* --- Icon handling ---------------------------------------------------------
 * Same 16px box whether the icon came from Feather (<svg>) or Bootstrap Icons
 * (<i class="bi">). The legacy `me-2`/`mr-2` spacing utilities on button icons
 * are neutralised - the flex `gap` owns the spacing now, so a button never
 * ends up with a double gap.
 * ------------------------------------------------------------------------- */
.btn > i,
.btn > svg,
.cs-btn > i,
.cs-btn > svg {
  flex: 0 0 auto;
  width: var(--cs-icon-size);
  height: var(--cs-icon-size);
  font-size: var(--cs-icon-size) !important;
  line-height: 1;
  margin: 0 !important;
}

.btn > i.bi::before,
.cs-btn > i.bi::before { vertical-align: middle; }

.btn-sm > i,
.btn-sm > svg { width: var(--cs-icon-size-sm); height: var(--cs-icon-size-sm); font-size: var(--cs-icon-size-sm) !important; }

/* Square icon-only buttons. */
.btn-icon,
.cs-icon-button,
.grid-action-btn-icon {
  width: var(--cs-control-height-md);
  min-width: var(--cs-control-height-md);
  padding: 0 !important;
}
.btn-icon.btn-sm,
.cs-icon-button.btn-sm {
  width: var(--cs-control-height-sm);
  min-width: var(--cs-control-height-sm);
  height: var(--cs-control-height-sm);
}

/* --- Spacing between adjacent buttons --------------------------------------
 * Spacing is intrinsic to the system rather than sprinkled per call site as
 * `mr-2`/`me-2`. Button groups and input groups opt out below because their
 * controls are meant to sit flush.
 * ------------------------------------------------------------------------- */
.btn + .btn { margin-left: var(--cs-space-2); }
.btn-group .btn + .btn,
.input-group .btn + .btn,
.input-group > .btn { margin-left: -1px; }

/* --- Button groups & toolbars --------------------------------------------- */
.btn-group > .btn { border-radius: 0 !important; }
.btn-group > .btn:first-child { border-top-left-radius: var(--cs-radius-control) !important; border-bottom-left-radius: var(--cs-radius-control) !important; }
.btn-group > .btn:last-child { border-top-right-radius: var(--cs-radius-control) !important; border-bottom-right-radius: var(--cs-radius-control) !important; }
.btn-group > .btn + .btn { margin-left: -1px; }
.btn-group > .btn:hover { position: relative; z-index: 1; }

.cs-toolbar {
  display: flex;
  align-items: center;
  gap: var(--cs-space-2);
  flex-wrap: wrap;
}

/* =============================================================================
 * Form controls
 * ========================================================================== */
.form-control,
.form-select,
.custom-select,
.input-group-text,
.cs-input,
textarea.form-control,
.select2-container--default .select2-selection--single,
.select2-container--krajee-bs4 .select2-selection {
  min-height: var(--cs-control-height-md);
  padding: 0.35rem var(--cs-space-3) !important;
  border: var(--cs-border-width) solid var(--cs-border-strong) !important;
  border-radius: var(--cs-radius-control) !important;
  background-color: var(--cs-surface) !important;
  color: var(--cs-text) !important;
  font-family: var(--cs-font-sans) !important;
  font-size: var(--cs-text-base) !important;
  line-height: var(--cs-leading-snug);
  box-shadow: none !important;
  transition: border-color var(--cs-transition-fast), box-shadow var(--cs-transition-fast);
}

textarea.form-control { min-height: calc(var(--cs-control-height-md) * 2); padding-top: var(--cs-space-2) !important; }

.form-control::placeholder { color: var(--cs-text-subtle); opacity: 1; }

.form-control:focus,
.form-select:focus,
.custom-select:focus,
.cs-input:focus {
  border-color: var(--cs-primary) !important;
  box-shadow: var(--cs-focus-ring) !important;
  outline: none;
}

.form-control:disabled,
.form-control[readonly] {
  background-color: var(--cs-surface-sunken) !important;
  color: var(--cs-text-muted) !important;
  cursor: not-allowed;
}

.form-control.is-invalid,
.has-error .form-control {
  border-color: var(--cs-danger) !important;
}
.invalid-feedback,
.help-block-error { color: var(--cs-danger-text); font-size: var(--cs-text-xs); }

label,
.form-label,
.control-label {
  color: var(--cs-text-muted);
  font-family: var(--cs-font-sans);
  font-size: var(--cs-text-sm);
  font-weight: var(--cs-weight-medium);
  margin-bottom: var(--cs-space-1);
}

.input-group-text {
  background: var(--cs-surface-subtle) !important;
  color: var(--cs-text-muted) !important;
  border-color: var(--cs-border-strong) !important;
}

/* Input groups: the shared 3px radius belongs to the outer edges only. */
.input-group > :not(:first-child) { border-top-left-radius: 0 !important; border-bottom-left-radius: 0 !important; margin-left: -1px; }
.input-group > :not(:last-child) { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; }

/* Checkboxes and radios pick up the brand colour. */
.form-check-input:checked,
.custom-control-input:checked ~ .custom-control-label::before {
  background-color: var(--cs-primary) !important;
  border-color: var(--cs-primary) !important;
}
.form-check-input:focus { box-shadow: var(--cs-focus-ring); }

/* Select2, to match a native select. */
.select2-container--default .select2-selection--single .select2-selection__rendered,
.select2-container--krajee-bs4 .select2-selection__rendered { color: var(--cs-text) !important; line-height: var(--cs-leading-snug) !important; padding-left: 0 !important; }
.select2-dropdown {
  background: var(--cs-surface) !important;
  border: var(--cs-border-width) solid var(--cs-border) !important;
  border-radius: var(--cs-radius-control) !important;
  box-shadow: var(--cs-shadow-md) !important;
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
  background: var(--cs-primary-soft) !important;
  color: var(--cs-primary-text) !important;
}

/* =============================================================================
 * Cards, panels and modals
 * ========================================================================== */
.card,
.cs-card,
.modal-content {
  background: var(--cs-surface);
  border: var(--cs-border-width) solid var(--cs-border) !important;
  border-radius: var(--cs-radius-surface) !important;
  box-shadow: var(--cs-shadow-sm);
  color: var(--cs-text);
}

.card-header,
.card-footer,
.modal-header,
.modal-footer {
  background: var(--cs-surface-subtle);
  border-color: var(--cs-border) !important;
  padding: var(--cs-space-4) var(--cs-space-5);
}

.card-header,
.modal-header { border-radius: var(--cs-radius-surface) var(--cs-radius-surface) 0 0; }
.card-footer,
.modal-footer { border-radius: 0 0 var(--cs-radius-surface) var(--cs-radius-surface); }

.card-body,
.modal-body { padding: var(--cs-space-5); }

/* Frosted headers, where the browser supports it. */
@supports (backdrop-filter: blur(14px)) or (-webkit-backdrop-filter: blur(14px)) {
  .card-header,
  .modal-header {
    background: var(--cs-glass);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    backdrop-filter: blur(14px) saturate(140%);
  }
}

.modal-backdrop.show { background: rgba(15, 23, 42, 0.45); opacity: 1; }
.modal-content { box-shadow: var(--cs-shadow-lg); }
.close,
.btn-close { color: var(--cs-text-muted); opacity: 0.8; text-shadow: none; }
.close:hover,
.btn-close:hover { color: var(--cs-text); opacity: 1; }

/* Bootstrap 4 supplies no icon for the empty Bootstrap 5-style btn-close
   buttons used by our modals. Keep both header close variants consistent. */
.modal .modal-dialog .modal-header .close,
.modal .modal-dialog .modal-header .btn-close {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  margin: -8px -8px -8px auto;
  padding: 0;
  border: 0;
  border-radius: var(--cs-radius-control);
  background: transparent;
  color: var(--cs-text);
  font-size: 28px;
  font-weight: 400;
  line-height: 1;
  opacity: 1;
  text-shadow: none;
  cursor: pointer;
}

.modal .modal-dialog .modal-header .btn-close::before {
  content: "\00d7";
}

.modal .modal-dialog .modal-header .close:hover,
.modal .modal-dialog .modal-header .btn-close:hover {
  background: var(--cs-surface-hover);
  color: var(--cs-text);
  opacity: 1;
}

.modal .modal-dialog .modal-header .close:focus-visible,
.modal .modal-dialog .modal-header .btn-close:focus-visible {
  outline: none;
  box-shadow: var(--cs-focus-ring);
}

/* =============================================================================
 * Tabs
 * ========================================================================== */
.nav-tabs {
  display: flex;
  gap: var(--cs-space-1);
  padding: 0 !important;
  border: 0 !important;
  border-bottom: var(--cs-border-width) solid var(--cs-border) !important;
  background: transparent !important;
  box-shadow: none !important;
}

.nav-tabs .nav-item { flex: 0 0 auto; margin: 0; }

/* Resting tabs carry a transparent border at the active radius, so selecting a
 * tab changes colour and nothing else. Without the 1px transparent border here,
 * every label in the strip would shift a pixel as the active chip's border
 * appeared. The previous underline design got that property for free from a
 * transparent `border-bottom`; the chip has to buy it back explicitly. */
.nav-tabs .nav-link {
  display: inline-flex;
  align-items: center;
  gap: var(--cs-space-2);
  padding: var(--cs-space-3) var(--cs-space-4) !important;
  margin: 0 !important;
  border: var(--cs-border-width) solid transparent !important;
  border-radius: var(--cs-radius-surface) !important;
  background: transparent !important;
  color: var(--cs-text) !important;
  font-family: var(--cs-font-sans) !important;
  font-size: var(--cs-text-base) !important;
  font-weight: var(--cs-weight-medium) !important;
  line-height: var(--cs-leading-tight) !important;
  box-shadow: none !important;
  cursor: pointer;
  transition: color var(--cs-transition-fast), background-color var(--cs-transition-fast),
    border-color var(--cs-transition-fast), box-shadow var(--cs-transition-fast);
}

/* Hover is a bare tint: no border, no shadow. Inactive tabs stay chrome-free so
 * that exactly one element in the strip ever looks like a raised surface - see
 * the note on the active state below. */
.nav-tabs .nav-link:hover {
  background: var(--cs-surface-hover) !important;
}

/* Keyboard focus. Tabs had no `:focus-visible` rule at all before this - they
 * were the only interactive element in the system without one. */
.nav-tabs .nav-link:focus-visible {
  outline: none;
  box-shadow: var(--cs-focus-ring) !important;
}

/* A raised chip, not an underline.
 *
 * This reverses the earlier underline-only design, which existed because a
 * filled active tab reads as a button group. Two things keep the chip from
 * reading that way, and both are load-bearing:
 *
 *   1. The strip's baseline hairline runs unbroken behind every tab, framing
 *      them as one navigational rail. A raised surface on a rail reads as
 *      "selected within this strip"; the same surface floating on open canvas
 *      reads as a button. Do not remove the baseline.
 *   2. Inactive tabs carry no chrome whatsoever - no border, no fill, no
 *      shadow. One chrome'd element is a selection; several are a toolbar.
 *
 * The chip sits at --cs-radius-surface rather than --cs-radius-control to make
 * the structural claim explicit: this is a raised panel, not a control. */
.nav-tabs .nav-link.active,
.nav-tabs .nav-item.show .nav-link {
  background: var(--cs-surface) !important;
  border-color: var(--cs-border) !important;
  color: var(--cs-primary) !important;
  font-weight: var(--cs-weight-semibold) !important;
  box-shadow: var(--cs-shadow-sm) !important;
}

/* Hover on the active tab must not repaint the chip. */
.nav-tabs .nav-link.active:hover,
.nav-tabs .nav-item.show .nav-link:hover {
  background: var(--cs-surface) !important;
  color: var(--cs-primary) !important;
}

/* Focus on the active tab keeps the chip's own lift under the focus ring. */
.nav-tabs .nav-link.active:focus-visible,
.nav-tabs .nav-item.show .nav-link:focus-visible {
  box-shadow: var(--cs-focus-ring), var(--cs-shadow-sm) !important;
}

/* Dark mode lifts surfaces with light, not shadow: a black shadow on a
 * near-black canvas is invisible work. The chip's edge carries the lift
 * instead, one step brighter than the strip's own hairline.
 *
 * legacy-bridge.css used to paint the whole strip --cs-surface in dark, which
 * is the chip's own fill - the active tab would have vanished into its own
 * background. `.nav-tabs` is removed from that selector list at source. */
html[data-theme="dark"] .nav-tabs .nav-link.active,
html[data-theme="dark"] .nav-tabs .nav-item.show .nav-link {
  border-color: var(--cs-border-strong) !important;
  box-shadow: none !important;
}

html[data-theme="dark"] .nav-tabs .nav-link.active:focus-visible,
html[data-theme="dark"] .nav-tabs .nav-item.show .nav-link:focus-visible {
  box-shadow: var(--cs-focus-ring) !important;
}

/* The strip's own hairline is the rail the chip sits on, so it has to survive.
 * recipe/custom.css used to strip it with two unscoped `border-bottom: 0
 * !important` rules; those are deleted at source, and this re-asserts the
 * baseline for anything else that tries the same. */
.nav-tabs,
.recipe-page .nav-tabs {
  border-bottom: var(--cs-border-width) solid var(--cs-border) !important;
}

/* nav-fill stretches each tab to an equal share of the row, which turns long
 * labels like "Assembled Recipe Specification" into wide slabs - and a slab is
 * exactly the button-group reading the chip is designed to avoid. Tabs hug
 * their label; the class is removed in the views, and this catches any that
 * are missed. */
.nav-tabs.nav-fill { justify-content: flex-start; }
.nav-tabs.nav-fill .nav-item { flex: 0 0 auto; }

/* The chip is self-contained - it does not fuse into the panel below - so the
 * panel stays a bare region rather than becoming a bordered card: no top
 * border, and enough room to clear the chip's lift. */
.nav-tabs + .tab-content,
.recipe-form-tabs-wrapper + .recipe-form-tab-content-wrapper .tab-content {
  border-top: 0;
  padding-top: var(--cs-space-4);
}

.nav-tabs .nav-link > i,
.nav-tabs .nav-link > svg {
  width: var(--cs-icon-size);
  height: var(--cs-icon-size);
  font-size: var(--cs-icon-size) !important;
  margin: 0 !important;
  flex: 0 0 auto;
}

/* -----------------------------------------------------------------------------
 * Tab bar as page toolbar
 * -----------------------------------------------------------------------------
 * Puts a page's create actions on the same line as its tabs, so a list view
 * does not spend a second full row on one or two buttons. The tab strip keeps
 * the baseline rule across the whole width; the buttons sit above it, aligned
 * to the tabs' text.
 * -------------------------------------------------------------------------- */
.cs-tabbar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--cs-space-3);
}

/* The strip grows to fill the row so its baseline rail runs the full width,
   while the buttons keep their natural size at the right-hand end.

   The chip did not change the strip's height: the resting tab swapped a 2px
   bottom border for a 1px border on all four sides, so the box measures the
   same and `.cs-tabbar__actions` needs no re-tuning. */
.cs-tabbar > .nav-tabs {
  flex: 1 1 auto;
  min-width: 0;
}

.cs-tabbar__actions {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  gap: var(--cs-space-2);
  /* Lifts the buttons clear of the tab strip's baseline rule. */
  padding-bottom: var(--cs-space-2);
}

.cs-tabbar__action-set {
  display: flex;
  align-items: center;
  gap: var(--cs-space-2);
}

/* Only the active tab's actions are in the row; the rest carry [hidden]. The
   explicit rule is needed because the flex display above would otherwise beat
   the UA stylesheet's `[hidden] { display: none }`. */
.cs-tabbar__action-set[hidden] {
  display: none !important;
}

/* Below the tab strip's own scroll breakpoint, actions go under the tabs
   rather than squeezing them. */
@media (max-width: 767.98px) {
  .cs-tabbar { align-items: stretch; }
  .cs-tabbar__actions { justify-content: flex-end; padding-bottom: 0; }
}

/* Pills reuse the same palette. */
.nav-pills .nav-link { border-radius: var(--cs-radius-control) !important; color: var(--cs-text-muted) !important; font-size: var(--cs-text-base); font-weight: var(--cs-weight-medium); }
.nav-pills .nav-link.active { background: var(--cs-primary) !important; color: var(--cs-on-primary) !important; }

/* =============================================================================
 * Tables & Yii GridView
 * ========================================================================== */
.grid-view,
.cs-table-wrap {
  background: var(--cs-surface);
  border: var(--cs-border-width) solid var(--cs-border);
  border-radius: var(--cs-radius-surface);
  box-shadow: var(--cs-shadow-xs);
  overflow: hidden;
}

.table,
.cs-table {
  --bs-table-bg: transparent;
  --bs-table-color: var(--cs-text);
  --bs-table-striped-bg: var(--cs-surface-subtle);
  --bs-table-striped-color: var(--cs-text);
  --bs-table-hover-bg: var(--cs-primary-soft);
  --bs-table-hover-color: var(--cs-text);
  width: 100%;
  margin-bottom: 0;
  background: transparent;
  color: var(--cs-text);
  font-family: var(--cs-font-sans);
  font-size: var(--cs-text-base);
  border-collapse: separate;
  border-spacing: 0;
}

/* Column headers: small, uppercase, muted - a label, not a heading. */
.table thead th,
.grid-view thead th,
.cs-table thead th {
  position: relative;
  padding: var(--cs-space-3) var(--cs-space-3);
  background: var(--cs-surface-subtle) !important;
  border-top: 0 !important;
  border-bottom: var(--cs-border-width) solid var(--cs-border) !important;
  color: var(--cs-text-muted) !important;
  font-family: var(--cs-font-sans);
  font-size: var(--cs-text-2xs) !important;
  font-weight: var(--cs-weight-semibold) !important;
  letter-spacing: var(--cs-tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: middle;
}

.table tbody td,
.grid-view tbody td,
.cs-table tbody td {
  padding: var(--cs-space-3);
  border-top: 0 !important;
  border-bottom: var(--cs-border-width) solid var(--cs-border-subtle) !important;
  color: var(--cs-text);
  vertical-align: middle;
}

.table tbody tr:last-child td,
.grid-view tbody tr:last-child td { border-bottom: 0 !important; }

.table tbody tr,
.grid-view tbody tr { transition: background-color var(--cs-transition-fast); }
.table-hover tbody tr:hover,
.grid-view tbody tr:hover { background: var(--cs-primary-soft) !important; }

.table-striped tbody tr:nth-of-type(odd) { background: var(--cs-surface-subtle); }

/* --- Sort indicators -------------------------------------------------------
 * ONE indicator per sortable header, 4px, right-aligned in reserved space.
 * Unsorted columns show a faint neutral caret; the active column shows a
 * single primary-coloured caret pointing the sort direction. The legacy
 * FontAwesome double-arrow and the site.css triangle fallback are both
 * switched off (see legacy-bridge.css) so nothing stacks two glyphs.
 * ------------------------------------------------------------------------- */
.grid-view th a,
.cs-table th a {
  position: relative;
  display: inline-block;
  padding-right: 14px;
  color: inherit !important;
  text-decoration: none;
}

.grid-view th a::after,
.cs-table th a::after {
  content: "";
  position: absolute;
  right: 0;
  top: 50%;
  margin-top: -2px;
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid currentColor;
  opacity: 0.3;
  transition: opacity var(--cs-transition-fast), border-color var(--cs-transition-fast);
}

.grid-view th a:hover::after { opacity: 0.6; }

.grid-view th a.asc::after,
.cs-table th a.asc::after {
  border-top: 0;
  border-bottom: 4px solid var(--cs-primary);
  opacity: 1;
}

.grid-view th a.desc::after,
.cs-table th a.desc::after {
  border-top-color: var(--cs-primary);
  opacity: 1;
}

.grid-view th a.asc,
.grid-view th a.desc { color: var(--cs-primary-text) !important; }

/* --- Table media -----------------------------------------------------------
 * One thumbnail treatment for every row image, whatever the module called it.
 * ------------------------------------------------------------------------- */
.table img.recipe-img,
.table img.ingredient-img,
.table img.recipe-thumbnail,
.grid-view img.recipe-img,
.grid-view img.ingredient-img,
.cs-thumb {
  width: 40px !important;
  height: 40px !important;
  object-fit: cover;
  border: var(--cs-border-width) solid var(--cs-border) !important;
  border-radius: var(--cs-radius-control) !important;
  background: var(--cs-surface-sunken);
  opacity: 1 !important;
  box-shadow: none !important;
  display: inline-block;
  vertical-align: middle;
}

/* An image that fell back to the placeholder is not a photo and must not be
 * cropped like one. `fill` hands the SVG a viewport the exact shape of the
 * frame; the file's own preserveAspectRatio then centres the glyph and its
 * overdrawn background paints the leftover strips. This is what keeps the
 * placeholder legible in portrait and landscape frames alike.
 *
 * The attribute is set by design-system.js at the moment it swaps the src, so
 * it never applies to a real image. */
img[data-placeholder-applied] {
  object-fit: fill !important;
  background: var(--cs-surface-subtle);
}

/* --- GridView chrome ------------------------------------------------------- */
.grid-view .summary {
  padding: var(--cs-space-3) var(--cs-space-4);
  color: var(--cs-text-muted);
  font-size: var(--cs-text-xs);
  border-bottom: var(--cs-border-width) solid var(--cs-border);
}

.grid-view .empty,
.cs-empty {
  padding: var(--cs-space-10) var(--cs-space-4);
  color: var(--cs-text-muted);
  text-align: center;
  font-size: var(--cs-text-base);
}

.table-responsive { border-radius: var(--cs-radius-surface); }

/* =============================================================================
 * Badges & status pills
 * ========================================================================== */
.badge,
.cs-badge,
.status-badge {
  display: inline-flex;
  align-items: center;
  /* Centred so a status column reads as a tidy stack: the label sits in the
     middle of its pill even when one status is much longer than another. */
  justify-content: center;
  text-align: center;
  gap: var(--cs-space-1);
  padding: 0.2rem var(--cs-space-2);
  border: var(--cs-border-width) solid transparent;
  border-radius: var(--cs-radius-pill);
  font-family: var(--cs-font-sans);
  font-size: var(--cs-text-xs);
  font-weight: var(--cs-weight-semibold);
  line-height: var(--cs-leading-tight);
  letter-spacing: 0;
  white-space: nowrap;
  vertical-align: middle;
}

/* Pastel fills so a table of badges stays calm. */
.badge-primary, .badge.bg-primary, .cs-badge-primary {
  background: var(--cs-primary-soft) !important;
  color: var(--cs-primary-text) !important;
  border-color: var(--cs-primary-border) !important;
}
.badge-secondary, .badge.bg-secondary, .badge-info, .badge.bg-info, .cs-badge-secondary {
  background: var(--cs-secondary-soft) !important;
  color: var(--cs-secondary-text) !important;
  border-color: var(--cs-secondary-border) !important;
}
.badge-success, .badge.bg-success, .cs-badge-success {
  background: var(--cs-tertiary-soft) !important;
  color: var(--cs-tertiary-text) !important;
  border-color: var(--cs-tertiary-border) !important;
}
.badge-warning, .badge.bg-warning, .cs-badge-warning {
  background: var(--cs-warning-soft) !important;
  color: var(--cs-warning-text) !important;
  border-color: var(--cs-warning-border) !important;
}
.badge-danger, .badge.bg-danger, .cs-badge-danger {
  background: var(--cs-danger-soft) !important;
  color: var(--cs-danger-text) !important;
  border-color: var(--cs-danger-border) !important;
}
.badge-light, .badge-dark, .badge.bg-light, .badge.bg-dark, .cs-badge-neutral {
  background: var(--cs-neutral-soft) !important;
  color: var(--cs-neutral-text) !important;
  border-color: var(--cs-border) !important;
}

/* Square badge, when it labels a value rather than a status. */
.cs-badge-square { border-radius: var(--cs-radius-control); }

/* Leading status dot. */
.cs-status-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--cs-radius-pill);
  background: currentColor;
  flex: 0 0 auto;
}

/* =============================================================================
 * Dropdowns
 * ========================================================================== */
.dropdown-menu {
  min-width: 11rem;
  padding: var(--cs-space-1);
  border: var(--cs-border-width) solid var(--cs-border) !important;
  border-radius: var(--cs-radius-surface) !important;
  background: var(--cs-surface) !important;
  box-shadow: var(--cs-shadow-md) !important;
  color: var(--cs-text);
  font-size: var(--cs-text-base);
  z-index: var(--cs-z-dropdown);
}

.dropdown-item {
  display: flex;
  align-items: center;
  gap: var(--cs-space-2);
  padding: var(--cs-space-2) var(--cs-space-3) !important;
  border-radius: var(--cs-radius-control) !important;
  color: var(--cs-text) !important;
  font-family: var(--cs-font-sans);
  font-size: var(--cs-text-base);
  font-weight: var(--cs-weight-regular);
  cursor: pointer;
}

.dropdown-item:hover,
.dropdown-item:focus {
  background: var(--cs-primary-soft) !important;
  color: var(--cs-primary-text) !important;
}

.dropdown-item.active { background: var(--cs-primary) !important; color: var(--cs-on-primary) !important; }

/* A delete entry in a menu is the one place red belongs here. */
.dropdown-item.cs-dropdown-danger,
.dropdown-item.text-danger { color: var(--cs-danger-text) !important; }
.dropdown-item.cs-dropdown-danger:hover,
.dropdown-item.text-danger:hover { background: var(--cs-danger-soft) !important; color: var(--cs-danger-text) !important; }

.dropdown-item > i,
.dropdown-item > svg { width: var(--cs-icon-size); height: var(--cs-icon-size); font-size: var(--cs-icon-size) !important; flex: 0 0 auto; }

.dropdown-divider { margin: var(--cs-space-1) 0; border-color: var(--cs-border); }
.dropdown-header { padding: var(--cs-space-2) var(--cs-space-3); color: var(--cs-text-subtle); font-size: var(--cs-text-2xs); font-weight: var(--cs-weight-semibold); letter-spacing: var(--cs-tracking-wide); text-transform: uppercase; }

/* Single caret on dropdown toggles - never stacked with an icon glyph. */
.dropdown-toggle::after {
  margin-left: var(--cs-space-1);
  border-top: 4px solid currentColor;
  border-right: 4px solid transparent;
  border-left: 4px solid transparent;
  border-bottom: 0;
  vertical-align: middle;
}

/* =============================================================================
 * Pagination
 * ========================================================================== */
.pagination { gap: var(--cs-space-1); margin: 0; padding: var(--cs-space-3) var(--cs-space-4); }

.pagination .page-link {
  min-width: var(--cs-control-height-sm);
  height: var(--cs-control-height-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--cs-space-2);
  border: var(--cs-border-width) solid var(--cs-border) !important;
  border-radius: var(--cs-radius-control) !important;
  background: var(--cs-surface) !important;
  color: var(--cs-text-muted) !important;
  font-size: var(--cs-text-sm);
  font-weight: var(--cs-weight-medium);
}

.pagination .page-link:hover { background: var(--cs-surface-hover) !important; color: var(--cs-text) !important; }

.pagination .page-item.active .page-link {
  background: var(--cs-primary) !important;
  border-color: var(--cs-primary) !important;
  color: var(--cs-on-primary) !important;
}

.pagination .page-item.disabled .page-link { opacity: 0.45; background: var(--cs-surface-sunken) !important; }

/* =============================================================================
 * Alerts, toasts and SweetAlert
 * ========================================================================== */
.alert {
  padding: var(--cs-space-3) var(--cs-space-4);
  border: var(--cs-border-width) solid transparent;
  border-radius: var(--cs-radius-control);
  font-size: var(--cs-text-base);
}
.alert-primary { background: var(--cs-primary-soft); border-color: var(--cs-primary-border); color: var(--cs-primary-text); }
.alert-info, .alert-secondary { background: var(--cs-secondary-soft); border-color: var(--cs-secondary-border); color: var(--cs-secondary-text); }
.alert-success { background: var(--cs-tertiary-soft); border-color: var(--cs-tertiary-border); color: var(--cs-tertiary-text); }
.alert-warning { background: var(--cs-warning-soft); border-color: var(--cs-warning-border); color: var(--cs-warning-text); }
.alert-danger { background: var(--cs-danger-soft); border-color: var(--cs-danger-border); color: var(--cs-danger-text); }

.swal2-popup {
  border-radius: var(--cs-radius-surface) !important;
  background: var(--cs-surface) !important;
  color: var(--cs-text) !important;
  font-family: var(--cs-font-sans) !important;
  box-shadow: var(--cs-shadow-lg) !important;
}
.swal2-title { font-family: var(--cs-font-display) !important; font-size: var(--cs-text-lg) !important; color: var(--cs-text) !important; }
.swal2-html-container { font-size: var(--cs-text-base) !important; color: var(--cs-text-muted) !important; }

/* =============================================================================
 * Sidebar
 * ========================================================================== */
.sidebar-wrapper .sidebar,
.sidebar-dark .sidebar .sidebar-body,
.sidebar-dark .sidebar .sidebar-header {
  background: linear-gradient(170deg, var(--cs-sidebar-bg-from), var(--cs-sidebar-bg-to)) !important;
  border-color: var(--cs-sidebar-border) !important;
}

.sidebar .sidebar-header {
  display: flex;
  align-items: center;
  gap: var(--cs-space-2);
  padding: var(--cs-space-3) var(--cs-space-4);
  border-bottom: var(--cs-border-width) solid var(--cs-sidebar-border) !important;
}

.sidebar .sidebar-header .sidebar-brand {
  display: flex;
  align-items: center;
  gap: var(--cs-space-2);
  min-width: 0;
  margin: 0;
  font-family: var(--cs-font-display) !important;
  font-size: 1.3125rem !important;
  font-weight: var(--cs-weight-bold) !important;
  line-height: 1 !important;
  letter-spacing: -0.02em;
  color: var(--cs-sidebar-text-active) !important;
}

.sidebar .sidebar-header .cs-brand-mark { width: 40px; height: 40px; object-fit: contain; flex: 0 0 auto; }
/* The template colours every span inside .sidebar-brand with its own #6571ff.
   Qualified with .sidebar-dark to outrank it, so the wordmark uses the brand
   ramp: "Chef" in the sidebar's foreground, "Stack" in brand purple. */
.sidebar .sidebar-header .cs-brand-wordmark,
.sidebar-dark .sidebar .sidebar-header .sidebar-brand .cs-brand-wordmark {
  white-space: nowrap;
  color: var(--cs-sidebar-text-active);
}
.sidebar .sidebar-header .cs-brand-wordmark > span,
.sidebar-dark .sidebar .sidebar-header .sidebar-brand .cs-brand-wordmark > span {
  color: var(--cs-purple-300);
}
/* The brand takes the slack so the theme toggle and hamburger sit together
   at the trailing edge, in that order. */
.sidebar .sidebar-header .sidebar-brand { margin-right: auto; }
.sidebar .sidebar-header .sidebar-toggler { position: static; flex: 0 0 20px; margin-left: 0; }

body.sidebar-folded:not(.open-sidebar-folded) .sidebar .sidebar-header .sidebar-brand { display: none; }

/* Rows are inset from the sidebar edge so the hover highlight reads as a
   distinct pill with room around it, rather than a full-bleed band pressed
   against the text. */
.sidebar .sidebar-body .nav-item { margin: 0 6px 2px; }

.sidebar .sidebar-body .nav-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px !important;
  border-radius: var(--cs-radius-control);
  color: var(--cs-sidebar-text) !important;
  font-family: var(--cs-font-sans) !important;
  font-size: var(--cs-text-base) !important;
  font-weight: var(--cs-weight-medium) !important;
  transition: background-color var(--cs-transition-fast), color var(--cs-transition-fast);
}

/* The template lays these rows out with an absolutely positioned icon and a
   hardcoded `margin-left: 30px` on the label, at a specificity of six classes.
   Both are undone here with a matching selector chain so the row becomes a
   real flex line: icon in flow, `gap` owning the spacing. Leaving the icon
   out of flow is what made Log Out's icon land on top of its label, because
   an out-of-flow item still takes its static position from the flex line.

   Feather also swaps each <i> for an <svg> after layout, so the box is pinned
   explicitly - an unpinned SVG sizes itself and knocks the label off centre. */
.sidebar .sidebar-body .nav .nav-item .nav-link .link-icon,
.sidebar .sidebar-body .nav .nav-item .nav-link > svg {
  position: static;
  display: block;
  width: var(--cs-icon-size);
  height: var(--cs-icon-size);
  min-width: var(--cs-icon-size);
  flex: 0 0 var(--cs-icon-size);
  margin: 0;
  stroke-width: 2;
}

/* 240px is a tight sidebar and the longest labels ("Create Assembled Recipe")
   were overflowing it. Dropping the template's 30px indent in favour of the
   flex gap reclaims the width; min-width:0 plus ellipsis keeps any future
   long label inside the rail instead of running past the edge. */
.sidebar .sidebar-body .nav .nav-item .nav-link .link-title {
  margin-left: 0;
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: var(--cs-text-base);
  line-height: 1.2;
}

.sidebar .sidebar-body .nav-link:hover,
.sidebar .sidebar-body .nav-item.active > .nav-link,
.sidebar .sidebar-body .nav-link.active {
  background: var(--cs-sidebar-active-bg) !important;
  color: var(--cs-sidebar-text-active) !important;
}

/* Section headings need more air now that the rows are inset. */
.sidebar .sidebar-body .nav-category {
  margin: var(--cs-space-4) var(--cs-space-3) var(--cs-space-1);
  color: var(--cs-text-subtle) !important;
  font-size: var(--cs-text-2xs) !important;
  font-weight: var(--cs-weight-semibold) !important;
  letter-spacing: var(--cs-tracking-wide);
  text-transform: uppercase;
}
.sidebar .sidebar-body .nav > .nav-category:first-child { margin-top: var(--cs-space-2); }

/* The theme switch is an ordinary nav row (it sits above Profile), so it only
   needs the chrome a <button> does not inherit from <a>. */
.sidebar .sidebar-body .nav-link.cs-theme-toggle {
  width: calc(100% - 0px);
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
  font: inherit;
}

/* Sign out reads as a quiet destructive action, not an alarm. */
.sidebar .sidebar-body .nav .cs-signout {
  margin-top: var(--cs-space-3);
  border: var(--cs-border-width) solid rgba(248, 113, 113, 0.32);
  border-radius: var(--cs-radius-control);
  background: rgba(248, 113, 113, 0.1);
  color: #fca5a5 !important;
}
.sidebar .sidebar-body .nav .cs-signout:hover {
  background: rgba(248, 113, 113, 0.2) !important;
  color: #fecaca !important;
}

/* =============================================================================
 * Page header - the shared title/actions strip for index and detail pages
 * ========================================================================== */
.cs-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cs-space-4);
  flex-wrap: wrap;
  margin-bottom: var(--cs-space-5);
}

.cs-page-header__title {
  margin: 0;
  font-family: var(--cs-font-display);
  font-size: var(--cs-text-xl);
  font-weight: var(--cs-weight-semibold);
  color: var(--cs-text);
}

.cs-page-header__subtitle {
  margin: var(--cs-space-1) 0 0;
  color: var(--cs-text-muted);
  font-size: var(--cs-text-sm);
}

/* -----------------------------------------------------------------------------
 * Breadcrumbs
 * -----------------------------------------------------------------------------
 * A locator, not a headline: small uppercase tracking in the faintest text
 * tone, so it sits above the page title without competing with it.
 *
 * The weight runs opposite to the usual convention here - ancestors are bold
 * and the current page is not. The trail is read as "where can I go back to",
 * and the ancestors are the only parts that are actually clickable.
 * -------------------------------------------------------------------------- */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0;
  padding: 0;
  margin-bottom: var(--cs-space-3);
  background: transparent;
  border: 0;
  font-family: var(--cs-font-sans);
  font-size: var(--cs-text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: var(--cs-leading-tight);
}

.breadcrumb-item {
  color: var(--cs-text-subtle);
  font-weight: var(--cs-weight-regular);
}

/* Ancestors: the links. Bold, and never underlined - the hover colour is the
   affordance, which keeps the strip from looking like body copy with links. */
.breadcrumb-item a {
  color: var(--cs-text-subtle);
  font-weight: var(--cs-weight-semibold);
  text-decoration: none !important;
  transition: color var(--cs-transition-fast);
}

.breadcrumb-item a:hover,
.breadcrumb-item a:focus-visible {
  color: var(--cs-primary);
  text-decoration: none !important;
}

/* Current page: present but recessive. */
.breadcrumb-item.active {
  color: var(--cs-text-muted);
  font-weight: var(--cs-weight-regular);
}

/* Bootstrap 4 and 5 disagree on how the separator is injected, so both the
   `::before` pseudo-element and any `--bs-*` variable route are covered. */
.breadcrumb-item + .breadcrumb-item {
  padding-left: var(--cs-space-2);
}

.breadcrumb-item + .breadcrumb-item::before {
  content: "/";
  padding-right: var(--cs-space-2);
  color: var(--cs-border-strong);
  font-weight: var(--cs-weight-regular);
}

/* =============================================================================
 * Bulk action toolbar
 * =============================================================================
 * The "Delete selected (n)" strip above a grid. Previously a bare `p-3
 * border-bottom` form, so the button sat flush against the card edge and the
 * table below it with nothing separating the action from the data.
 * ========================================================================== */
/* The .cs-bulk-toolbar <form> WRAPS THE WHOLE GRID - it opens above the table
 * and closes below it, because the row checkboxes have to post with it. So it
 * carries no chrome of its own: all the visible strip, and all the collapsing,
 * belongs to .cs-bulk-toolbar__row. Styling the form would band the entire
 * table, and collapsing it would hide the data. */
.cs-bulk-toolbar {
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
}

.cs-bulk-toolbar__row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--cs-space-3);
  padding: var(--cs-space-3) var(--cs-space-4);
  background: var(--cs-surface-subtle);
  border-bottom: var(--cs-border-width) solid var(--cs-border);
}

/* The help text is guidance, not a label on the button - give it room. */
.cs-bulk-toolbar__row > .text-muted,
.cs-bulk-toolbar__row > .small {
  margin: 0;
  color: var(--cs-text-muted);
  font-size: var(--cs-text-xs);
}

@media (max-width: 575.98px) {
  .cs-bulk-toolbar__row { align-items: stretch; flex-direction: column; }
}

/* -----------------------------------------------------------------------------
 * Reveal on selection
 * -----------------------------------------------------------------------------
 * The strip used to render permanently and merely disable its button, so every
 * grid paid a row of vertical space for an action that is unavailable most of
 * the time. It now stays collapsed until the first row is ticked.
 *
 * Collapsed is the CSS default rather than a class, so a grid whose JavaScript
 * never runs shows nothing at all instead of showing a dead button.
 *
 * `visibility: hidden` rather than `display: none`: it takes the submit button
 * out of the tab order while leaving the element in the document, so the
 * transition has something to animate and the form keeps its structure.
 * -------------------------------------------------------------------------- */
.cs-bulk-toolbar__row {
  overflow: hidden;
  max-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-bottom-width: 0;
  visibility: hidden;
  transition: max-height var(--cs-transition-base),
    padding var(--cs-transition-base),
    visibility var(--cs-transition-base);
}

/* A fixed cap rather than `auto`: max-height is only interpolatable between
   lengths, and one control plus its padding cannot exceed this. */
.cs-bulk-toolbar__row.is-active {
  max-height: 120px;
  padding-top: var(--cs-space-3);
  padding-bottom: var(--cs-space-3);
  border-bottom-width: var(--cs-border-width);
  visibility: visible;
}

@media (prefers-reduced-motion: reduce) {
  .cs-bulk-toolbar__row { transition: none; }
}

/* =============================================================================
 * Footer
 * =============================================================================
 * The app-shell colophon. The NobleUI template gave it its own font stack and
 * a near-body text colour, so it read as page content; it is a colophon and
 * should recede. The dark marketing footer on the public dashboard
 * (_dashboard_footer.php) is a different surface and keeps its own look.
 * ========================================================================== */
.cs-footer {
  gap: var(--cs-space-2);
  padding: var(--cs-space-4) 0;
  background: transparent;
  border-top: var(--cs-border-width) solid var(--cs-border);
}

.cs-footer .cs-footer__line {
  color: var(--cs-text-subtle) !important;
  font-family: var(--cs-font-sans) !important;
  font-size: var(--cs-text-xs) !important;
  font-weight: var(--cs-weight-regular) !important;
  line-height: var(--cs-leading-normal);
}

/* The credit link stays as quiet as the line it sits in; the underline only
   appears on hover, so the strip reads as one sentence at rest. */
.cs-footer .cs-footer__line a {
  color: var(--cs-text-subtle);
  text-decoration: none;
  transition: color var(--cs-transition-fast);
}

.cs-footer .cs-footer__line a:hover,
.cs-footer .cs-footer__line a:focus-visible {
  color: var(--cs-primary);
  text-decoration: underline;
}

/* =============================================================================
 * Resizable panel
 * =============================================================================
 * A container the user can drag taller to see more of a long list without
 * scrolling inside it. Used by the ingredients table and the Methodology of
 * Final Construction table.
 *
 * This replaces a one-off implementation that only the ingredients table had.
 * Its clamp read the max height with
 *   parseFloat(getComputedStyle(el).getPropertyValue('--...-max-height'))
 * where the token's value is the literal string `min(760px, 80vh)`. Custom
 * properties are not resolved unless registered, so parseFloat returned NaN,
 * the clamp returned NaN, and the first pixel of drag wrote `NaNpx` - which is
 * invalid at computed-value time, so the height fell back to auto and the
 * panel collapsed. The limits are plain pixel tokens here for that reason, and
 * design-system.js resolves the viewport cap itself.
 *
 * Sizes are authored as tokens on the wrapper so a page can override one
 * instance without touching this file.
 * ========================================================================== */
.cs-resizable {
  --cs-resizable-height: 320px;
  --cs-resizable-min: 200px;
  --cs-resizable-max: 760px;
  position: relative;
  margin-bottom: var(--cs-space-4);
}

.cs-resizable__panel {
  height: var(--cs-resizable-height) !important;
  min-height: var(--cs-resizable-min);
  /* No max-height: the drag is already clamped, and a CSS cap here would
     silently disagree with the handle's own limit. */
}

.cs-resizable__handle {
  position: absolute;
  z-index: 5;
  left: 0;
  right: 0;
  bottom: calc(var(--cs-space-4) * -1);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: var(--cs-space-4);
  padding: 0;
  border: 0;
  background: transparent;
  cursor: ns-resize;
  /* Without this the browser claims the gesture for scrolling on touch and
     pointermove never fires. */
  touch-action: none;
}

/* The grip: three hairlines, same visual weight as a table rule so it reads as
   part of the panel's edge rather than as a control sitting on top of it. */
.cs-resizable__handle span,
.cs-resizable__handle span::before,
.cs-resizable__handle span::after {
  display: block;
  width: 28px;
  height: 2px;
  border-radius: 2px;
  background: var(--cs-border-strong);
  content: "";
  transition: background-color var(--cs-transition-fast);
}

.cs-resizable__handle span::before { margin-top: -5px; }
.cs-resizable__handle span::after { margin-top: 8px; }

.cs-resizable__handle:hover span,
.cs-resizable__handle:hover span::before,
.cs-resizable__handle:hover span::after,
.cs-resizable__handle:focus-visible span,
.cs-resizable__handle:focus-visible span::before,
.cs-resizable__handle:focus-visible span::after {
  background: var(--cs-primary);
}

.cs-resizable__handle:focus-visible {
  outline: none;
  box-shadow: var(--cs-focus-ring);
  border-radius: var(--cs-radius-control);
}

/* While dragging, every element under the cursor must agree about the cursor
   and refuse to select text, or the pointer flickers across table cells. */
.cs-resizable.is-resizing,
.cs-resizable.is-resizing * {
  cursor: ns-resize !important;
  user-select: none !important;
}

/* =============================================================================
 * Search
 * =============================================================================
 * Two components, one family. Both are rendered from shared partials
 * (views/_shared/_search_bar.php and _filter_input.php) so their markup cannot
 * drift again; everything here is geometry those partials rely on.
 * ========================================================================== */

/* Submit-style search bar: field + attached submit button. The .input-group
   rules already collapse the inner corners, so the pair reads as one control
   at exactly one control height. */
.cs-search-form { width: 100%; }
.cs-search { width: 100%; }
.cs-search > .form-control { min-width: 0; }

/* The attached submit is square and flush with the field. */
.cs-search > .btn {
  flex: 0 0 auto;
  width: var(--cs-control-height-md);
  padding: 0 !important;
}

/* Live filter: leading magnifier, no submit. The icon cell is visually part of
   the field, so it drops its own right border and shares the background. */
.cs-filter-input > .input-group-text {
  border-right: 0 !important;
  background: var(--cs-surface) !important;
  color: var(--cs-text-subtle) !important;
  padding-right: var(--cs-space-1) !important;
}
.cs-filter-input > .form-control {
  border-left: 0 !important;
  padding-left: var(--cs-space-2) !important;
}
/* Focus has to move onto the whole group, not just the input, or the ring
   stops halfway across the control. */
.cs-filter-input:focus-within > .input-group-text,
.cs-filter-input:focus-within > .form-control {
  border-color: var(--cs-primary) !important;
}
.cs-filter-input:focus-within { border-radius: var(--cs-radius-control); box-shadow: var(--cs-focus-ring); }
.cs-filter-input > .form-control:focus { box-shadow: none !important; }

/* Kill the native clear affordance; it renders differently per browser. */
.cs-search input[type="search"]::-webkit-search-decoration,
.cs-search input[type="search"]::-webkit-search-results-button,
.cs-search input[type="search"]::-webkit-search-results-decoration { display: none; }

/* =============================================================================
 * Number stepper
 * =============================================================================
 * Replaces the browser's stacked up/down spinner with - and + buttons.
 * ui-assets/js/design-system.js wraps eligible inputs at runtime; this is only
 * the presentation.
 *
 * Inputs inside a table are deliberately NOT wrapped (people type a value into
 * a nutrient grid rather than stepping to it, and three controls per cell
 * would not fit) - there the native spinner is simply hidden.
 * ========================================================================== */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

.cs-stepper {
  display: inline-flex;
  align-items: stretch;
  width: 100%;
  isolation: isolate;
}

.cs-stepper > .form-control {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  border-radius: 0 !important;
  margin: 0 -1px;
  position: relative;
  z-index: 1;
}

.cs-stepper__btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--cs-control-height-md);
  height: var(--cs-control-height-md);
  padding: 0;
  border: var(--cs-border-width) solid var(--cs-border-strong);
  background: var(--cs-surface-subtle);
  color: var(--cs-text-muted);
  font-size: var(--cs-icon-size);
  line-height: 1;
  cursor: pointer;
  user-select: none;
  transition: background-color var(--cs-transition-fast), color var(--cs-transition-fast);
}
.cs-stepper__btn:hover:not(:disabled) { background: var(--cs-primary-soft); color: var(--cs-primary-text); }
.cs-stepper__btn:focus-visible { outline: 2px solid var(--cs-primary); outline-offset: -2px; z-index: 2; }
.cs-stepper__btn:disabled { opacity: 0.45; cursor: not-allowed; }
.cs-stepper__btn--down { border-radius: var(--cs-radius-control) 0 0 var(--cs-radius-control); }
.cs-stepper__btn--up { border-radius: 0 var(--cs-radius-control) var(--cs-radius-control) 0; }

.cs-stepper--sm .cs-stepper__btn { width: var(--cs-control-height-sm); height: var(--cs-control-height-sm); font-size: var(--cs-icon-size-sm); }

/* -----------------------------------------------------------------------------
 * Step-order controls
 * -----------------------------------------------------------------------------
 * The STEP NO. column in the Methodology table. These buttons reorder the row
 * rather than edit a value, but they now carry minus/plus glyphs because that
 * is how the column reads: minus moves the step to a lower number, plus to a
 * higher one. Styled as a stepper so the two controls that look alike also
 * behave alike.
 *
 * The markup keeps its .btn classes (it is generated from four places, three
 * of them JavaScript row templates), so this overrides the button geometry
 * rather than replacing the classes.
 * -------------------------------------------------------------------------- */
.method-sequence-control {
  display: inline-flex !important;
  align-items: center;
  gap: 0 !important;
  isolation: isolate;
}

.method-sequence-control .sequence-up,
.method-sequence-control .sequence-down {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--cs-control-height-sm) !important;
  height: var(--cs-control-height-sm) !important;
  min-height: var(--cs-control-height-sm) !important;
  padding: 0 !important;
  border: var(--cs-border-width) solid var(--cs-border-strong) !important;
  background: var(--cs-surface-subtle) !important;
  color: var(--cs-text-muted) !important;
  font-size: var(--cs-icon-size-sm) !important;
}

.method-sequence-control .sequence-up:hover,
.method-sequence-control .sequence-down:hover {
  background: var(--cs-primary-soft) !important;
  color: var(--cs-primary-text) !important;
}

/* Minus sits left of the number, plus right, mirroring the number stepper. */
.method-sequence-control .sequence-up {
  border-radius: var(--cs-radius-control) 0 0 var(--cs-radius-control) !important;
}
.method-sequence-control .sequence-down {
  border-radius: 0 var(--cs-radius-control) var(--cs-radius-control) 0 !important;
}

/* Disabled at the ends of the list: the first step cannot move earlier and the
   last cannot move later. Muted rather than hidden, so the control keeps its
   shape and the row does not reflow as steps are reordered. */
.method-sequence-control .sequence-up:disabled,
.method-sequence-control .sequence-down:disabled {
  background: var(--cs-surface) !important;
  border-color: var(--cs-border) !important;
  color: var(--cs-text-subtle) !important;
  opacity: 1 !important;
  cursor: default;
  pointer-events: none;
}

/* The number reads as the stepper's field: same height, shared hairline. */
.method-sequence-control .sequence-display {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  height: var(--cs-control-height-sm);
  margin: 0 -1px;
  padding: 0 var(--cs-space-2);
  border: var(--cs-border-width) solid var(--cs-border-strong);
  background: var(--cs-surface);
  color: var(--cs-text);
  font-size: var(--cs-text-sm);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* -----------------------------------------------------------------------------
 * Product Constraints cards
 * -----------------------------------------------------------------------------
 * The three constraint sets used to sit three-across. The mandatory set now
 * has its own full-width row so it reads as the primary one, with optional and
 * stagnant sharing the row beneath it.
 *
 * Its table is only five narrow columns (Nutrient, Min, Max, Solved, Info), so
 * letting it stretch the full width of a wide page makes it look broken rather
 * than important. Cap it and leave it left-aligned inside the card.
 * -------------------------------------------------------------------------- */
.cs-constraint-col--full .table-product-specification {
  max-width: 720px;
}

/* =============================================================================
 * Motion
 * =============================================================================
 * Deliberately restrained: opacity and small translations at 120-180ms, no
 * scale or bounce. This is a tool people use all day. Everything here is
 * switched off by the prefers-reduced-motion block in base.css.
 * ========================================================================== */
@keyframes cs-enter {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
@keyframes cs-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Page transition: the main content area rises in on load. */
.page-content,
.main-content { animation: cs-enter 180ms cubic-bezier(0.4, 0, 0.2, 1) both; }

/* Pjax swaps replace only the grid, so the page-level animation never fires -
   this keeps sorting, searching and paging from snapping. */
.cs-pjax-enter { animation: cs-fade-in 140ms ease-out both; }

/* Overlays. */
.modal.show .modal-dialog { animation: cs-enter 160ms cubic-bezier(0.4, 0, 0.2, 1) both; }
.dropdown-menu.show { animation: cs-enter 120ms cubic-bezier(0.4, 0, 0.2, 1) both; }
.swal2-popup.swal2-show { animation: cs-enter 160ms cubic-bezier(0.4, 0, 0.2, 1) both; }

/* Press feedback. A 1px nudge is enough to feel connected to the click. */
.btn:active:not(:disabled):not(.disabled),
.cs-stepper__btn:active:not(:disabled) { transform: translateY(1px) !important; }

/* Row and tab hover already transition their colours; give the sort caret and
   the table row the same easing so a grid feels coherent.

   This rule is later in the file than the tab block and equally specific, so it
   *replaces* the transition declared there - box-shadow has to be in this list
   or the active tab's chip would snap in rather than fade. */
.grid-view tbody tr,
.nav-tabs .nav-link,
.dropdown-item,
.pagination .page-link,
.sidebar .sidebar-body .nav-link {
  transition: background-color var(--cs-transition-fast),
    color var(--cs-transition-fast), border-color var(--cs-transition-fast),
    box-shadow var(--cs-transition-fast);
}

/* =============================================================================
 * Responsive
 * ========================================================================== */
@media (max-width: 767.98px) {
  /* `overflow-y: hidden` kills the vertical scrollbar the horizontal scroll
     would otherwise produce, but it also clips anything drawn outside the
     padding box - including the active chip's lift. The top padding gives that
     lift room; the chip's border still carries its edge where the downward
     shadow is clipped at the rail. */
  .nav-tabs { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; padding: var(--cs-space-1) 0 0 !important; }
  .nav-tabs::-webkit-scrollbar { height: 0; }
  .nav-tabs .nav-link { white-space: nowrap; padding: var(--cs-space-3) var(--cs-space-3) !important; }

  .card-body, .modal-body { padding: var(--cs-space-4); }
  .card-header, .card-footer, .modal-header, .modal-footer { padding: var(--cs-space-3) var(--cs-space-4); }

  .table, .grid-view .table { font-size: var(--cs-text-sm); }
  .table thead th, .table tbody td { padding: var(--cs-space-2); }

  .cs-page-header { align-items: flex-start; flex-direction: column; }
}
