/* AGRM overrides on top of Tabler.
 *
 * Kept deliberately short. Tabler is a Bootstrap superset and does almost
 * everything we need; anything added here is a decision that the theme could
 * not make for us, and each one says why.
 */

/* ================================================================== *
 * Design tokens - DESIGN.md sections 4 to 6
 * ================================================================== *
 *
 * Every colour, size and type step the interface uses is named here and
 * nowhere else. A raw hex value anywhere below this block is a decision made
 * outside the system, which is how "approved" once came out green on one
 * screen and blue on the next. core/test_design_tokens.py fails on one.
 *
 * The values are the ones DESIGN.md publishes, and their contrast was measured
 * rather than estimated - body text holds 7:1 in both themes, because eight
 * hours in front of it is the real test. The same test checks that this file
 * and DESIGN.md still agree.
 */

:root {
  /* Neutrals. Text is never pure black: maximum contrast tires the eye over
   * a shift, and #1d242c still clears 15:1 on white. */
  --color-bg-app: #f4f5f7;
  --color-bg-surface: #ffffff;
  --color-bg-subtle: #eef0f3;
  --color-bg-sidebar: #eceef1;
  --color-border: #e1e4e8;
  --color-border-input: #8a94a0;
  --color-text: #1d242c;
  --color-text-secondary: #4b5663;
  --color-text-muted: #5f6b78;
  --color-text-disabled: #9aa3ad;
  --color-overlay: rgba(17, 19, 22, 0.45);

  /* The one accent. Actions, focus, selection, links - never a status. */
  --color-accent: #3454b4;
  --color-accent-hover: #2a4596;
  --color-accent-subtle: #e9edf8;
  --color-on-accent: #ffffff;

  /* Six status states and only six. Tinted background, dark text. Consumed by
   * the status badge from step (c) onwards; defined now so nothing is ever
   * coloured by hand in the meantime. */
  --status-draft-bg: #eceef1;
  --status-draft-fg: #48525e;
  --status-released-bg: #e2f0ef;
  --status-released-fg: #1d5b57;
  --status-in-progress-bg: #f8eed8;
  --status-in-progress-fg: #6f4d0e;
  --status-blocked-bg: #f7e2df;
  --status-blocked-fg: #932b22;
  --status-completed-bg: #e3efe5;
  --status-completed-fg: #2c6436;
  --status-cancelled-bg: transparent;
  --status-cancelled-fg: #5f6b78;
  --status-cancelled-border: #c3c9d0;

  /* Feedback reuses the status families. The destructive fill appears only
   * inside a confirmation dialog. */
  --color-error-text: #932b22;
  --color-error-border: #b8473d;
  --color-danger: #a8352b;
  --color-danger-hover: #8c2b22;
  --color-on-danger: #ffffff;

  /* Orange belongs to the QA banner and to nothing else. It used to be the
   * same #f76707 as every in-flight status badge. */
  --color-env-qa: #b4501a;

  /* The rgb triplets Tabler builds its tints, text opacities and focus
   * shadows from. They cannot be derived from a hex value in CSS, so they are
   * written out - and a test fails if one ever stops matching its colour,
   * because a stale triplet leaves the old blue in every focus ring while the
   * buttons change. */
  --color-accent-rgb: 52, 84, 180;
  --color-accent-hover-rgb: 42, 69, 150;
  --color-accent-subtle-rgb: 233, 237, 248;
  --color-bg-app-rgb: 244, 245, 247;
  --color-text-rgb: 29, 36, 44;
  --color-text-secondary-rgb: 75, 86, 99;
  --color-text-muted-rgb: 95, 107, 120;
  --color-env-qa-rgb: 180, 80, 26;
  --color-danger-rgb: 168, 53, 43;
  --status-completed-fg-rgb: 44, 100, 54;
  --status-completed-bg-rgb: 227, 239, 229;
  --status-in-progress-fg-rgb: 111, 77, 14;
  --status-in-progress-bg-rgb: 248, 238, 216;
  --status-blocked-bg-rgb: 247, 226, 223;

  /* One UI font. Segoe UI on the plant's Windows desktops: ClearType-hinted,
   * crisp at 12-13px where a web font blurs, full Turkish coverage, and nothing
   * added to static/. */
  --font-ui: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;

  /* Five type steps. */
  --text-caption: 0.75rem;     /* 12px */
  --text-caption-lh: 1rem;     /* 16px */
  --text-table: 0.8125rem;     /* 13px */
  --text-table-lh: 1.25rem;    /* 20px */
  --text-body: 0.875rem;       /* 14px */
  --text-body-lh: 1.25rem;     /* 20px */
  --text-heading: 1rem;        /* 16px */
  --text-heading-lh: 1.5rem;   /* 24px */
  --text-title: 1.25rem;       /* 20px */
  --text-title-lh: 1.75rem;    /* 28px */
  --weight-regular: 400;
  --weight-strong: 600;

  /* The 4px grid. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;

  /* Fixed sizes. A field is exactly a row high, so an input inside a table
   * row, or beside a button, lines up and nothing jumps. */
  --size-row: 32px;
  --size-field: 32px;
  --size-page-gutter: 24px;
  --size-topbar: 48px;
  --size-sidebar: 240px;
  --size-sidebar-collapsed: 56px;
  --size-badge: 20px;
  --size-badge-header: 24px;
  --size-tabbar: 40px;
  /* Short values get short fields (DESIGN.md 8.7): maximum widths, so a
   * narrow column still shrinks them. */
  --size-field-date: 128px;
  --size-field-number: 144px;
  --size-field-time: 112px;
  --size-select-column: 40px;

  /* The type-ahead lookup's 12px spinner (DESIGN.md 8.7), drawn in the muted
   * text colour. An image because an input cannot hold a pseudo-element. The
   * still ring is for reduced motion. */
  --image-lookup-spinner: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Ccircle cx='6' cy='6' r='4.5' fill='none' stroke='%235f6b78' stroke-width='1.5' stroke-dasharray='20 9'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 6 6' to='360 6 6' dur='0.8s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/svg%3E");
  --image-lookup-spinner-still: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Ccircle cx='6' cy='6' r='4.5' fill='none' stroke='%235f6b78' stroke-width='1.5' stroke-dasharray='20 9'%3E%3C/circle%3E%3C/svg%3E");
  --size-row-touch: 44px;
  --size-field-touch: 44px;
  --radius: 4px;
}

/* Dark tokens. Defined now so that nothing gets hardcoded that would block
 * them; whether to ship a switch is still the owner's decision (DESIGN.md
 * section 13, decision 1). Nothing sets data-bs-theme="dark" on <html> yet.
 *
 * Scoped to :root rather than to any [data-bs-theme=dark] element, because the
 * current sidebar carries that attribute on its own and must not be repainted
 * by a theme nobody switched on. */
