html[data-turbo-visit-direction="forward"]::view-transition-old(sidebar):only-child {
    animation: slide-to-right 0.5s ease-out;
}
.dt-container {
    overflow: auto;
}

:root {
    --choices-primary-color: var(--bs-primary);
    --choices-highlight-color: var(--bs-primary);
    --choices-text-color: #fff;
}

/* Settings → Crons: a small status indicator next to each job, echoing its
   badge colour. Only "running" pulses — motion is reserved for the one
   moment it actually means something, a job executing right now. */
.cron-status-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    flex: none;
    border-radius: 50%;
    background-color: currentColor;
}

.cron-status-dot--running {
    animation: cron-status-pulse 1.8s ease-out infinite;
}

@keyframes cron-status-pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(var(--bs-info-rgb), 0.5);
    }
    100% {
        box-shadow: 0 0 0 6px rgba(var(--bs-info-rgb), 0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .cron-status-dot--running {
        animation: none;
    }
}

/* ── Admin filter bars ───────────────────────────────────────────────────
   Stock Status, Stock Movements and Products share one filter bar. The
   fields hold different controls — date inputs, text inputs and Choices
   multi-selects, which are taller and append their own status line — so
   hand-tuned Bootstrap columns left the row ragged and clipped the
   narrowest field.

   Two rules keep it proportional instead:
   1. an auto-fit grid, so every field is exactly as wide as every other
      and the row wraps honestly rather than by column arithmetic;
   2. a fixed reserved strip at the foot of each field, with the status
      line positioned into it, so a field showing tags and a field showing
      a date are the same height and the labels share one baseline. */
.filter-bar {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: 0.5rem 1rem;
    align-items: start;
    margin-bottom: 1.25rem;
}

.filter-field {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    /* Room for the status strip, whether or not this field has one. */
    padding-bottom: 1.4rem;
}

/* Labels are chrome, not content: a utility register that never competes
   with the data in the table below. */
.filter-field > .form-label {
    margin-bottom: 0.3rem;
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--bs-secondary-color, #6c757d);
}

.filter-field > .form-control,
.filter-field > .form-select {
    min-height: 2.125rem;
}

/* Choices is themed through its own custom properties rather than by
   out-specifying its selectors. Its stylesheet loads after this one via the
   importmap, so equal-specificity overrides lost — which is why the open
   dropdown kept its 41px rows and 14px type while the closed control sat at
   27px with 10.4px type. The open state read as a different, heavier
   component than the closed one; that mismatch was the real disproportion.

   Setting the variables on .filter-field scopes the compact treatment to the
   filter bars and leaves the roomier user-access selects alone. */
.filter-field .choices {
    margin-bottom: 0;

    /* This theme's root is 12.8px, not 16px, so rem values run small: 0.9rem
       lands at ~11.5px — a shade under the app's body text, which is right
       for chrome, and identical open or closed. */
    --choices-font-size-md: 0.9rem;
    --choices-font-size-sm: 0.8rem;
    --choices-dropdown-item-padding: 0.25rem 0.5rem;
    --choices-inner-padding: 0.1rem 0.25rem 0.05rem;
    --choices-inner-one-padding: 0.1rem 0.25rem;
    --choices-multiple-item-padding: 0.05rem 0.35rem;
    --choices-multiple-item-margin: 0.1rem;
    --choices-input-padding: 0 0 0 0.1rem;
    --choices-input-height: 1.15rem;
    --choices-border-radius: var(--bs-border-radius);
    /* Square-ish chips, not pills: the tags are data, not badges. And quiet
       ones — a solid brand-coloured block per selection turns a filter bar
       into a wall of buttons. The selection is stated in words underneath. */
    --choices-border-radius-item: 3px;
    --choices-primary-color: var(--bs-secondary-bg, #eceef1);
    --choices-item-color: var(--bs-body-color, #1f2937);
    --choices-bg-color: var(--bs-body-bg);
    --choices-bg-color-dropdown: var(--bs-body-bg);
    --choices-keyline-color: var(--bs-border-color);
    --choices-text-color: var(--bs-body-color);
    --choices-highlighted-color: var(--bs-tertiary-bg);
}

/* The control is a fixed height, not a growing one. Chips stack as they are
   picked, so a field with three selections was three rows tall while its
   neighbour stayed one — the row went ragged again exactly as it did before
   the grid, only now driven by content instead of column widths. Pinning the
   height and scrolling inside keeps every field identical however much is
   selected; the status strip underneath always states the count in words, so
   nothing is hidden by the crop. */
.filter-field .choices__inner {
    height: 2.125rem;
    overflow: hidden;
    border-color: var(--bs-border-color);
}

/* Chips run in a single scrolling line rather than wrapping. Wrapping is what
   made the fields grow to different heights; scrolling sideways keeps every
   field exactly one row tall no matter how many values are picked, and every
   chip stays reachable — a vertical crop would have left half a chip showing
   and the rest unreachable. */
.filter-field .choices__list--multiple {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    overflow-x: auto;
    scrollbar-width: none;
}

.filter-field .choices__list--multiple::-webkit-scrollbar {
    display: none;
}

.filter-field .choices__list--multiple .choices__item {
    flex: none;
}

/* A filter list is a utility, not a menu of content: show a handful of rows
   and let the search box do the work on long lists.

   The extra .choices in the selector is deliberate. Choices caps the list
   with two equally specific rules — .choices__list--dropdown .choices__list
   and .choices__list[aria-expanded] .choices__list, the second being two
   classes plus an attribute — so a three-class override merely ties and
   loses on load order. Four wins. */
.filter-field .choices .choices__list--dropdown .choices__list,
.filter-field .choices .choices__list[aria-expanded] .choices__list {
    max-height: 13rem;
}

/* The dropdown is not bound to its field's width — store and brand names run
   longer than a grid cell — but it stays modest and never runs off screen. */
.filter-field .choices__list--dropdown {
    width: max-content;
    min-width: 100%;
    max-width: min(22rem, 80vw);
    z-index: 30;
}

.filter-field .choices__list--dropdown .choices__item {
    white-space: normal;
    overflow-wrap: break-word;
}

/* Keep the open field above its neighbours so the list is never clipped. */
.filter-field .choices.is-open {
    position: relative;
    z-index: 30;
}
/* The status strip: always present, always the same height, and always
   stating what the field currently means — "All brands" when nothing is
   picked (the one genuinely non-obvious state) or "3 of 412 brands". */
.filter-field .choices-bulk {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    gap: 0.4rem;
    margin: 0;
    font-size: 0.68rem;
    line-height: 1.4;
    white-space: nowrap;
    overflow: hidden;
}

.filter-field .choices-bulk .choices-status {
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--bs-secondary-color, #6c757d);
}

.filter-field .choices-bulk .choices-status-all {
    color: var(--bs-success);
    font-weight: 600;
}

.filter-field .choices-bulk .btn-link {
    font-size: 0.68rem;
    text-decoration: none;
    vertical-align: baseline;
}

.filter-field .choices-bulk .btn-link:hover {
    text-decoration: underline;
}

/* The action cluster sits on the control baseline, not the label's. */
.filter-actions {
    display: flex;
    align-items: flex-end;
    gap: 0.5rem;
    padding-bottom: 1.4rem;
}

.filter-actions .btn {
    min-height: 2.125rem;
}

@media (max-width: 575.98px) {
    .filter-bar {
        grid-template-columns: 1fr;
    }
}


/* ── Size grid (waist × length) ──────────────────────────────────────────
   Denim sizes are coordinates. Flattened onto one axis a jeans style runs
   to ninety columns of mostly empty cells and the shape a buyer reads —
   which lengths are gone across every waist — disappears. The grid puts
   waist across and length down; the margins carry the answer.

   Occupied cells should read as a shape, so empty cells recede to a middot
   and the margins are the only emphasised numbers besides the stock
   itself. */
.size-grid-block + .size-grid-block {
    margin-top: 1.5rem;
}

.size-grid-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.4rem;
}

.size-grid-total {
    font-size: 0.8125rem;
    color: var(--bs-secondary-color, #6c757d);
    font-variant-numeric: tabular-nums;
}

.size-grid {
    width: auto;
    min-width: 0;
    font-variant-numeric: tabular-nums;
}

.size-grid th,
.size-grid td {
    padding: 0.3rem 0.6rem;
    white-space: nowrap;
}

/* The axes are chrome: they label the grid, they are not data in it. */
.size-grid thead th,
.size-grid .size-grid-axis {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--bs-secondary-color, #6c757d);
}

.size-grid thead th:not(.size-grid-axis) {
    text-align: right;
}

.size-grid tbody th.size-grid-axis {
    text-align: left;
}

.size-grid-empty {
    color: var(--bs-tertiary-color, #adb5bd);
}

/* Margins: the totals a buyer actually scans. */
.size-grid .size-grid-margin {
    font-weight: 600;
    background-color: var(--bs-tertiary-bg);
}

.size-grid .size-grid-corner {
    color: var(--bs-emphasis-color);
}

.size-grid tfoot td,
.size-grid tfoot th {
    border-top: 1px solid var(--bs-border-color);
}