:root[data-bs-theme="dark"] {
  --color-bg-app: #111316;
  --color-bg-surface: #171a1e;
  --color-bg-subtle: #1e2227;
  --color-bg-sidebar: #121417;
  --color-border: #272c32;
  --color-border-input: #5f6873;
  --color-text: #dde2e7;
  --color-text-secondary: #aab3bd;
  --color-text-muted: #8c96a1;
  --image-lookup-spinner: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Ccircle cx='6' cy='6' r='4.5' fill='none' stroke='%238c96a1' stroke-width='1.5' stroke-dasharray='20 9'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 6 6' to='360 6 6' dur='0.8s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/svg%3E");
  --image-lookup-spinner-still: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Ccircle cx='6' cy='6' r='4.5' fill='none' stroke='%238c96a1' stroke-width='1.5' stroke-dasharray='20 9'%3E%3C/circle%3E%3C/svg%3E");
  --color-text-disabled: #5a636d;
  --color-overlay: rgba(0, 0, 0, 0.6);

  --color-accent: #8ea3e6;
  --color-accent-hover: #a9b9ee;
  --color-accent-subtle: #1f2640;
  --color-on-accent: #10131a;

  --status-draft-bg: #252a31;
  --status-draft-fg: #b9c2cc;
  --status-released-bg: #152b2a;
  --status-released-fg: #7fcdc4;
  --status-in-progress-bg: #2f2510;
  --status-in-progress-fg: #e0b65e;
  --status-blocked-bg: #361c1a;
  --status-blocked-fg: #ee958b;
  --status-completed-bg: #182b1c;
  --status-completed-fg: #90cc9a;
  --status-cancelled-bg: transparent;
  --status-cancelled-fg: #8c96a1;
  --status-cancelled-border: #3a414b;

  --color-error-text: #ee958b;
  --color-error-border: #c9665c;
  --color-danger: #e38479;
  --color-danger-hover: #eaa197;
  --color-on-danger: #10131a;

  --color-accent-rgb: 142, 163, 230;
  --color-accent-hover-rgb: 169, 185, 238;
  --color-accent-subtle-rgb: 31, 38, 64;
  --color-bg-app-rgb: 17, 19, 22;
  --color-text-rgb: 221, 226, 231;
  --color-text-secondary-rgb: 170, 179, 189;
  --color-text-muted-rgb: 140, 150, 161;
  --color-danger-rgb: 227, 132, 121;
  --status-completed-fg-rgb: 144, 204, 154;
  --status-completed-bg-rgb: 24, 43, 28;
  --status-in-progress-fg-rgb: 224, 182, 94;
  --status-in-progress-bg-rgb: 47, 37, 16;
  --status-blocked-bg-rgb: 54, 28, 26;
}

/* ------------------------------------------------------------------ *
 * Tabler, pointed at the tokens
 * ------------------------------------------------------------------ */

/* Override Tabler's variables rather than fight its components. Only
 * variables Tabler actually reads are listed. Two things it does *not* read
 * from variables get rules of their own in the Primitives section below:
 *
 * - .text-bg-* badges are compiled constants, e.g. RGBA(247,103,7,...). The
 *   status badge therefore has its own .status-* classes rather than
 *   borrowing Tabler's.
 * - .form-control:focus hardcodes its border (#90b5e2) and ring, so the field
 *   focus state is a rule, not a variable.
 *
 * Only variables Tabler declares at :root belong here. Bootstrap 5.3 declares
 * component variables on the component itself - .card sets its own
 * --tblr-card-spacer-x - and those shadow anything set at :root, so a card
 * spacing token here would look live and do nothing. Card padding is a rule on
 * .card, in step (c). core/test_design_tokens.py fails on a shadowed name.
 *
 * --tblr-page-padding is deliberately untouched. It is not the page margin: it
 * is the base of every grid gutter (gutter-x is twice it), so setting it to the
 * 24px page gutter would widen every .row to 48px and make the product less
 * dense. The page gutter is applied to the content container in step (b). */
:root {
  --tblr-font-sans-serif: var(--font-ui);
  --tblr-body-font-size: var(--text-body);
  --tblr-border-radius: var(--radius);

  --tblr-body-bg: var(--color-bg-app);
  --tblr-body-bg-rgb: var(--color-bg-app-rgb);
  --tblr-body-color: var(--color-text);
  --tblr-body-color-rgb: var(--color-text-rgb);
  --tblr-emphasis-color: var(--color-text);
  --tblr-emphasis-color-rgb: var(--color-text-rgb);
  --tblr-secondary-color: var(--color-text-muted);
  --tblr-secondary-color-rgb: var(--color-text-muted-rgb);
  --tblr-bg-surface: var(--color-bg-surface);
  --tblr-bg-surface-secondary: var(--color-bg-subtle);
  --tblr-border-color: var(--color-border);
  --tblr-secondary: var(--color-text-muted);
  --tblr-secondary-rgb: var(--color-text-muted-rgb);
  --tblr-muted: var(--color-text-muted);
  --tblr-muted-rgb: var(--color-text-muted-rgb);

  --tblr-primary: var(--color-accent);
  --tblr-primary-rgb: var(--color-accent-rgb);
  --tblr-primary-fg: var(--color-on-accent);
  --tblr-primary-darken: var(--color-accent-hover);
  --tblr-primary-lt: var(--color-accent-subtle);
  --tblr-primary-lt-rgb: var(--color-accent-subtle-rgb);
  --tblr-primary-bg-subtle: var(--color-accent-subtle);
  --tblr-primary-text-emphasis: var(--color-accent-hover);
  --tblr-link-color: var(--color-accent);
  --tblr-link-color-rgb: var(--color-accent-rgb);
  --tblr-link-hover-color: var(--color-accent-hover);
  --tblr-link-hover-color-rgb: var(--color-accent-hover-rgb);
  --tblr-focus-ring-color: rgba(var(--color-accent-rgb), 0.25);

  /* bg-orange is what the environment banner is drawn with, and nothing else
   * in the product uses orange. */
  --tblr-orange: var(--color-env-qa);
  --tblr-orange-rgb: var(--color-env-qa-rgb);

  /* success, warning and danger, pointed at the status families (step c).
   * They drive alerts, text-danger, btn-danger and btn-outline-danger in 106
   * places across 75 templates. Warning is the ochre of "in progress", never
   * orange; danger is the destructive red, and alert text uses the darker
   * error tone through -text-emphasis. Every -rgb triplet is paired with its
   * colour, because Bootstrap builds tints from the triplet. */
  --tblr-success: var(--status-completed-fg);
  --tblr-success-rgb: var(--status-completed-fg-rgb);
  --tblr-success-fg: var(--color-on-accent);
  --tblr-success-darken: var(--status-completed-fg);
  --tblr-success-lt: var(--status-completed-bg);
  --tblr-success-lt-rgb: var(--status-completed-bg-rgb);
  --tblr-success-text-emphasis: var(--status-completed-fg);
  --tblr-success-bg-subtle: var(--status-completed-bg);
  --tblr-success-border-subtle: var(--status-completed-bg);

  --tblr-warning: var(--status-in-progress-fg);
  --tblr-warning-rgb: var(--status-in-progress-fg-rgb);
  --tblr-warning-fg: var(--color-on-accent);
  --tblr-warning-darken: var(--status-in-progress-fg);
  --tblr-warning-lt: var(--status-in-progress-bg);
  --tblr-warning-lt-rgb: var(--status-in-progress-bg-rgb);
  --tblr-warning-text-emphasis: var(--status-in-progress-fg);
  --tblr-warning-bg-subtle: var(--status-in-progress-bg);
  --tblr-warning-border-subtle: var(--status-in-progress-bg);

  --tblr-danger: var(--color-danger);
  --tblr-danger-rgb: var(--color-danger-rgb);
  --tblr-danger-fg: var(--color-on-danger);
  --tblr-danger-darken: var(--color-danger-hover);
  --tblr-danger-lt: var(--status-blocked-bg);
  --tblr-danger-lt-rgb: var(--status-blocked-bg-rgb);
  --tblr-danger-text-emphasis: var(--color-error-text);
  --tblr-danger-bg-subtle: var(--status-blocked-bg);
  --tblr-danger-border-subtle: var(--status-blocked-bg);
}

/* ------------------------------------------------------------------ *
 * App shell - DESIGN.md sections 8.1 to 8.3
 * ------------------------------------------------------------------ */

/* The environment banner sits above everything, sidebar included, and stays
 * put when the page scrolls. Its height is published once, as an offset, so the
 * fixed sidebar and the sticky top bar can start below it without either one
 * knowing why. Production renders no banner and the offset stays zero. */
:root {
  --env-banner-offset: 0px;
}

.has-env-banner {
  --env-banner-offset: var(--space-6);
}

.env-banner {
  position: sticky;
  top: 0;
  z-index: 1035;
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--space-6);
  padding: 0 var(--space-2);
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  font-weight: var(--weight-strong);
}

/* The first thing a keyboard reaches. Invisible until focused, because a
 * mouse user never needs it and a keyboard user needs it on every page. */
.skip-link {
  position: absolute;
  top: calc(-1 * var(--space-12));
  left: var(--space-2);
  z-index: 1100;
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg-surface);
  color: var(--color-accent);
  border: 1px solid var(--color-border-input);
  border-radius: var(--radius);
}

.skip-link:focus {
  top: var(--space-2);
}

/* Sidebar. A neutral surface rather than Tabler's dark rail: a dark column
 * beside light content is a hard edge the eye crosses all day. Owner decision 2
 * in DESIGN.md section 13 - one attribute in base.html if it goes the other way. */
.app-sidebar.navbar-vertical {
  --tblr-navbar-bg: var(--color-bg-sidebar);
  --tblr-navbar-border-color: var(--color-border);
  /* Tabler paints an active item with its own five-class selector reading
   * these component variables, which beat the plain .active rule below. Set
   * here, on the component, Tabler's rule paints the token instead of its
   * rgba(0,0,0,.06). Found by measuring the computed style after a screenshot
   * looked right and was not. */
  --tblr-navbar-active-bg: var(--color-accent-subtle);
  --tblr-navbar-active-color: var(--color-text);
  --tblr-navbar-active-border-color: var(--color-accent);
  --tblr-dropdown-link-active-bg: var(--color-accent-subtle);
  --tblr-dropdown-link-active-color: var(--color-text);
  background: var(--color-bg-sidebar);
  border-right: 1px solid var(--color-border);
}

.app-sidebar .navbar-brand {
  min-height: var(--size-topbar);
  margin: 0;
  font-size: var(--text-heading);
  line-height: var(--text-heading-lh);
  font-weight: var(--weight-strong);
}

.app-sidebar .navbar-brand a {
  color: var(--color-text);
}

.app-sidebar .nav-link,
.app-sidebar .dropdown-item {
  min-height: var(--size-row);
  padding-top: 0;
  padding-bottom: 0;
  font-size: var(--text-table);
  line-height: var(--text-table-lh);
  color: var(--color-text-secondary);
  border-radius: 0;
}

.app-sidebar .nav-link:hover,
.app-sidebar .dropdown-item:hover {
  background: var(--color-bg-subtle);
  color: var(--color-text);
}

.app-sidebar .nav-link:focus-visible,
.app-sidebar .dropdown-item:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

/* Where you are. Background, weight and a bar on the leading edge - three
 * signals, so it survives a monitor with the contrast turned down. */
.app-sidebar.navbar-vertical .navbar-collapse .nav-link.active,
.app-sidebar.navbar-vertical .navbar-collapse .dropdown-menu .dropdown-item.active {
  background: var(--color-accent-subtle);
  color: var(--color-text);
  font-weight: var(--weight-strong);
  box-shadow: inset 2px 0 0 var(--color-accent);
}

.app-sidebar .nav-link.is-active-group {
  color: var(--color-text);
  font-weight: var(--weight-strong);
}

/* Collapsed to a rail of icons. Desktop only: below Tabler's lg breakpoint the
 * sidebar is already Tabler's own collapsing top menu, and a second collapse
 * mechanism on top of it would fight the first. */
@media (min-width: 992px) {
  :root[data-sidebar="collapsed"] .app-sidebar.navbar-vertical.navbar-expand-lg {
    width: var(--size-sidebar-collapsed);
  }

  :root[data-sidebar="collapsed"] .navbar-expand-lg.navbar-vertical ~ .navbar,
  :root[data-sidebar="collapsed"] .navbar-expand-lg.navbar-vertical ~ .page-wrapper {
    margin-left: var(--size-sidebar-collapsed);
  }

  :root[data-sidebar="collapsed"] .app-sidebar .navbar-brand {
    font-size: var(--text-caption);
    line-height: var(--text-caption-lh);
  }

  :root[data-sidebar="collapsed"] .app-sidebar .nav-link-title,
  :root[data-sidebar="collapsed"] .app-sidebar .nav-link.dropdown-toggle::after {
    display: none;
  }

  :root[data-sidebar="collapsed"] .app-sidebar .nav-link {
    justify-content: center;
  }

  :root[data-sidebar="collapsed"] .app-sidebar .nav-link-icon {
    margin: 0;
  }

  /* A group opens beside the rail rather than inside it. The sidebar scrolls,
   * and a scrolling box clips anything positioned past its edge, so the
   * flyout is fixed - app_shell.js sets its top from the icon that opened it. */
  :root[data-sidebar="collapsed"] .app-sidebar .navbar-collapse .dropdown-menu.show {
    position: fixed;
    left: var(--size-sidebar-collapsed);
    z-index: 1040;
    min-width: calc(var(--size-sidebar) - var(--space-4));
    padding: var(--space-1) 0;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
  }

  :root[data-sidebar="collapsed"] .app-sidebar .navbar-collapse .dropdown-menu .dropdown-item {
    padding-left: var(--space-3);
    padding-right: var(--space-3);
  }
}

/* Top bar. Sticky below the banner, so the controls in it are always one reach
 * away however far down a list somebody has scrolled. */
.app-topbar.navbar {
  position: sticky;
  top: var(--env-banner-offset);
  z-index: 1020;
  min-height: var(--size-topbar);
  padding-top: 0;
  padding-bottom: 0;
}

.app-topbar > .container-fluid {
  padding-left: var(--size-page-gutter);
  padding-right: var(--size-page-gutter);
}

/* Pushed right on its own rather than by space-between: below the desktop
 * breakpoint the sidebar toggle is hidden, and with only one child left
 * space-between would put the controls on the left. */
.app-topbar .navbar-nav {
  flex-wrap: nowrap;
  gap: var(--space-2);
  margin-left: auto;
}

/* The name and Log out used to stack into two lines at the right-hand edge:
 * a Tabler .nav-item rule lays its children out as a column, which also made
 * the top bar 52px rather than 48. Measured, not guessed - the name (20px), the
 * gap and the form (24px) add up to exactly the extra height. */
.app-topbar .app-user {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
}

/* The quiet icon buttons of the top bar: the sidebar toggle, and the keyboard
 * shortcut list beside the user's name. DESIGN.md 8.8, "Icon-only". */
.app-sidebar-toggle,
.app-topbar-icon {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--size-field);
  height: var(--size-field);
  margin-right: var(--space-2);
  padding: 0;
  color: var(--color-text-secondary);
  background: transparent;
  border: 0;
  border-radius: var(--radius);
}

.app-sidebar-toggle:hover,
.app-topbar-icon:hover {
  background: var(--color-bg-subtle);
  color: var(--color-text);
}

.app-sidebar-toggle:focus-visible,
.app-topbar-icon:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

@media (min-width: 992px) {
  .app-sidebar-toggle {
    display: inline-flex;
  }
}

.app-topbar-icon {
  display: inline-flex;
  margin-right: 0;
}

/* The keyboard shortcut list, DESIGN.md section 10: grouped definition lists,
 * keys in a fixed column so the descriptions line up. Keys as keycaps: the
 * outline, not a fill, so a column of them stays quiet. */
.shortcut-list {
  max-height: min(70vh, 640px);
  overflow-y: auto;
}

.shortcut-group {
  margin: var(--space-3) 0 var(--space-1);
  color: var(--color-text-secondary);
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  font-weight: var(--weight-strong);
}

.shortcut-group:first-child {
  margin-top: 0;
}

.shortcut-list dl {
  margin: 0;
}

.shortcut-list dl > div {
  display: grid;
  grid-template-columns: 14rem 1fr;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-table);
  line-height: var(--text-table-lh);
}

.shortcut-list dt {
  font-weight: normal;
}

.shortcut-list dd {
  margin: 0;
}

.shortcut-list kbd {
  display: inline-block;
  min-width: var(--space-6);
  padding: 0 var(--space-1);
  color: var(--color-text);
  font-family: var(--font-ui);
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  text-align: center;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-input);
  border-radius: var(--radius);
  box-shadow: none;
}

/* The page container: full width, one gutter. Tables take the whole screen a
 * 1920px monitor offers; container-xl stopped them at about 1320px and left
 * the rest as margin. */
.page-body {
  margin-top: var(--space-4);
  margin-bottom: var(--space-6);
}

.app-container {
  padding-left: var(--size-page-gutter);
  padding-right: var(--size-page-gutter);
}

/* The skip link lands here, and a focusable <main> would otherwise draw a
 * ring round the whole page. */
.page-body:focus {
  outline: none;
}

/* Navigation feedback: a thin accent line under the top bar once a page takes
 * longer than 300ms to arrive. A fade, not a sliding animation - DESIGN.md
 * rule 16 - and not even the fade for anyone who asked for reduced motion. */
.app-progress {
  position: fixed;
  top: calc(var(--env-banner-offset) + var(--size-topbar));
  right: 0;
  left: 0;
  z-index: 1040;
  height: 2px;
  background: var(--color-accent);
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease-out;
}

.app-progress.is-active {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .app-progress {
    transition: none;
  }
}

.has-env-banner .app-sidebar.navbar-vertical.navbar-expand-lg {
  top: var(--env-banner-offset);
}

/* ------------------------------------------------------------------ *
 * Page headings
 * ------------------------------------------------------------------ */

/* Every screen in this system opens with a plain <h1>, and there are a hundred
 * and fifty of them. Rewriting each to Tabler's page-header markup would mean
 * a hundred and fifty chances to wrap a {% tag %} across a newline - which
 * renders as nothing at all while still returning 200, and has cost this
 * project a data grid with no columns before now.
 *
 * So the proportions come from here instead: an <h1> inside the page body gets
 * Tabler's .page-title treatment without any template being touched. Screens
 * that want the full header - a title with buttons beside it, a pretitle above
 * it - still opt in by writing the markup, and this rule gets out of the way
 * because .page-title is more specific.
 */
.page-body h1 {
  margin: 0 0 var(--space-4);
  font-size: var(--text-title);
  line-height: var(--text-title-lh);
  font-weight: var(--weight-strong);
}

/* The old markup for a section heading inside a page. Same reasoning: match
 * Tabler's card title rather than leave a Bootstrap h5 floating at a size
 * nothing else on the page uses.
 */
.page-body h2.h5 {
  font-size: var(--text-heading);
  line-height: var(--text-heading-lh);
  font-weight: var(--weight-strong);
  margin-top: var(--space-6);
}

/* The page header pattern - {% page_header %} and the header-with-actions row
 * that fifty-six screens emit. DESIGN.md section 5.3: the title is the one 20px
 * step on a page, and nothing else competes with it.
 *
 * Tabler gives .page-wrapper .page-header a top margin on top of the top margin
 * .page-body already has, which is most of the sixty-odd pixels of nothing that
 * sat above every title. The header now starts where the content starts. */
.page-body .page-header {
  margin: 0 0 var(--space-4);
  min-height: 0;
}

.page-title {
  font-size: var(--text-title);
  line-height: var(--text-title-lh);
  font-weight: var(--weight-strong);
}

/* Tabler's pretitle is 10px uppercase with letter-spacing. Uppercase costs
 * legibility and width, and Turkish casing depends on the lang attribute being
 * exactly right - "Üretim" and "ÜRETİM" are not the same length, and a
 * mis-set lang turns i into I rather than İ. DESIGN.md rule 14. */
.page-pretitle {
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  font-weight: var(--weight-regular);
  text-transform: none;
  letter-spacing: 0;
  color: var(--color-text-muted);
}

/* ================================================================== *
 * Primitives - DESIGN.md section 8
 * ================================================================== *
 *
 * Every screen already uses Tabler's .btn, .form-control and .table, so most
 * of these restyle what is there rather than asking templates to change. The
 * genuinely new pieces - status badge, toast, dialog, skeleton - have one
 * partial each in templates/ and are adopted screen by screen in step (d). */

/* ------------------------------------------------------------------ *
 * Cards
 * ------------------------------------------------------------------ */

/* Component variables live on .card, not :root (see the Tabler block above).
 * 16px rather than 24px of padding, and no shadow: a shadow says "this floats",
 * and a card on an ERP screen does not float, it holds a table. */
.card {
  --tblr-card-spacer-x: var(--space-4);
  --tblr-card-spacer-y: var(--space-4);
  --tblr-card-cap-padding-x: var(--space-4);
  --tblr-card-cap-padding-y: var(--space-3);
  --tblr-card-border-color: var(--color-border);
  --tblr-card-box-shadow: none;
}

/* ------------------------------------------------------------------ *
 * Numbers
 * ------------------------------------------------------------------ */

/* Quantities, prices and balances are compared down a column, not read across
 * a row. Tabular figures keep every digit in the same place so the eye can do
 * that. .num is the whole treatment; right-aligned table cells get the figures
 * too, because that is what every existing numeric column already is. */
.num {
  font-variant-numeric: tabular-nums lining-nums;
  text-align: right;
  white-space: nowrap;
}

/* The same figures without the alignment: dates, times and operation numbers
 * line up in a left-aligned column, and a figure in the header strip sits
 * under its caption. */
.tnum {
  font-variant-numeric: tabular-nums lining-nums;
  white-space: nowrap;
}

.table td.text-end,
.table th.text-end {
  font-variant-numeric: tabular-nums lining-nums;
}

/* ------------------------------------------------------------------ *
 * Buttons - DESIGN.md 8.8
 * ------------------------------------------------------------------ */

/* Exactly a field high, so a button beside an input lines up. The label is
 * 13px semibold, and widths are never fixed: Turkish labels run long. */
.btn {
  --tblr-btn-padding-y: 0;
  --tblr-btn-padding-x: var(--space-3);
  --tblr-btn-font-size: var(--text-table);
  --tblr-btn-font-weight: var(--weight-strong);
  --tblr-btn-line-height: var(--text-table-lh);
  --tblr-btn-border-radius: var(--radius);
  --tblr-btn-box-shadow: none;
  /* Disabled is a tint, not opacity. Opacity makes contrast unpredictable - the
   * label of a faded primary button can drop below anything readable. */
  --tblr-btn-disabled-opacity: 1;
  --tblr-btn-disabled-bg: var(--color-bg-subtle);
  --tblr-btn-disabled-color: var(--color-text-disabled);
  --tblr-btn-disabled-border-color: var(--color-border);
  min-height: var(--size-field);
}

/* .btn-sm is declared before this file, so the rule above would otherwise
 * inflate every small button back to full size. 24px: the size inside a table
 * row. */
.btn-sm {
  --tblr-btn-padding-x: var(--space-2);
  --tblr-btn-font-size: var(--text-caption);
  --tblr-btn-line-height: var(--text-caption-lh);
  min-height: var(--space-6);
}

/* Secondary - a button with no colour variant - gets a field's border, so it
 * reads as a control against the surface (WCAG 1.4.11). Scoped with :where so
 * it cannot out-rank a variant that sets its own border. */
.btn:where(:not(.btn-primary, .btn-secondary, .btn-success, .btn-info, .btn-warning, .btn-danger, .btn-light, .btn-dark, .btn-link, [class*="btn-outline-"], [class*="btn-ghost-"])) {
  --tblr-btn-border-color: var(--color-border-input);
  --tblr-btn-hover-border-color: var(--color-text-secondary);
  --tblr-btn-hover-bg: var(--color-bg-subtle);
}

.btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  box-shadow: none;
}

/* Busy. The label stays, the width is held by primitives.js so nothing
 * reflows, and clicks are ignored. The spinner is the one continuous motion in
 * the product (DESIGN.md rule 16): it carries information, and it stops for
 * anyone who asked for reduced motion. */
.btn.is-busy {
  pointer-events: none;
}

.btn.is-busy::before {
  content: "";
  flex: 0 0 auto;
  width: var(--text-caption);
  height: var(--text-caption);
  margin-right: var(--space-2);
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: app-spin 0.8s linear infinite;
}

@keyframes app-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ------------------------------------------------------------------ *
 * Fields - DESIGN.md 8.7
 * ------------------------------------------------------------------ */

.form-control,
.form-select {
  min-height: var(--size-field);
  padding-top: calc((var(--size-field) - var(--text-table-lh) - 2px) / 2);
  padding-bottom: calc((var(--size-field) - var(--text-table-lh) - 2px) / 2);
  font-size: var(--text-table);
  line-height: var(--text-table-lh);
  border-color: var(--color-border-input);
  border-radius: var(--radius);
}

.form-control-sm,
.form-select-sm {
  min-height: var(--space-6);
  padding-top: 0;
  padding-bottom: 0;
  font-size: var(--text-caption);
  line-height: calc(var(--space-6) - 2px);
}

.form-control:hover:not(:disabled):not([readonly]):not(:focus),
.form-select:hover:not(:disabled):not(:focus) {
  border-color: var(--color-text-secondary);
}

/* Tabler writes this focus state as constants (#90b5e2), so no token reaches
 * it. The border turns accent - 6.8:1 against the surface, which is what makes
 * the focus visible - and the ring softens it. */
.form-control:focus,
.form-select:focus {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 2px rgba(var(--color-accent-rgb), 0.25);
}

.form-control:disabled,
.form-select:disabled {
  background-color: var(--color-bg-subtle);
  border-color: var(--color-border);
  color: var(--color-text-disabled);
}

/* Read-only is data, not a dead control: no border, still selectable and
 * copyable. Distinct from disabled, which is a control you cannot use. */
.form-control[readonly] {
  background-color: transparent;
  border-color: transparent;
  box-shadow: none;
}

/* Django marks every field that failed validation with aria-invalid="true",
 * so the error border needs no change to any form. The server's message is
 * already rendered beneath the field as .invalid-feedback. */
.form-control[aria-invalid="true"],
.form-select[aria-invalid="true"] {
  border-color: var(--color-error-border);
}

.form-control[aria-invalid="true"]:focus,
.form-select[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 2px rgba(var(--color-danger-rgb), 0.2);
}

.form-label {
  margin-bottom: var(--space-1);
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  font-weight: var(--weight-strong);
  color: var(--color-text-secondary);
}

/* Tabler draws the required asterisk in a hardcoded #d63939. A required field
 * is not an error, and a column of red asterisks reads as a form already
 * complaining. */
.form-label.required::after {
  color: var(--color-text-muted);
}

.form-hint {
  margin-top: var(--space-1);
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  color: var(--color-text-muted);
}

.invalid-feedback {
  margin-top: var(--space-1);
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  color: var(--color-error-text);
}

/* ------------------------------------------------------------------ *
 * Status badge - DESIGN.md 8.10
 * ------------------------------------------------------------------ */

.status-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: var(--size-badge);
  padding: 0 var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  font-weight: var(--weight-strong);
  white-space: nowrap;
  vertical-align: middle;
}

.page-header .status-badge {
  height: var(--size-badge-header);
}

.status-glyph {
  display: inline-flex;
}

.status-glyph svg {
  width: var(--text-caption);
  height: var(--text-caption);
}

.status-draft {
  background: var(--status-draft-bg);
  color: var(--status-draft-fg);
}

.status-released {
  background: var(--status-released-bg);
  color: var(--status-released-fg);
}

.status-in-progress {
  background: var(--status-in-progress-bg);
  color: var(--status-in-progress-fg);
}

.status-blocked {
  background: var(--status-blocked-bg);
  color: var(--status-blocked-fg);
}

.status-completed {
  background: var(--status-completed-bg);
  color: var(--status-completed-fg);
}

/* Draft and cancelled are both neutral. What tells them apart is fill against
 * dashed outline - a difference a colour-blind reader can still see. */
.status-cancelled {
  background: var(--status-cancelled-bg);
  color: var(--status-cancelled-fg);
  border: 1px dashed var(--status-cancelled-border);
}

/* The developer alarm for an unmapped status. Outside the palette on purpose,
 * and a test fails before it can reach the plant. */
.status-unknown {
  background: transparent;
  color: var(--color-text-secondary);
  border: 1px dashed var(--tblr-purple);
}

/* ------------------------------------------------------------------ *
 * Tables - DESIGN.md 8.4
 * ------------------------------------------------------------------ */

/* One row height for every table. There were eight class combinations, and
 * table-sm on some but not others, so the same kind of list was a different
 * density on each screen. No zebra: stripes compete with selection and with
 * exception marks, which both need the row background. */
.table {
  --tblr-table-striped-bg: transparent;
  --tblr-table-border-color: var(--color-border);
  font-size: var(--text-table);
  line-height: var(--text-table-lh);
}

.table > :not(caption) > * > * {
  padding: calc((var(--size-row) - var(--text-table-lh) - 1px) / 2) var(--space-3);
}

/* A badge or a checkbox aligned "middle" hangs below the text baseline and
 * grows the line box, so a row holding one came out 34px instead of 32. Aligned
 * to the top of the line they sit inside it. */
.table .status-badge,
.table .form-check-input {
  vertical-align: top;
}

.table .form-check-input {
  margin-top: calc((var(--text-table-lh) - 1rem) / 2);
}

.table > thead .form-check-input {
  margin-top: 0;
}

/* Tabler's header is 10px uppercase with letter-spacing. 12px sentence case
 * instead: legible, narrower in Turkish, and not shouting above every column. */
.table > thead > tr > th {
  position: sticky;
  top: calc(var(--env-banner-offset) + var(--size-topbar));
  z-index: 2;
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  font-weight: var(--weight-strong);
  text-transform: none;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* The sticky header only works if nothing between it and the page scrolls.
 * .table-responsive is overflow-x: auto, which makes it the scroll container
 * and pins the header to a box that never scrolls vertically. At the designed
 * widths (DESIGN.md 6.4) a full-width table fits, so the overflow is lifted and
 * the header sticks to the page. Below 1280px tables scroll sideways again and
 * the header simply stays where it is. */
@media (min-width: 1280px) {
  .table-responsive {
    overflow-x: visible;
  }
}

.table > tbody > tr:hover > * {
  --tblr-table-accent-bg: var(--color-bg-subtle);
}

/* A selected row is any row whose checkbox is ticked. Read from the checkbox
 * itself, so it needs no script and cannot disagree with what will be
 * submitted. */
.table > tbody > tr:has(input[type="checkbox"]:checked) > * {
  --tblr-table-accent-bg: var(--color-accent-subtle);
}

.table > tbody > tr:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

table[data-row-nav] > tbody > tr {
  cursor: pointer;
}

/* Undone records stay on the list and stay clickable - nothing is hidden
 * because it was reversed - but they stop competing with live ones. */
.table > tbody > tr.row-muted > * {
  color: var(--color-text-secondary);
}

/* An exception is a 2px bar on the leading edge, not a tinted row, so it
 * survives selection and hover. The reason belongs in a column; the bar only
 * makes the row findable. */
.table > tbody > tr.row-exception-blocked > :first-child {
  box-shadow: inset 2px 0 0 var(--status-blocked-fg), inset 0 0 0 9999px var(--tblr-table-accent-bg);
}

.table > tbody > tr.row-exception-in-progress > :first-child {
  box-shadow: inset 2px 0 0 var(--status-in-progress-fg), inset 0 0 0 9999px var(--tblr-table-accent-bg);
}

.table > tfoot > tr.row-total > * {
  border-top: 1px solid var(--color-border-input);
  font-weight: var(--weight-strong);
}

/* The bar that replaces a table's card header once rows are selected. */
.bulk-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--size-topbar);
  padding: 0 var(--space-4);
  background: var(--color-accent-subtle);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-table);
}

/* ------------------------------------------------------------------ *
 * Empty state - DESIGN.md 8.13
 * ------------------------------------------------------------------ */

/* Tabler's .empty is a 3rem-padded, full-height hero with a 48px icon and a
 * 20px title. Here it is a short, quiet line inside the region that is empty:
 * no illustration, one sentence, at most one action. selection_report.html
 * already uses these classes, so every selection screen adopts it at once. */
.empty {
  height: auto;
  min-height: 0;
  padding: var(--space-8) var(--space-4);
  gap: var(--space-1);
}

.empty-icon {
  width: auto;
  height: auto;
  margin: 0 0 var(--space-2);
  color: var(--color-text-muted);
}

.empty-icon svg {
  width: var(--space-6);
  height: var(--space-6);
}

.empty-title {
  margin: 0;
  font-size: var(--text-body);
  line-height: var(--text-body-lh);
  font-weight: var(--weight-strong);
}

.empty-subtitle {
  margin: 0;
  font-size: var(--text-table);
  line-height: var(--text-table-lh);
  color: var(--color-text-muted);
}

.empty-action {
  margin-top: var(--space-3);
}

/* ------------------------------------------------------------------ *
 * Loading skeleton - DESIGN.md 8.14
 * ------------------------------------------------------------------ */

/* Shown only after 300ms, so a fast response never flickers - done in CSS, so
 * a region can include the partial and need no script to time it. A slow
 * opacity pulse, not a shimmer, and none at all under reduced motion. */
.skeleton-table {
  visibility: hidden;
  animation: skeleton-appear 1ms linear 300ms forwards, skeleton-pulse 1.6s ease-in-out 300ms infinite;
}

.skeleton-row {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  height: var(--size-row);
  padding: 0 var(--space-3);
  border-bottom: 1px solid var(--color-border);
}

.skeleton-bar {
  flex: 1 1 0;
  height: var(--space-2);
  border-radius: var(--radius);
  background: var(--color-bg-subtle);
}

.skeleton-bar-num {
  flex: 0 0 12%;
}

@keyframes skeleton-appear {
  to {
    visibility: visible;
  }
}

@keyframes skeleton-pulse {
  50% {
    opacity: 0.55;
  }
}

/* ------------------------------------------------------------------ *
 * Toast - DESIGN.md 8.11
 * ------------------------------------------------------------------ */

/* Confirmation that something finished - above all a posting, named with the
 * document number it created. Errors and warnings are not toasts: they stay on
 * the page where somebody can read them properly. */
.toast-stack {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: 1090;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 360px;
  max-width: calc(100vw - 2 * var(--space-4));
}

.app-toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--color-bg-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-text-muted);
  border-radius: var(--radius);
  font-size: var(--text-table);
  line-height: var(--text-table-lh);
  transition: opacity 120ms ease-out;
}

.app-toast-success {
  border-left-color: var(--status-completed-fg);
}

.app-toast-message {
  flex: 1 1 auto;
}

.app-toast-close {
  flex: 0 0 auto;
  padding: 0 var(--space-1);
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-heading);
  line-height: var(--text-table-lh);
}

.app-toast-close:hover {
  background: var(--color-bg-subtle);
  color: var(--color-text);
}

.app-toast-close:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.app-toast.is-leaving {
  opacity: 0;
}

/* ------------------------------------------------------------------ *
 * Confirmation dialog - DESIGN.md 8.12
 * ------------------------------------------------------------------ */

/* A native <dialog>: showModal() gives the backdrop, the focus trap and Esc
 * without a library, and autofocus puts focus on Cancel. */
.app-dialog {
  width: min(480px, calc(100vw - 2 * var(--space-4)));
  padding: 0;
  color: var(--color-text);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

.app-dialog-wide {
  width: min(640px, calc(100vw - 2 * var(--space-4)));
}

.app-dialog::backdrop {
  background: var(--color-overlay);
}

.app-dialog-header {
  padding: var(--space-4) var(--space-4) 0;
}

.app-dialog-title {
  margin: 0;
  font-size: var(--text-heading);
  line-height: var(--text-heading-lh);
  font-weight: var(--weight-strong);
}

.app-dialog-body {
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-body);
  line-height: var(--text-body-lh);
}

.app-dialog-body > :last-child {
  margin-bottom: 0;
}

/* The bulk dialog also confirms a single form it sits inside - a rejection
 * with the reason typed on the page. Opened that way nothing fills its count
 * and list, and they should take no space. */
.app-dialog [data-bulk-total]:empty,
.app-dialog [data-bulk-items]:empty {
  display: none;
}

.app-dialog-items {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-4);
  font-variant-numeric: tabular-nums;
}

.app-dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4) var(--space-4);
}

/* ================================================================== *
 * Screen furniture - step (d)
 * ================================================================== *
 *
 * The pieces screens are built from, above the primitives: the detail header
 * strip, the filter summary, the bulk bar's visibility, the form footer and the
 * error summary. Each is one implementation that every module uses. */

.page-subtitle {
  margin-top: var(--space-1);
  color: var(--color-text-secondary);
  font-size: var(--text-body);
  line-height: var(--text-body-lh);
}

.page-header .page-title .status-badge {
  vertical-align: middle;
}

/* The header summary, DESIGN.md 8.9: one bordered strip of key figures, a
 * caption above each value. It replaces <dl class="row">, which on a 1920px
 * screen put a label at the left edge and its value a third of the way across. */
.summary-strip {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  margin: 0 0 var(--space-4);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

/* The same strip as the first thing inside a card, under its header. */
.summary-strip-flush {
  margin: 0;
  border: 0;
  border-bottom: 1px solid var(--color-border);
  border-radius: 0;
}

.summary-item {
  min-width: 0;
  padding: var(--space-3) var(--space-4);
  border-right: 1px solid var(--color-border);
}

.summary-item:last-child {
  border-right: 0;
}

/* A form in the strip (the GİB number entry) takes a row of its own. */
.summary-item-wide {
  grid-column: 1 / -1;
  border-top: 1px solid var(--color-border);
  border-right: 0;
}

.summary-item dt {
  margin: 0 0 var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--text-caption);
  line-height: var(--text-caption-lh);
  font-weight: var(--weight-regular);
}

.summary-item dd {
  margin: 0;
  font-size: var(--text-body);
  line-height: var(--text-body-lh);
  overflow-wrap: anywhere;
}

/* A section of a page: a table in a card with a title. Section titles were
 * <h2 class="h5"> floating over a bare table, a different size on each page. */
.card-header .card-title {
  font-size: var(--text-heading);
  line-height: var(--text-heading-lh);
  font-weight: var(--weight-strong);
}

.card-header .card-subtitle {
  margin: 0 0 0 var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-table);
}

/* An empty table body holds the empty state, not a bare sentence in the
 * first column (DESIGN.md rule 1). */
.table > tbody > tr.empty-row > td {
  padding: 0;
}

.table > tbody > tr.empty-row:hover > * {
  --tblr-table-accent-bg: transparent;
}

.table .empty {
  padding: var(--space-6) var(--space-4);
}

/* Secondary text under an identifier in a cell - a material's description -
 * on the same line, so a row stays one row high. */
.cell-sub {
  margin-left: var(--space-2);
  color: var(--color-text-muted);
}

/* The confirmation worklist: a table that is also a form. The inputs get
 * widths that fit what goes in them - a quantity is not a paragraph - and the
 * confirmation columns are marked off from the order's own. */
.worklist-table .worklist-group,
.worklist-table .worklist-group-start {
  border-left: 1px solid var(--color-border-input);
}

/* Named for the worklist where they started; any table that is also a form
 * (.table-form) uses the same widths - the sales forms and the approvals
 * worklist do. */
.worklist-table .worklist-input-qty,
.worklist-table .worklist-input-min,
.table-form .worklist-input-qty,
.table-form .worklist-input-min {
  min-width: 7rem;
}

.worklist-table .worklist-input-wide,
.table-form .worklist-input-wide {
  min-width: 11rem;
}

/* A field is exactly a row high (DESIGN.md 8.7), so a cell holding one gives
 * up its vertical padding and the row stays 32px rather than growing to 43.
 * .table-form is the same rule for any table that is also a form. */
.worklist-table > tbody > tr > td:has(> .form-control, > .form-select),
.table-form > tbody > tr > td:has(> .form-control, > .form-select) {
  padding-top: 0;
  padding-bottom: 0;
}

/* The line-entry grid reads as a table first (DESIGN.md 8.7): its fields keep
 * a light border rather than the input border until hovered or focused. The
 * spec says no border at all; an empty cell in a grid nobody has typed into
 * yet then gives no hint where to type, so a faint one stays. */
.line-grid td .form-control:not(:hover):not(:focus):not([aria-invalid="true"]),
.line-grid td .form-select:not(:hover):not(:focus):not([aria-invalid="true"]) {
  border-color: var(--color-border);
}

.line-grid-actions {
  margin: var(--space-2) 0 var(--space-3);
}

/* Short values get short fields (DESIGN.md 8.7): a quantity in a 600px box
 * reads as prose. Right-aligned, like the figures beside it. */
.form-control.field-quantity {
  width: 9rem;
  margin-left: auto;
  text-align: right;
}

/* A titled section inside one form card. */
.form-section-title {
  margin: var(--space-2) 0 var(--space-3);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border);
  color: var(--color-text-secondary);
  font-size: var(--text-body);
  font-weight: var(--weight-strong);
}

/* The row menu: one quiet "…" per row, 24px inside a 32px row. */
.table .row-menu {
  width: var(--space-6);
  height: var(--space-6);
  min-height: var(--space-6);
  padding: 0;
  color: var(--color-text-muted);
}

/* Its cell gives up its vertical padding, or the 24px button makes the row 36. */
.table > tbody > tr > td:has(> .dropdown > .row-menu) {
  padding-top: 0;
  padding-bottom: 0;
}

.table .row-menu .icon {
  width: var(--space-4);
  height: var(--space-4);
  margin: 0;
}

/* The filter bar's summary and stale hint, DESIGN.md 8.6. */
.filter-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  margin: 0 0 var(--space-3);
  font-size: var(--text-table);
  line-height: var(--text-table-lh);
}

.filter-summary-label {
  color: var(--color-text-secondary);
  font-weight: var(--weight-strong);
}

.stale-hint {
  margin: 0 0 var(--space-3);
  color: var(--status-in-progress-fg);
  font-size: var(--text-table);
  font-weight: var(--weight-strong);
}

/* Form footer, DESIGN.md 8.7: right-aligned, primary on the far right, and it
 * stays on screen at the foot of a long form. */
.form-actions {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  background: var(--color-bg-surface);
}

/* On a filter bar Execute leads: it is the thing done most, and the results
 * appear below it on the left. */
.form-actions-start {
  position: static;
  justify-content: flex-start;
}

.form-actions-hint {
  max-width: 64rem;
  margin-left: auto;
  color: var(--color-text-muted);
  font-size: var(--text-caption);
}

.error-summary {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-error-border);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--status-blocked-bg);
  color: var(--color-error-text);
  font-size: var(--text-table);
}

.error-summary:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.error-summary-title {
  margin: 0 0 var(--space-1);
  font-weight: var(--weight-strong);
}

.error-summary-list {
  margin: 0;
  padding-left: var(--space-4);
}

.error-summary-list a {
  color: inherit;
  text-decoration: underline;
}

/* The bulk bar exists only while something is selected, and it takes the place
 * of the card header rather than floating over the rows (DESIGN.md 8.5). The
 * hiding waits for the script: without it the bar stays visible and its
 * buttons still post. */
.js .bulk-bar:not(.is-active) {
  display: none;
}

.card:has(.bulk-bar.is-active) > .card-header {
  display: none;
}

.bulk-bar-count {
  font-weight: var(--weight-strong);
}

.bulk-bar .btn-list {
  margin-left: auto;
}

/* A bar measuring one figure against another - utilisation. Neutral fill,
 * because the accent belongs to actions; blocked red only past the limit. */
.progress {
  height: var(--space-2);
  background: var(--color-bg-subtle);
}

.progress-bar {
  background: var(--color-text-muted);
}

.progress-bar.is-over {
  background: var(--status-blocked-fg);
}

/* ------------------------------------------------------------------ *
 * Added in the 14 September refresh: detail tabs, the sticky first column,
 * short fields and the lookup spinner. DESIGN.md 8.4, 8.7, 8.9.
 * ------------------------------------------------------------------ */

/* Detail tabs. The bar is hidden until primitives.js finds two panels worth
 * choosing between; without the script the panels stack. */
.doc-tabs {
  display: flex;
  gap: var(--space-4);
  height: var(--size-tabbar);
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}

.doc-tabs[hidden] {
  display: none;
}

.doc-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: -1px;
  padding: 0 var(--space-1);
  color: var(--color-text-secondary);
  font-size: var(--text-body);
  line-height: var(--text-body-lh);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.doc-tab:hover {
  color: var(--color-text);
  text-decoration: none;
}

.doc-tab.active {
  color: var(--color-text);
  font-weight: var(--weight-strong);
  border-bottom-color: var(--color-accent);
}

.doc-tab:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: var(--radius);
}

.doc-tab-count {
  color: var(--color-text-muted);
  font-size: var(--text-caption);
  font-weight: normal;
  font-variant-numeric: tabular-nums;
}

/* The link to the change log sits at the far end of the tab bar: it opens a
 * page of its own, so it is not dressed as a tab. */
.doc-tabs-end {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
}

/* The sticky first column. Only where a table actually scrolls sideways -
 * below 1280px, where .table-responsive is a scroll container again - and the
 * first column is almost always the document number. When the first column is
 * the row-selection checkbox, it and the column after it both stay. */
@media (max-width: 1279.98px) {
  .table-responsive .table > :is(thead, tbody, tfoot) > tr > :first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background-color: var(--color-bg-surface);
  }

  .table-responsive .table > thead > tr > :first-child {
    z-index: 3;
    background-color: var(--color-bg-subtle);
  }

  .table-responsive .table:has(> thead > tr > th:first-child input[data-select-all]) > :is(thead, tbody, tfoot) > tr > :first-child {
    width: var(--size-select-column);
    min-width: var(--size-select-column);
  }

  .table-responsive .table:has(> thead > tr > th:first-child input[data-select-all]) > :is(thead, tbody, tfoot) > tr > :nth-child(2) {
    position: sticky;
    left: var(--size-select-column);
    z-index: 1;
    background-color: var(--color-bg-surface);
  }

  .table-responsive .table:has(> thead > tr > th:first-child input[data-select-all]) > thead > tr > :nth-child(2) {
    z-index: 3;
    background-color: var(--color-bg-subtle);
  }
}

/* Short values get short fields (DESIGN.md 8.7). A maximum, so a narrow
 * column still shrinks them; read from the input's own type, so no form has
 * to declare anything. */
.form-control[type="date"] {
  max-width: var(--size-field-date);
}

.form-control[type="number"] {
  max-width: var(--size-field-number);
}

.form-control[type="time"] {
  max-width: var(--size-field-time);
}

/* A lookup that is taking a while. The field stays editable. */
.form-control.is-looking-up {
  padding-right: calc(var(--space-3) + var(--space-3));
  background-image: var(--image-lookup-spinner);
  background-repeat: no-repeat;
  background-position: right var(--space-2) center;
  background-size: var(--space-3) var(--space-3);
}

/* ------------------------------------------------------------------ *
 * Reduced motion
 * ------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .form-control.is-looking-up {
    background-image: var(--image-lookup-spinner-still);
  }

  .btn.is-busy::before {
    animation: none;
    border-right-color: currentColor;
    opacity: 0.5;
  }

  .skeleton-table {
    animation: skeleton-appear 1ms linear 300ms forwards;
  }

  .app-toast {
    transition: none;
  }
}

/* The divider inside a sidebar group took a full dropdown margin, which read
 * as a gap in the middle of the Production menu. */
.app-sidebar .dropdown-divider {
  margin: var(--space-1) 0;
}

/* ------------------------------------------------------------------ *
 * Print
 * ------------------------------------------------------------------ */

/* An irsaliye or an order confirmation is a legal document that goes out of the
 * building. Nothing from the application frame belongs on it.
 */
@media print {
  .navbar,
  .navbar-vertical,
  .page-header .btn,
  .card-footer .btn {
    display: none !important;
  }

  .page-wrapper {
    margin: 0 !important;
  }
}

/* ------------------------------------------------------------------ *
 * Selection screens
 * ------------------------------------------------------------------ */

/* The filter boxes are textareas because a pasted column out of Excel arrives
 * with newlines in it and a plain input flattens that into one unusable
 * string. But a screen of eight visible textareas reads as a form you have to
 * write essays into, which is not what a filter row should look like.
 *
 * So they sit at one row - the height of an ordinary input - and grow when
 * somebody is actually typing in one, which is the only time the extra height
 * is worth anything. Resize stays available for a really long list.
 */
.selection-field {
  min-height: var(--size-field);
  resize: vertical;
  transition: height 0.12s ease-in-out;
}

.selection-field:focus {
  height: 5.5rem;
}
