/* ═══════════════════════════════════════════════════════════════════════════
   tabler.min.custom.css — HSR Price Calculator custom overrides & utilities
   Loaded after tabler.min.css to override framework defaults.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Base overrides ───────────────────────────────────────────────────────── */
/* Radius tokens (project rule): every UI element uses a 3px corner radius.
   The values fan out via Tabler's --tblr-* variable system so component
   defaults pick them up without per-component rules. */
:root {
    --tblr-font-sans-serif: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
    --tblr-border-color: rgba(0, 0, 0, 0.1);
    --tblr-border-radius:        3px;
    --tblr-border-radius-sm:     3px;
    --tblr-border-radius-lg:     3px;
    --tblr-card-border-radius:   3px;
    --tblr-modal-border-radius:  3px;
}
html { margin-left: 0 !important; }
body { font-size: 13px; }

/* ── Global compact sizing — 32px inputs & buttons by default ────────────── */
.form-control, .form-select { font-size: 12px !important; padding: 5px 8px !important; height: 32px !important; box-shadow: none !important; }
.form-control:focus, .form-select:focus { box-shadow: none !important; }
.input-group { box-shadow: none !important; }
.form-select { padding-right: 24px !important; }
.form-check { padding-left: 28px; min-height: 24px; display: flex; align-items: center; gap: 0; }
.form-check .form-check-input { margin-left: -28px; margin-top: 0; flex-shrink: 0; }
.form-check .form-check-label { margin-left: 4px; }
.btn { font-size: 12px !important; padding: 0 10px !important; line-height: 20px !important; min-height: 0 !important; height: 32px !important; display: inline-flex; align-items: center; justify-content: center; gap: 0 !important; --tblr-btn-border-radius: var(--tblr-border-radius-sm); }
/* dropdown-toggle-split — the tiny carat-only button that sits next to
   a primary action in a split-button group. Bootstrap reserves it for
   "extra options"; the global .btn padding above (0 10px) makes it as
   wide as the main button, which wastes horizontal space. Tighten the
   horizontal padding so the button is only as wide as the carat needs.
   `flex: 0 0 auto` forces it to its intrinsic size in btn-groups inside
   d-grid containers (e.g., the quote-edit sidebar) where the default
   `flex: 1 1 auto` from Bootstrap would otherwise let the carat split
   the row 50/50 with the main button. */
.btn.dropdown-toggle-split {
    /* Fixed width — Tabler's `.dropdown-toggle-split` has its own
       padding rule with higher specificity that fights ours, so we
       just nail the box width directly. 22 px gives just enough room
       for the carat glyph + a 1 px border each side. */
    width: 22px !important;
    min-width: 22px !important;
    padding: 0 !important;
    flex: 0 0 auto !important;
}
.btn28.dropdown-toggle-split,
.btn32.dropdown-toggle-split {
    width: 22px !important;
    min-width: 22px !important;
    padding: 0 !important;
    flex: 0 0 auto !important;
}
/* Tighten Bootstrap's default `margin-left: 0.255em` on the dropdown
   caret pseudo so the chevron sits centred in the narrow button. */
.dropdown-toggle-split::after { margin-left: 0 !important; }

/* Non-pill badges use 2px radius (matches form inputs + buttons). Pill badges
   keep their large radius via .rounded-pill which sets its own value. */
.badge { --tblr-badge-border-radius: var(--tblr-border-radius-sm); }
button.input-group-text { width: 32px; }

.module-card:hover { transform: translateY(-2px); transition: transform .15s; }
/* ── Turbo-style loading bar ──────────────────────────────────────────────── */
.loading-bar {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    height: 3px;
    background: var(--tblr-primary);
    z-index: 9999;
    opacity: 0;
    transition: width 300ms ease-out, opacity 150ms 150ms ease-in;
}
/* ── Toast notifications — opaque backgrounds so they stay readable on any
      page content underneath. Tabler's alert-* uses translucent tints by
      default; we override only inside #notifyContainer to keep the rest of
      the app's inline alerts unchanged. */
#notifyContainer .alert {
    backdrop-filter: none;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
}
#notifyContainer .alert .alert-icon { position: static; margin: 0; flex: 0 0 auto; }
#notifyContainer .alert > small     { flex: 1 1 auto; line-height: 1.35; }
#notifyContainer .alert .btn-close  { position: static; margin: 0; flex: 0 0 auto; }
#notifyContainer .alert-success { background-color: #e6f8ee; color: #1a7c43; }
#notifyContainer .alert-danger  { background-color: #fce4e6; color: #b42318; }
#notifyContainer .alert-warning { background-color: #fef2cf; color: #8a5a00; }
#notifyContainer .alert-info    { background-color: #e1f0fb; color: #0b5fa5; }

/* ── Notification badge ──────────────────────────────────────────────────── */
.badge-notification { position: absolute; top: 6px !important; right: 4px !important; font-size: 0.55rem; min-width: 1rem; padding: 1px 4px; line-height: 1.2; color: #fff !important; }
.navbar-brand-bar { min-height: 0 !important; padding: 8px 0 0 0 !important; box-shadow: none !important; }
.sticky-header { position: sticky; top: 0; z-index: 1030; background: var(--tblr-bg-surface, #fff); }
/* ── Menu item active — blue text and icon ───────────────────────────────── */
.navbar-expand-md .navbar-nav .nav-item.active > .nav-link { color: var(--tblr-primary) !important; }
/* ── Menu item spacing — hover has no underline (color change only); only
      the active item shows the bottom border via Tabler's own .nav-item.active
      styling. */
.navbar-expand-md .navbar-nav .nav-item { position: relative; margin-right: 1rem; }
.navbar-expand-md .navbar-nav .nav-item:last-child { margin-right: 0; }
.navbar-expand-md .navbar-nav .nav-link { background-color: transparent !important; }
.navbar-expand-md .navbar-nav .nav-link:hover,
.navbar-expand-md .navbar-nav .nav-link:focus { background-color: transparent !important; }
/* ── Navbar dropdown — remove top/bottom padding ───────────────────────── */
.navbar-expand-md .navbar-nav .dropdown-menu { padding-top: 0; padding-bottom: 0; overflow: hidden; }
.navbar-expand-md .navbar-nav .dropdown-divider { margin: 0; }
/* ── Dropdown menu item — match main menu font size (13px) ──────────────── */
.navbar-expand-md .navbar-nav .dropdown-menu .dropdown-item,
.navbar-expand-md .navbar-nav .dropdown-menu .dropdown-item-text { font-size: 13px; }
/* ── Dropdown menu item hover — light blue background ───────────────────── */
.navbar-expand-md .navbar-nav .dropdown-menu .dropdown-item:hover,
.navbar-expand-md .navbar-nav .dropdown-menu .dropdown-item:focus { background-color: var(--tblr-primary-lt) !important; }
/* ── Dropdown menu item active — blue icon ──────────────────────────────── */
.navbar-expand-md .navbar-nav .dropdown-menu .dropdown-item.active .dropdown-item-icon { color: var(--tblr-primary) !important; }
/* ── Delete (danger) dropdown items — tint the icon red to match the text
      (the base .dropdown-item-icon forces muted secondary + .7 opacity). ── */
.dropdown-item.text-danger .dropdown-item-icon { color: var(--tblr-danger) !important; opacity: 1; }
/* ── User-info dropdown link — clickable but not styled like a dropdown-item
      (keeps body text color, no blue, same padding as dropdown-item-text) ─── */
.navbar-expand-md .navbar-nav .dropdown-menu .dropdown-user-link { display: block; text-decoration: none; color: inherit; }
.navbar-expand-md .navbar-nav .dropdown-menu .dropdown-user-link:hover { background-color: var(--tblr-primary-lt); }
.navbar-expand-md .navbar-nav .dropdown-menu .dropdown-user-link .dropdown-item-text { padding-top: 0.5rem; padding-bottom: 0.5rem; }
/* ── Menu item padding — tighter spacing ────────────────────────────────── */
/* FL-133 — top-level navbar items had `padding: 0.25rem 0` which made
   them run flush against each other (cramped) AND the .nav-link-icon
   inside them shipped a 0.5rem margin-right that left an oversized
   gap between the icon and the title. Give the link a sane horizontal
   padding and tighten the icon→title gap so the menu reads like a
   normal toolbar. */
.navbar-expand-md .navbar-nav .nav-link { padding: 0.25rem 0.625rem; }
.navbar-expand-md .navbar-nav .nav-link .nav-link-icon { margin-right: 0.25rem; }
.navbar-brand-bar .navbar-brand { margin: 0; padding-top: 0; padding-bottom: 0; }
.navbar-brand-logo { height: 22px; width: auto; }
.navbar-brand-text { font-size: 16px; font-weight: 700; position: relative; top: 1px; left: -2px; }

.page-body { margin-top: 15px; margin-bottom: 0; }

/* ── Page loader ──────────────────────────────────────────────────────────── */
.page-loader {
    position: fixed; inset: 0; z-index: 9999;
    background: var(--tblr-body-bg, #f1f5f9);
    display: flex; align-items: center; justify-content: center;
    transition: opacity 300ms ease;
}
.page-loader.fade-out { opacity: 0; pointer-events: none; }
.breadcrumb { margin-bottom: 0; font-size: 13px; }
.breadcrumb-item + .breadcrumb-item::before { color: var(--tblr-secondary); }

/* Tighten the eye/toggle button inside input-groups (except search icon) */
.input-group .btn:not(.btn-search-icon) { padding-left: 20px; padding-right: 10px; }

/* ── Form validation border — red on invalid (background + padding handled below) */
.form-control.is-invalid,
.form-select.is-invalid,
.was-validated .form-control:invalid,
.was-validated .form-select:invalid {
    border-color: var(--tblr-danger, #d63939) !important;
}

/* ── Date input / Litepicker-attached input — custom calendar icon.
   After JS init the type switches to "text" with data-litepicker="1", so
   we match both selectors. */
input[type="date"] { position: relative; }
input[type="date"]::-webkit-calendar-picker-indicator {
    opacity: 0;
    position: absolute;
    right: 0;
    top: 0;
    width: 24px;
    height: 100%;
    margin: 0;
    padding: 0;
    cursor: pointer;
}
input[type="date"],
input[data-litepicker] {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12'/%3E%3Cpath d='M16 3v4'/%3E%3Cpath d='M8 3v4'/%3E%3Cpath d='M4 11h16'/%3E%3Cpath d='M7 14h.013'/%3E%3Cpath d='M10.01 14h.005'/%3E%3Cpath d='M13.01 14h.005'/%3E%3Cpath d='M16.015 14h.005'/%3E%3Cpath d='M13.015 17h.005'/%3E%3Cpath d='M7.01 17h.005'/%3E%3Cpath d='M10.01 17h.005'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 16px 16px;
    padding-right: 24px !important;
    width: 108px !important;
}
/* Range inputs need room for two dates + separator: DD.MM.YYYY - DD.MM.YYYY */
input[data-lp-range="1"][data-litepicker] { width: 195px !important; }

/* ── Litepicker calendar popup — match Tom Select drop-down style ─────────
   Blue border, 4px radius, Inter font. We override Litepicker's CSS vars
   where possible; otherwise target the generated classes directly. */
.litepicker {
    --litepicker-container-months-color-bg: var(--tblr-card-bg, #fff);
    --litepicker-day-color: var(--tblr-body-color);
    --litepicker-day-color-hover: var(--tblr-primary);
    --litepicker-is-today-color: var(--tblr-primary);
    --litepicker-is-in-range-color: rgba(var(--tblr-primary-rgb), 0.1);
    --litepicker-is-start-color: #fff;
    --litepicker-is-start-color-bg: var(--tblr-primary);
    --litepicker-is-end-color: #fff;
    --litepicker-is-end-color-bg: var(--tblr-primary);
    --litepicker-button-next-month-color-hover: var(--tblr-primary);
    --litepicker-button-prev-month-color-hover: var(--tblr-primary);
    font-family: 'Inter', sans-serif !important;
    font-size: 12px !important;
}
.litepicker {
    border: 1px solid var(--tblr-border-color) !important;
    border-radius: var(--tblr-border-radius-sm) !important;
    box-shadow: 0 4px 16px rgba(0,0,0,.08) !important;
    background: var(--tblr-card-bg, #fff) !important;
    overflow: hidden;
}
.litepicker .container__months { box-shadow: none !important; }
.litepicker .month-item-header { padding: 8px; }
.litepicker .month-item-name,
.litepicker .month-item-year { color: var(--tblr-body-color) !important; font-weight: 400 !important; }
.litepicker .container__days .day-item:hover { color: var(--tblr-primary) !important; background: rgba(var(--tblr-primary-rgb), 0.08) !important; box-shadow: none !important; }
.litepicker .container__days .day-item.is-today { color: var(--tblr-primary) !important; font-weight: 600; }
.litepicker .container__days .day-item.is-start-date,
.litepicker .container__days .day-item.is-end-date { background: var(--tblr-primary) !important; color: #fff !important; }

/* Year / month dropdowns inside the picker header — match our form-select
   (translucent border, 4px radius, blue chevron, blue focus ring). */
.litepicker .month-item-header { display: flex !important; align-items: center; gap: 4px; padding: 8px; }
.litepicker .month-item-header > div { display: flex; align-items: center; gap: 4px; flex: 1; justify-content: center; }
.litepicker .month-item-header .month-item-name,
.litepicker .month-item-header .month-item-year { display: inline-flex; align-items: center; }
/* Tom Select-wrapped month/year dropdowns — compact, inline, white bg. */
.litepicker .month-item-header .ts-wrapper.form-select { width: auto !important; min-width: 0 !important; height: 26px !important; background: #fff !important; }
.litepicker .month-item-header .ts-wrapper.form-select .ts-control { min-height: 24px !important; height: 24px !important; padding: 0 22px 0 8px !important; font-size: 12px !important; background: #fff !important; }
/* The Tom Select options list is appended to <body>; Litepicker sits at
   z-index 9999, so lift the dropdown above so it's visible when opened
   from inside the calendar. */
body > .ts-dropdown { z-index: 10000 !important; background: #fff !important; border: 1px solid var(--tblr-border-color-translucent) !important; border-radius: var(--tblr-border-radius) !important; margin-top: 1px !important; padding: 0 !important; box-shadow: 0 4px 16px rgba(0,0,0,.08); overflow: hidden !important; }
body > .ts-dropdown .ts-dropdown-content { background: #fff !important; border-radius: var(--tblr-border-radius) !important; padding: 0 !important; max-height: 213px; overflow-y: auto; }
body > .ts-dropdown .option { white-space: nowrap; }
body > .ts-dropdown .option:hover,
body > .ts-dropdown .option.active { background: var(--tblr-primary-lt) !important; color: var(--tblr-primary) !important; }
.litepicker .month-item-header select {
    height: 26px !important;
    padding: 0 22px 0 8px !important;
    font-size: 12px !important;
    font-weight: 400 !important;
    color: var(--tblr-body-color) !important;
    border: 1px solid var(--tblr-border-color-translucent) !important;
    border-radius: var(--tblr-border-radius-sm) !important;
    background-color: #fff !important;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e") !important;
    background-repeat: no-repeat !important;
    background-position: right 6px center !important;
    background-size: 12px 10px !important;
    appearance: none !important;
    -webkit-appearance: none !important;
    cursor: pointer !important;
}
.litepicker .month-item-header select:focus {
    border-color: var(--tblr-border-color) !important;
    box-shadow: none !important;
    outline: none !important;
}

/* Shortcut buttons footer (Today + range presets). */
.litepicker .lp-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 4px;
    padding: 8px;
    border-top: 1px solid var(--tblr-border-color-translucent);
    background: var(--tblr-bg-surface-tertiary, #f9fafb);
}
.litepicker .lp-today-btn {
    height: 24px;
    padding: 0 8px;
    font-size: 11px;
    font-weight: 500;
    color: var(--tblr-primary);
    background: transparent;
    border: 1px solid var(--tblr-primary);
    border-radius: var(--tblr-border-radius-sm);
    cursor: pointer;
    white-space: nowrap;
}
.litepicker .lp-today-btn:hover { background: var(--tblr-primary); color: #fff; }
/* Calendar icon turns blue on hover (anywhere in the input) and on focus. */
input[type="date"]:hover,
input[type="date"]:focus,
input[type="date"]:focus-within,
input[data-litepicker]:hover,
input[data-litepicker]:focus {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23206bc4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12'/%3E%3Cpath d='M16 3v4'/%3E%3Cpath d='M8 3v4'/%3E%3Cpath d='M4 11h16'/%3E%3Cpath d='M7 14h.013'/%3E%3Cpath d='M10.01 14h.005'/%3E%3Cpath d='M13.01 14h.005'/%3E%3Cpath d='M16.015 14h.005'/%3E%3Cpath d='M13.015 17h.005'/%3E%3Cpath d='M7.01 17h.005'/%3E%3Cpath d='M10.01 17h.005'/%3E%3C/svg%3E");
}

/* Kill all transitions on form controls / buttons / selects so the border
   and icon colour swap instantly and stay in sync on hover / focus. */
.form-control, .form-select, .btn, .btn-search-icon, .input-group-text,
.ts-wrapper, .ts-wrapper .ts-control,
.litepicker .month-item-header select,
input[type="date"], input[data-litepicker] {
    transition: none !important;
}

/* Hover: blue border for every ENABLED input / select / Tom Select
   control. Disabled controls stay grey so the operator gets a clear
   "not interactive" affordance.
   ---
   IMPORTANT: `.form-select` is copied from the native <select> onto Tom
   Select's wrapper <div>. `:disabled` is a pseudo-class — it ONLY applies
   to real form controls (input, select, textarea, button, fieldset), so
   on a <div> it's always-false and `:not(:disabled)` always-true. The
   ts-wrapper's actual disabled state lives in a `.disabled` CLASS that
   Tom Select adds. We need BOTH `:not(:disabled)` (for native selects)
   AND `:not(.disabled)` (for Tom Select wrappers) on every `.form-select`
   selector below — otherwise hovering a locked Tom Select still paints
   the blue border + blue chevron. */
.form-control:not(.is-invalid):not(:disabled):hover,
.form-select:not(.is-invalid):not(:disabled):not(.disabled):hover,
.ts-wrapper.form-select:not(.disabled):hover,
.ts-wrapper.form-select:not(.disabled):hover .ts-control,
.litepicker .month-item-header select:not(:disabled):hover {
    border-color: var(--tblr-primary) !important;
}
/* Hover chevron swap — same enabled-only gate. Skip .no-chevron selects
   (Customer / Substrate) so the chevron stays hidden on hover too. */
.form-select:not(.is-invalid):not(:disabled):not(.disabled):not(.no-chevron):hover,
.ts-wrapper.form-select:not(.disabled):not(.no-chevron):hover,
.litepicker .month-item-header select:not(:disabled):hover {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23206bc4' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e") !important;
}
/* Input-group hover — every adjoining border turns blue, including the
   split borders on .btn-search-icon / .input-group-text which would
   otherwise keep their grey border-right/top/bottom set with !important.
   Skip when any child is disabled (whole group reads as inactive). */
.input-group:not(:has(:disabled)):hover .form-control:not(:disabled),
.input-group:not(:has(:disabled)):hover .form-select:not(:disabled),
.input-group:not(:has(:disabled)):hover .btn:not(:disabled),
.input-group:not(:has(:disabled)):hover .input-group-text {
    border-color: var(--tblr-primary) !important;
}
.input-group:not(:has(:disabled)):hover .btn-search-icon:not(:disabled),
.input-group:not(:has(:disabled)):hover .btn-search-icon:not(:disabled):hover,
.input-group:not(:has(:disabled)):hover .input-group-text,
.input-group:not(:has(:disabled)):hover button.input-group-text:not(:disabled) {
    border-right-color: var(--tblr-primary) !important;
    border-top-color: var(--tblr-primary) !important;
    border-bottom-color: var(--tblr-primary) !important;
    border-left-color: var(--tblr-primary) !important;
}
/* Focus: blue border + glow halo for every input / select / Tom Select control. */
.form-control:not(.is-invalid):focus,
.form-control:not(.is-invalid):focus-within,
.form-select:not(.is-invalid):focus,
.ts-wrapper.form-select.focus,
.litepicker .month-item-header select:focus {
    border-color: var(--tblr-primary) !important;
    box-shadow: 0 0 0 3px rgba(var(--tblr-primary-rgb), 0.15) !important;
    outline: none !important;
}


/* ── Input-group button styling ───────────────────────────────────────────── */
.input-group .btn-search-icon,
.input-group .btn-search-icon:hover,
.input-group .btn-search-icon:focus,
.input-group .btn-search-icon:active { background: #fff !important; box-shadow: none !important; border: none !important; border-color: var(--tblr-border-color) !important; border-right: 1px solid var(--tblr-border-color) !important; border-top: 1px solid var(--tblr-border-color) !important; border-bottom: 1px solid var(--tblr-border-color) !important; }
/* Suffix add-on box (both <button> and <span> variants of .input-group-text).
   Matches the 32px input height, no doubled border with the input next to it. */
.input-group-text,
button.input-group-text,
button.input-group-text:hover,
button.input-group-text:focus,
button.input-group-text:active {
    height: 32px !important;
    min-height: 32px !important;
    padding: 0 8px;
    font-size: 12px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: #fff !important;
    box-shadow: none !important;
    border: 1px solid var(--tblr-border-color) !important;
    border-left: 0 !important;
    border-radius: 0 var(--tblr-border-radius) var(--tblr-border-radius) 0 !important;
}
/* Button-style add-ons are square (icon-only). Span add-ons hug their text width. */
button.input-group-text { width: 32px; padding: 0; }

/* The form-control adjacent to a trailing add-on shouldn't paint its own
   border-right — the add-on supplies the continuation. Prevents the 2px
   doubled seam between input and addon. */
.input-group .form-control:has(+ .input-group-text),
.input-group .form-control:has(+ button.input-group-text),
.input-group .form-control:has(+ .btn-search-icon) { border-right: none !important; }
.input-group .form-control:has(+ .input-group-text) { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; }

/* ── Form select arrow position ──────────────────────────────────────────── */
.form-select { background-position: right 0.5rem center; }

/* ── Navbar dropdown arrow position ──────────────────────────────────────── */
.navbar-nav .dropdown-toggle::after { position: relative; top: -1px; left: 0px; }

/* ── Input-group focus ring ───────────────────────────────────────────────── */
.input-group .form-control,
.input-group .form-select,
.input-group .btn,
.input-group .input-group-text { transition: none !important; }
.input-group .form-control:focus,
.input-group .form-select:focus { box-shadow: none !important; border-color: var(--tblr-primary) !important; outline: none !important; z-index: 1 !important; }

.input-group:focus-within:has(.form-control:focus, .form-select:focus) {
    border-radius: var(--tblr-border-radius, 4px);
    box-shadow: 0 0 0 3px rgba(var(--tblr-primary-rgb), 0.15);
}

.input-group:focus-within:has(.form-control:focus, .form-select:focus) .btn,
.input-group:focus-within:has(.form-control:focus, .form-select:focus) .input-group-text { border-right-color: var(--tblr-primary) !important; border-top-color: var(--tblr-primary) !important; border-bottom-color: var(--tblr-primary) !important; transition: none !important; }

.input-group:focus-within:has(.is-invalid) {
}

.input-group .is-invalid ~ .btn { border-color: var(--tblr-danger, #d63939); }

.input-group .invalid-feedback { display: none; position: absolute; top: 100%; left: 0; z-index: 5; width: 100%; }
.input-group:has(.is-invalid) .invalid-feedback { display: block; }

.input-group .btn:has(+ .invalid-feedback) {
    border-top-right-radius: var(--tblr-border-radius, 4px) !important;
    border-bottom-right-radius: var(--tblr-border-radius, 4px) !important;
}

/* ── Utility classes ──────────────────────────────────────────────────────── */
.fs-28          { font-size: 28px; }
.fs-24          { font-size: 24px; }
.fs-13          { font-size: 13px; }
.fs-12          { font-size: 12px; }
.fs-16          { font-size: 16px; }
.fs-11          { font-size: 11px; }
.mw-130         { max-width: 130px; }
.mw-110         { max-width: 110px; }
.w-150          { width: 150px; }
.w-110          { width: 110px; }
.fs-32          { font-size: 32px; }
.overflow-visible { overflow: visible !important; }
.pt-6rem        { padding-top: 6rem; }
.login-logo     { height: 100px; border: 3px solid var(--tblr-border-color); border-radius: 12px; padding: 10px; background: #fff; }
.cursor-pointer { cursor: pointer; }
.bg-surface     { background: var(--tblr-bg-surface-secondary, #f4f6fa); }
.ws-nowrap      { white-space: nowrap; }

/* ── Custom button colors ─────────────────────────────────────────────────── */
.btn-excel          { background: #217346; color: #fff; border-color: #217346; }
.btn-excel:hover    { background: #1a5c38; color: #fff; border-color: #1a5c38; }
.btn-datasheet      { background: #800000; color: #fff; border-color: #800000; }
.btn-datasheet:hover { background: #600000; color: #fff; border-color: #600000; }
.btn-copy           { background: #6c757d; color: #fff; border-color: #6c757d; }
.btn-copy:hover     { background: #5a6268; color: #fff; border-color: #5a6268; }
.btn-save-calc      { background: #1e3a5f; color: #fff; border-color: #1e3a5f; }
.btn-save-calc:hover { background: #162d4a; color: #fff; border-color: #162d4a; }

/* ── Component utilities ──────────────────────────────────────────────────── */
/* ── Button icons — thicker stroke for better visibility ─────────────────── */
.btn .icon              { stroke-width: 2; }
/* Radius uses --tblr-border-radius-sm (4px) to match form-control / datepicker
   on the right edge — the input-group reads as one rounded rectangle.
   Triple-class selector bumps specificity above `.btn.btn32` (0,2,0) which
   sets `padding: 0 10px !important` on every sized button. */
/* FL-136 — match button.input-group-text geometry (32×32, symmetric padding)
   so every icon adjacent to an input has the same footprint regardless of
   which addon class is used. The old 24px width + asymmetric padding made
   the search icon look squeezed next to the wider €/m² text addons. */
.btn.btn-search-icon.btn-search-icon { padding: 0 !important; width: 32px !important; min-width: 0 !important; height: 32px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; border-radius: 0 var(--tblr-border-radius-sm) var(--tblr-border-radius-sm) 0 !important; }
.btn-white:hover { border-color: var(--tblr-primary) !important; color: var(--tblr-primary) !important; }
/* Search icon matches the datepicker icon (#6b7280 grey) for visual consistency. */
.btn.btn-search-icon.btn-search-icon { color: #6b7280 !important; }
/* Cancel Tabler's generic `.btn .icon` margins on an icon-only button —
   the default rule yanks the icon left and adds a right margin (to push
   icon away from a sibling text node), which on a label-less search
   button just produces visible empty space on the right side. Zero
   margin = icon sits dead-centre in the 32×32 box. */
.btn.btn-search-icon .icon { margin: 0 !important; }

/* Outline buttons sit on slightly tinted toolbars — give them an explicit
   white at rest, but let Tabler's :hover fill with the variant colour.
   EXCLUDE the btn-check checked state — that's a "filled" state where
   Bootstrap makes the text+icon white, so the background must be the
   primary colour, not white. Without the exclusion you get white-on-white. */
.btn-outline-primary:not(:hover):not(:focus):not(:active):not(.show):not([aria-expanded="true"]):not(.btn-check:checked + *) { background-color: #fff !important; }
.btn-check:checked + .btn-outline-primary { background-color: var(--tblr-primary) !important; color: #fff !important; }
/* An OPEN dropdown-toggle (this app flags it via aria-expanded="true", not
   .show) must stay in its active blue state, not fall back to the idle-white
   rule above when it loses :focus — otherwise the trigger flashes white while
   its menu is still open and you mouse over another row's Actions button. */
.btn-outline-primary.dropdown-toggle[aria-expanded="true"],
.btn-outline-primary.dropdown-toggle.show { background-color: var(--tblr-primary) !important; color: #fff !important; }
.btn-outline-danger:not(:hover):not(:focus):not(:active):not(.show):not([aria-expanded="true"]):not(.btn-check:checked + *) { background-color: #fff !important; }
.btn-check:checked + .btn-outline-danger { background-color: var(--tblr-danger) !important; color: #fff !important; }
.btn-search-icon:hover,
.btn-search-icon:active { color: var(--tblr-primary) !important; }
button.input-group-text:hover,
button.input-group-text:active { color: var(--tblr-primary) !important; }
.btn-close-sm    { padding-right: 4px; }
.btn-action-sm   { padding-left: 10px; padding-right: 4px; }
.online-dot      { position: absolute; bottom: -2px; right: -2px; width: 10px; height: 10px; padding: 0; border: 2px solid #fff; border-radius: 50%; }
.opacity-dim     { opacity: 0.45; }
.d-hidden        { display: none; }
.text-hover-primary { text-decoration: none; }
.text-hover-primary:hover { color: var(--tblr-primary) !important; text-decoration: underline !important; }
.phone-select    { max-width: 130px; }
.phone-select-sm { max-width: 110px; box-shadow: none !important; border-color: #e6e7e9 !important; outline: none !important; }
.table-fixed     { table-layout: fixed; width: 100%; }
.h-32            { height: 32px; }
.total-highlight { margin-left: -32px; margin-right: -32px; padding-left: 32px; padding-right: 32px; }
.cell-pad-sm     { padding-top: 8px; padding-bottom: 8px; }
.footer-note     { padding-top: 10px; padding-bottom: 10px; }

/* ── Column width helpers ─────────────────────────────────────────────────── */
.col-w-30   { width: 30px; }
.col-w-45   { width: 45px; }
.col-w-55   { width: 55px; }
.col-w-60   { width: 60px; }
.col-w-70   { width: 70px; }
.col-w-75   { width: 75px; }
.col-w-80   { width: 80px; }
.col-w-100  { width: 100px; }
.col-mw-63  { min-width: 63px; }
.col-mw-65  { min-width: 65px; }
.col-mw-130 { min-width: 130px; }
.col-mw-160 { min-width: 160px; }
.input-w-130 { width: 130px; }

/* ── Data table — standard styling for all data tables ───────────────────── */
.data-table             { font-size: 13px; }
.data-table th          { font-size: 13px; font-weight: 700; white-space: nowrap; }
.data-table thead th,
.data-table tbody td { white-space: nowrap; }
.data-table tbody tr:last-child > * { border-bottom-width: 0; }
.data-table tbody tr:nth-child(even) { background-color: rgba(var(--tblr-primary-rgb), 0.025); }
/* Zebra striping on the catalog/list tables too (they use .table-vcenter
   .table-hover, not .data-table). Matches the customers/materials look; the
   :hover state still wins on top of the stripe. */
.table-vcenter.table-hover tbody tr:nth-child(even) > * { background-color: rgba(var(--tblr-primary-rgb), 0.025); }
/* Keep :hover visible ON TOP of the stripe (the stripe rule above is specific
   enough to swallow Bootstrap's hover on even rows otherwise). */
.table-vcenter.table-hover tbody tr:hover > * { background-color: rgba(var(--tblr-primary-rgb), 0.07) !important; }

/* FL — eliminate the double horizontal line that appears above a bottom
   table toolbar. Bootstrap puts a 1px border-bottom on every <td>/<th>
   (including the last row), and the bottom toolbar adds its own border-top,
   so the two stack into a thicker / visibly doubled line. Zero the last
   row's bottom border ONLY when a .table-toolbar follows the
   .table-responsive — standalone tables (quote/show price tables, etc.)
   keep their natural bottom edge. */
.table-responsive:has(+ .table-toolbar) > .table > tbody > tr:last-child > * { border-bottom-width: 0; }

/* ── Table toolbar — light blue background ───────────────────────────────── */
.table-toolbar { background-color: var(--tblr-bg-surface-secondary, #f4f6fa); }
/* ── List-table edge alignment ───────────────────────────────────────────────
   The AJAX tables (customers/materials/users…) carry Tabler's `.card-table`,
   which flushes the first/last cell to the card spacer (1rem) so the first
   column lines up with the toolbar's search box (which sits at px-3 = 1rem).
   The catalog `index` tables don't carry `.card-table`, so their first column
   fell short of the search box ("not flush"). Flush EVERY toolbar list-table to
   the same 1rem edge via the toolbar adjacency, so it holds for current and
   future tables without remembering to add `.card-table` to each. */
.table-toolbar + .table-responsive > .table > :is(thead, tbody) > tr > *:first-child { padding-left: var(--tblr-card-spacer-x, 1rem); }
.table-toolbar + .table-responsive > .table > :is(thead, tbody) > tr > *:last-child  { padding-right: var(--tblr-card-spacer-x, 1rem); }
/* ── Capabilities editor table (machine + template edit modals share
   #capabilitiesTable) — the base .table paints only row bottom-borders, leaving
   the table without an outer frame inside the modal. Add the missing outer
   border (top/left/right/bottom); inner column dividers intentionally omitted
   (not a full .table-bordered grid). border-collapse merges it with the
   existing header/row borders, so no doubled lines. */
#capabilitiesTable { border: 1px solid var(--tblr-border-color); }
/* ── Cancel button — red text, red fill on hover ─────────────────────────── */
.btn-ghost-secondary { color: var(--tblr-danger) !important; border-color: transparent !important; background: transparent !important; }
.btn-ghost-secondary:hover { color: #fff !important; background: var(--tblr-danger) !important; border-color: var(--tblr-danger) !important; }
/* ── Input-group icon buttons (WhatsApp settings copy / generate, etc.) ───────
   btn-outline-secondary filled grey + whitened the icon on hover ("white on grey
   bg"). Instead, match the input's own border: neutral border + neutral icon at
   rest (so the appended button reads as part of the field), and a BLUE icon +
   blue border on hover — no fill. The Tabler icon is stroke:currentColor, so the
   button's `color` drives the glyph. */
.input-group .btn-input-icon {
    color: var(--tblr-border-color);
    border-color: var(--tblr-border-color);
    background-color: transparent;
}
.input-group .btn-input-icon:hover,
.input-group .btn-input-icon:focus,
.input-group .btn-input-icon:active {
    color: var(--tblr-primary);
    border-color: var(--tblr-primary);
    background-color: transparent;
    box-shadow: none;
}

.page-header   { margin-top: 16px !important; }

/* ── Card border-radius — clip grey backgrounds to card corners ─────────── */
/* FL-128 — make the 3px outer radius explicit on .card and .modal-content
   so it doesn't fight any Tabler inline override; the token is set at :root
   too but a direct rule with !important is the contract. */
.card { overflow: hidden; --tblr-card-spacer-x: 1rem; --tblr-card-cap-padding-x: 1rem; border-radius: 3px !important; }
/* Card titles that lead with an icon (WhatsApp / Mail / status / voice settings,
   etc.) — the inline SVG sits on the text baseline and reads as not vertically
   centred. Flex-centre the icon against the title text. Only matches titles that
   actually contain an icon, so plain text titles are untouched. */
.card-title:has(> .icon) { display: flex; align-items: center; }
.modal-content { border-radius: 3px !important; }

/* All card headers (title rows) share the same light grey background. */
.card-header { background-color: var(--tblr-bg-surface-tertiary) !important; }

/* Tab card header — use Tabler's native vertical positioning and sizing.
   The only custom tweak here is vertically centering the btn28 action button.
   align-self: center stops the LI from stretching to fill the UL's cross-axis,
   and the negative margin-top compensates for Tabler's asymmetric UL padding
   (padding-top: calc(cap-padding-y / 2), padding-bottom: 0) so the button
   lines up with the true card-header center rather than the UL content-box
   center. */
.card-header-tabs > .nav-item.ms-auto,
.card-header-pills > .nav-item.ms-auto {
    align-self: center;
    display: flex;
    align-items: center;
}

/* Active pill — Tabler's default tint is primary @ ~4% alpha which reads as
   barely-blue. Bump to primary @ 14% (+10 points) so the active tab stands
   out clearly. */
.nav-pills .nav-link.active,
.nav-pills .show > .nav-link { background-color: rgba(var(--tblr-primary-rgb), 0.14); }

/* Pill hover — primary-blue tint at half the intensity of the active pill
   (active is 14%, hover is 7%). Scoped to card-header-pills so navbar
   nav-links don't pick up a blue background (navbar keeps its underline
   hover indicator). :focus-visible instead of :focus so the highlight
   doesn't "stick" after a mouse click. */
.card-header-pills .nav-link:not(.active):hover,
.card-header-pills .nav-link:not(.active):focus-visible {
    background-color: rgba(var(--tblr-primary-rgb), 0.07) !important;
    color: var(--tblr-primary) !important;
}

/* Navbar hover — text + icon turn primary blue (no background, underline
   pseudo-element on .nav-item already draws the hover indicator). The
   :hover on .nav-item keeps the color active while the mouse is over the
   opened dropdown menu. :focus-visible instead of :focus so the blue
   doesn't "stick" after clicking and moving away. */
.navbar-expand-md .navbar-nav .nav-item:hover > .nav-link:not(.active),
.navbar-expand-md .navbar-nav .nav-link:not(.active):focus-visible {
    color: var(--tblr-primary) !important;
}

/* 5px spacing between card-header pills + 28px pill height */
.card-header-pills { gap: 5px; }
.card-header-pills .nav-link {
    height: 32px;
    padding: 0 12px;
    display: inline-flex;
    align-items: center;
    line-height: 1;
    font-size: 12px;
    border-radius: var(--tblr-border-radius-sm, 4px);
}
/* Action buttons living inside a card-header match the pill height (32px),
   EXCEPT inside a tabbed card-header (.card-header-tabs / .nav-tabs) where
   buttons share the row with the tab strip and need to fit inside the
   48px header without crowding — there btn28 stays at its native 28px.
   The :not(:has(...)) carve-out covers both card-headers that DIRECTLY
   contain a nav-tabs ul and ones where the tab list is nested. */
.card-header:not(:has(.nav-tabs)) .btn.btn28 {
    height: 32px !important;
    padding: 0 10px !important;
}

/* Vertical alignment of action buttons inside a tabbed card-header.
   The button's containing <li> defaults to flex `align-items: center`
   against the (taller) tab li's bounding box, which puts the button
   ~3-4px BELOW the tab strip's visual top — reading as misaligned
   because the tab TEXT itself sits near the top of the tab li with
   bottom-padding reserved for the underline indicator. Pin the
   action <li> to flex-start so its top aligns with the tab text top
   row. Applies to any nav-item with a .btn child (per the
   CLAUDE.md card-header-tabs convention). */
.card-header-tabs > .nav-item:has(> .btn) { align-self: flex-start; }
/* Tab icons sized to match the 12px text */
.card-header-pills .nav-link .icon { width: 14px; height: 14px; }

/* Stat widget strip background — greyed so widgets stand apart from the
   toolbar + table below. The top toolbar normally has no top border (so it
   doesn't double up with the card-header's own bottom border), but when it
   sits right after a stat-widgets strip we add a 1px top border to separate
   the grey widgets area from the toolbar below. */
.stat-widgets-wrap { background-color: var(--tblr-bg-surface-tertiary, #f5f7f9); }
.stat-widgets-wrap + .table-toolbar { border-top: 1px solid var(--tblr-border-color-translucent); }

/* Active-filter visual state — when a search input has a value or a
   datepicker range has a .has-filter class (set from JS on non-default
   range), text + icon render in the app's primary blue. */
.input-group:has(.form-control:not(:placeholder-shown)) .btn-search-icon,
.input-group .form-control:not(:placeholder-shown) {
    color: var(--tblr-primary) !important;
}
input[data-litepicker].has-filter,
input[data-litepicker].has-filter::placeholder { color: var(--tblr-primary) !important; }
input[data-litepicker].has-filter { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23206bc4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12'/%3E%3Cpath d='M16 3v4'/%3E%3Cpath d='M8 3v4'/%3E%3Cpath d='M4 11h16'/%3E%3Cpath d='M7 14h.013'/%3E%3Cpath d='M10.01 14h.005'/%3E%3Cpath d='M13.01 14h.005'/%3E%3Cpath d='M16.015 14h.005'/%3E%3Cpath d='M13.015 17h.005'/%3E%3Cpath d='M7.01 17h.005'/%3E%3Cpath d='M10.01 17h.005'/%3E%3C/svg%3E") !important; }

/* Table headers turn blue when their column has an active sort or filter. */
.data-table thead th:has(.col-sort-btn-on),
.data-table thead th:has(.col-filter-btn-on),
.data-table thead th:has(.col-sort-btn-on) a,
.data-table thead th:has(.col-filter-btn-on) a { color: var(--tblr-primary) !important; }

/* Per-page selector — text + chevron turn blue when a non-default value is
   selected (JS toggles .has-filter on the wrapped Tom Select). */
.ts-wrapper.form-select.has-filter,
.ts-wrapper.form-select.has-filter .ts-control,
.ts-wrapper.form-select.has-filter .ts-control .item { color: var(--tblr-primary) !important; }
.ts-wrapper.form-select.has-filter { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23206bc4' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e") !important; }

/* Pagination — chevrons + page number turn blue when current page != 1.
   Exclude .active so the active page keeps its white-on-blue contrast
   instead of turning blue-on-blue. Disabled items (ellipsis, edge buttons
   at boundaries) inherit the muted color from Bootstrap. */
.compact-pg.has-filter .page-item:not(.active):not(.disabled) .page-link { color: var(--tblr-primary) !important; }

/* Make tab borders match the rest of the app's borders (cards, card-header).
   Tabler's active-tab border defaults to the SOLID --tblr-border-color, but
   cards/card-headers use --tblr-border-color-translucent. Align the tab
   variables so the tab box sits visually on the same border line as the
   card below it. Active-tab bottom stays transparent (merges with body).
   Hover bottom also stays transparent so it doesn't stack with the
   card-header's own translucent bottom border (two translucent layers = a
   visibly darker line). */
.nav-tabs {
    --tblr-nav-tabs-border-color: var(--tblr-border-color-translucent);
    --tblr-nav-tabs-link-hover-border-color: var(--tblr-border-color-translucent) var(--tblr-border-color-translucent) transparent;
    --tblr-nav-tabs-link-active-border-color: var(--tblr-border-color-translucent) var(--tblr-border-color-translucent) var(--tblr-border-color-translucent);
}

/* Prevent doubled vertical divider between adjacent tabs: pull every non-first
   nav-item back by -1px so its left border lands in the same column as the
   previous tab's right border. One shared 1px line instead of two side-by-side. */
.nav-tabs .nav-item + .nav-item { margin-left: calc(-1 * var(--tblr-border-width)); }

/* Keep the -1px overlap at every seam so adjacent borders share a single
   1px column (one divider line instead of two side-by-side). */

/* ── Reset/Clear button — white background in all states ─────────────────── */
.btn-white { background-color: #fff !important; border-color: var(--tblr-border-color) !important; color: var(--tblr-secondary) !important; }
.btn-white:disabled, .btn-white[disabled] { background-color: #f1f3f5 !important; border-color: var(--tblr-border-color) !important; opacity: 1 !important; color: var(--tblr-secondary) !important; }
.data-table th:last-child,
.data-table td:last-child { text-align: right; }

/* ── Table scrollbar — horizontal scroll for wide tables ─────────────────── */
.table-responsive                              { overflow-x: auto !important; }
.table-responsive::-webkit-scrollbar           { height: 16px; }
.table-responsive::-webkit-scrollbar-track     { background: var(--tblr-bg-surface-secondary, #f4f6fa); }
.table-responsive::-webkit-scrollbar-thumb     { background: var(--tblr-border-color); border-radius: 6px; }
.table-responsive::-webkit-scrollbar-thumb:hover { background: var(--tblr-secondary); }

/* ── Compact pagination ──────────────────────────────────────────────────── */
.compact-pg .pagination            { font-size: 12px !important; margin: 0; }
.compact-pg .pagination .page-item { line-height: 0; }
.compact-pg .pagination .page-link { font-size: 12px !important; padding: 0 10px !important; height: 28px !important; display: inline-flex; align-items: center; justify-content: center; }
/* Icon-only navigation page-links (prev / next / first / last) are narrower
   than numbered buttons since their content is a 16px chevron. */
.compact-pg .pagination .page-link:has(svg) { padding: 0 5px !important; }

/* ── Column sort + filter buttons ────────────────────────────────────────── */
/* Alignment strategy: vertical-align: middle gets us roughly there, then
   a -1px translateY nudge lifts the 14×14 SVG so its visible centre lines
   up with the cap-height middle of the header text. This is far more
   reliable than playing with vertical-align <length> values — those depend
   on the font's specific baseline metrics and can drift when the font
   stack changes. translateY is mechanical: exactly N pixels, every time. */
.col-sort-btn             { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0; border: none; border-radius: 3px; background: transparent; color: var(--tblr-secondary); cursor: pointer; transition: color .15s; vertical-align: middle; margin-left: 1px; transform: translateY(-1px); }
.col-sort-btn:hover       { color: var(--tblr-primary); }
.col-sort-btn.col-sort-btn-on { color: var(--tblr-primary); }
.col-sort-btn svg         { width: 14px; height: 14px; display: block; }

/* ── Column filter button + dropdown ─────────────────────────────────────── */
/* Wrapper stays inline so its children participate in the same line-box
   as the th's text — inline-block creates its OWN line context and makes
   vertical-align behave inconsistently with the bare sort button. The
   button gets the same vertical-align: middle + translateY(-1px) trick
   as col-sort-btn so both kinds sit on the exact same row regardless of
   the wrapper. */
.col-filter-wrap          { position: relative; display: inline; margin-left: 1px; }
.col-filter-btn           { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0; border: none; border-radius: 3px; background: transparent; color: var(--tblr-secondary); cursor: pointer; transition: color .15s; vertical-align: middle; transform: translateY(-1px); }
.col-filter-btn:hover     { color: var(--tblr-primary); }
.col-filter-btn.col-filter-btn-on       { color: var(--tblr-primary); }
.col-filter-btn svg       { width: 14px; height: 14px; display: block; }
.col-filter-dropdown      { display: none; position: absolute; top: calc(100% - 1px); right: 0; left: auto; z-index: 300; min-width: 150px; max-height: 213px; overflow-y: auto; }
.col-filter-dropdown.open { display: block; }
.col-filter-option        { padding: 6px 10px; font-size: .75rem; cursor: pointer; color: var(--tblr-body-color); white-space: nowrap; }
.col-filter-option:hover  { background: var(--tblr-primary-lt) !important; color: var(--tblr-primary) !important; }
.col-filter-option.col-filter-selected { color: var(--tblr-primary); font-weight: 600; background: var(--tblr-primary-lt); }

/* ── Form dropdown style — grey border, radiused, consistent hover ────────── */
.col-filter-dropdown,
.ts-wrapper .ts-dropdown { background: var(--tblr-card-bg, #fff) !important; border: 1px solid var(--tblr-border-color) !important; border-radius: var(--tblr-border-radius) !important; box-shadow: 0 4px 16px rgba(0,0,0,.08); }
.ts-wrapper .ts-dropdown .option:hover,
.ts-wrapper .ts-dropdown .option.active { background: var(--tblr-primary-lt) !important; color: var(--tblr-primary) !important; }

/* ── Open dropdown popup — detached, 1 px outlined panel ───────────────
   The listbox floats 2px below the control with a thin grey border so
   its boundary is unambiguous on busy backgrounds. Drop shadow adds a
   little depth on top of the border. */
body > .ts-dropdown,
.ts-wrapper .ts-dropdown {
    border: 1px solid var(--tblr-border-color) !important;
    border-radius: var(--tblr-border-radius) !important;
    margin-top: 2px !important;
}

/* ── Modal overrides — scrollable body, match page font size, small buttons ── */

/* ── Tom Select — match app form control + badge styles ─────────────────── */
/* Every Tom Select wrapper gets position: relative so the absolutely-
   positioned nested dropdown anchors against it (otherwise it climbs to
   the nearest positioned ancestor, which can produce weird offsets).
   Inactive wrappers stay at z-index auto so they don't pollute the
   stacking order; the active one bumps to 1500 — well clear of modals
   (1055) AND any neighbouring inactive Tom Select wrapper whose own
   containing-block might otherwise stack on top of the open dropdown
   (this was causing the cutting-die dropdown to render behind the
   lamination Tom Select that sits below it). */
.ts-wrapper { position: relative; }
.ts-wrapper.dropdown-active { z-index: 1500; }
.ts-wrapper.dropdown-active .ts-dropdown { z-index: 1500; }
.ts-wrapper .ts-dropdown { z-index: 1500; }
/* Allow Tom Select dropdown to escape overflow clipping from any common
   ancestor (card, card-body, table wrapper, tab panes).
   NOTE: .modal-body / .modal-content are deliberately EXCLUDED. Tom Select
   popups are body-attached (dropdownParent:'body') so they already escape
   any modal clipping — and forcing .modal-body to overflow:visible destroys
   a .modal-dialog-scrollable modal's scroll containment: the dialog grows
   to full content height, the footer floats into the body, and tier-role
   selects near the bottom push their (body-attached) dropdown off-screen.
   That was the "modal breaks when I open a tier dropdown" bug. */
.card:has(.ts-wrapper.dropdown-active),
.card-body:has(.ts-wrapper.dropdown-active),
.table-responsive:has(.ts-wrapper.dropdown-active),
.tab-content:has(.ts-wrapper.dropdown-active),
.tab-pane:has(.ts-wrapper.dropdown-active),
/* Column-header filter dropdowns (.col-filter-dropdown.open) are absolutely
   positioned below the filter button. Any ancestor with overflow:hidden or
   overflow-x:auto clips them — let them escape the table wrapper and card. */
.card:has(.col-filter-dropdown.open),
.card-body:has(.col-filter-dropdown.open),
.table-responsive:has(.col-filter-dropdown.open),
.tab-content:has(.col-filter-dropdown.open),
.tab-pane:has(.col-filter-dropdown.open) { overflow: visible !important; }
/* FL-134 — the per-row Actions dropdown (Bootstrap dropdown wrapped by
   .row-actions-dropdown) sits inside a .table-responsive; lift the same
   ancestor clipping as the other dropdown families when it's open. */
.card:has(.row-actions-dropdown .dropdown-menu.show),
.card-body:has(.row-actions-dropdown .dropdown-menu.show),
.table-responsive:has(.row-actions-dropdown .dropdown-menu.show),
.tab-content:has(.row-actions-dropdown .dropdown-menu.show),
.tab-pane:has(.row-actions-dropdown .dropdown-menu.show) { overflow: visible !important; }
/* Form-inside-dropdown — strip the default <form> margin so the wrapped
   submit button keeps the dropdown item's height and padding clean. */
/* Wrapper shrinks to the trigger (instead of filling the text-end cell) so the
   menu's left:0 below anchors to the BUTTON's left, not the cell's. */
.row-actions-dropdown { display: inline-block; }
.row-actions-dropdown .dropdown-menu form { margin: 0; }
/* Compact menu. Bootstrap/Tabler force a ~10rem min-width on every dropdown,
   so a 2-3 item action menu was padded out far wider than its labels — hug
   the content instead, and tighten the menu's own vertical padding so the box
   matches the small btn28 trigger. */
.row-actions-dropdown .dropdown-menu {
    min-width: auto;
    --tblr-dropdown-min-width: auto;
    width: max-content;
    padding-top: 0;
    padding-bottom: 0;
    /* Flush the menu's LEFT edge to the trigger's left edge (opens rightward),
       instead of the static/right-anchored position. */
    left: 0 !important;
    right: auto !important;
}
/* Tight items: small top/bottom padding, equal L/R, compact icon close to the
   label. (Tabler sizes .icon with !important, so the icon needs it too.) */
.row-actions-dropdown .dropdown-menu .dropdown-item { text-align: left; font-size: 12px; line-height: 18px; padding: .3rem .6rem; min-width: 0; }
/* Flush divider — no space above/below (Bootstrap default .5rem, then .15rem,
   now zero per request). */
.row-actions-dropdown .dropdown-menu .dropdown-divider { margin: 0; }
.row-actions-dropdown .dropdown-menu .dropdown-item .dropdown-item-icon { width: 16px !important; height: 16px !important; margin-inline-end: .15rem; }
/* Vertically centre the dropdown caret — Bootstrap's vertical-align trick is
   ignored inside the flex .btn, leaving the ▾ sitting low under the label. */
.btn.dropdown-toggle::after { vertical-align: middle !important; }
/* Inline status / field selects keep the app's Tom Select chrome, but with a
   FIXED width so they don't jump as the selected label changes between rows.
   The wrapper selector outranks `.ts-wrapper.form-select { width:100% }` above
   (which otherwise content-sizes via the shrink-wrap table cell). */
.status-select,
.ts-wrapper.form-select.status-select { width: 7.5rem !important; min-width: 7.5rem !important; max-width: 7.5rem !important; flex: 0 0 7.5rem !important; }
/* Per-page selector — same Tom Select width-pin so it can't grow when opened.
   Only ever holds 25/50/100, so a tight fixed width is safe (the global
   initTomSelects wraps it like every .form-select; w-auto otherwise lets the
   filter input expand the control on focus). */
.tbl-perpage,
.ts-wrapper.form-select.tbl-perpage { width: 4.5rem !important; min-width: 4.5rem !important; max-width: 4.5rem !important; flex: 0 0 4.5rem !important; }
/* Match form-control height and border-radius — must override tom-select.bootstrap5.
   Keep Bootstrap's default .form-select chevron SVG on the right; the ts-control
   gets 28px right padding to clear it. */
.ts-wrapper.form-select { padding: 0 !important; height: 32px !important; border: 1px solid var(--tblr-border-color) !important; border-radius: var(--tblr-border-radius-sm, 4px) !important; background-position: right 0.5rem center !important; background-size: 16px 12px !important; width: 100% !important; box-sizing: border-box !important; max-width: 100% !important; min-width: 0 !important; }
/* FL-141 — opt-in width helper for Tom-Select-wrapped filter dropdowns.
   Tom Select copies the original element's classes onto .ts-wrapper, so
   tagging the native <select> with .ts-wide bumps the wrapper too. The
   bare-select rule catches pre-wrap state + non-wrapped selects. */
.ts-wrapper.form-select.ts-wide,
select.form-select.ts-wide { min-width: 260px !important; }
/* `width: 100%` on .ts-wrapper.form-select (above) is the right cap inside
   flex-grow contexts (calculator / quote-edit material + substrate pickers)
   but kills inline filter dropdowns in a `.table-toolbar` where the <select>
   was explicitly tagged `w-auto` so it sizes to its content. Honor the
   `w-auto` opt-out: wrappers that copied it stay auto-width, with `ts-wide`
   still floor-pinning to 260px when both classes are set. */
.ts-wrapper.form-select.w-auto { width: auto !important; max-width: none !important; }
.ts-wrapper.form-select .ts-control { border: none !important; border-radius: var(--tblr-border-radius-sm, 4px) !important; min-height: 30px !important; height: 30px !important; padding: 0 28px 0 8px !important; gap: 4px; align-items: center; background: transparent !important; flex-wrap: nowrap !important; width: 100% !important; box-sizing: border-box !important; min-width: 0 !important; max-width: 100% !important; overflow: hidden !important; }

/* Flexbox min-width gotcha — when a Tom Select wrapper sits inside a flex
   item (e.g. `.input-icon-wrap.flex-grow-1` next to a "List" button on
   the calculator + quote-edit material/substrate pickers), the default
   `min-width: auto` on the flex item lets the wrapper's content (the
   picked option's label) push the item wider. Result: selecting a long
   substrate or material name jumps the row width from e.g. 263px to
   522px, doubling the layout.

   Force `min-width: 0` on the flex item so flex can shrink it below its
   content's intrinsic width — the wrapper inside then caps at its
   parent's allocated width, and the picked label truncates with
   ellipsis (rule above) instead of expanding the row. */
.input-icon-wrap { min-width: 0; }
.d-flex > *:has(> .ts-wrapper.form-select) { min-width: 0; }
/* The selected option label inside the ts-control must truncate rather
   than push the control wider when a long name is picked. Without this,
   ts-control flexes its width to fit the .item, making the whole wrapper
   visibly shift between placeholder state and selected-state. */
.ts-wrapper.form-select .ts-control > .item {
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* ── Tags chip input (Tom Select multi on a .form-control <input>) ─────────
   The tag field (#mm_tags, materials modal) is a free-tagging Tom Select. The
   bootstrap5 base locks .ts-control to a single-row height with overflow:hidden,
   so once tags wrap to a 2nd row the extra chips AND the "type & press Enter"
   field get clipped/hidden — and the wide-min-width type-field jumped to that
   hidden row (the reported "shift" in the placeholder text). Let the control
   grow to fit every chip, give chips even spacing + room around the × button,
   and a small min-width on the type-field so it flows inline after the chips and
   wraps cleanly. Scoped to .ts-tags (added in materials initTagsChip) so the
   .form-select Tom Selects above are untouched. */
.ts-tags.ts-wrapper.form-control { height: auto !important; min-height: 32px; }
.ts-tags .ts-control { height: auto !important; min-height: 0 !important; overflow: visible !important; padding: 4px 8px; row-gap: 4px; align-items: center; }
.ts-tags .ts-control > .item { margin: 0 5px 0 0; padding: 1px 2px 1px 8px; line-height: 1.5; }
.ts-tags .ts-control > .item .remove { padding: 0 6px; margin-left: 4px; border-left-color: var(--tblr-border-color); }
.ts-tags .ts-control > input { min-width: 6rem !important; flex: 1 1 6rem; }
/* ── Remote-search loading state ────────────────────────────────────────
   Tom Select sets `.loading` on the wrapper while the AJAX `load:` is in
   flight (customer, substrate, lamination dropdowns). Replace the chevron
   background with a rotating blue spinner so the operator sees the fetch
   is happening — and hide the default in-dropdown spinner row (the
   render.loading override returns '' but Tom Select still renders the
   built-in `.spinner` node otherwise). */
.ts-wrapper.form-select { position: relative; }
.ts-wrapper.form-select.loading { background-image: none !important; }
.ts-wrapper.form-select.loading::after {
    content: '';
    position: absolute;
    top: 50%;
    right: 10px;
    width: 14px;
    height: 14px;
    margin-top: -7px;
    border: 2px solid var(--tblr-primary);
    border-top-color: transparent;
    border-radius: 50%;
    animation: ts-loading-spin 0.7s linear infinite;
    pointer-events: none;
}
@keyframes ts-loading-spin {
    to { transform: rotate(360deg); }
}
/* Belt-and-braces: kill Tom Select's built-in in-dropdown spinner node. */
.ts-wrapper .ts-dropdown .spinner,
body > .ts-dropdown .spinner { display: none !important; }
/* Single-select: kill the gap between .item and the typing input so the
   cursor sits flush against the last character of the selected label
   instead of floating a few pixels away. Also zero the input's own left
   padding/margin/min-width so nothing else opens that gap back up. */
.ts-wrapper.single.form-select .ts-control { gap: 0 !important; }
.ts-wrapper.single .ts-control > input {
    margin: 0 !important;
    padding: 0 !important;
    min-width: 0 !important;
}
.ts-wrapper.form-select.focus { border-color: var(--tblr-primary) !important; }
/* Tom Select bootstrap5 paints its own border/box-shadow on .ts-control via
   .ts-wrapper.focus .ts-control — kill it so only our wrapper border and
   focus-ring above take effect (otherwise two layers of colour stack). */
.ts-wrapper.form-select.focus .ts-control { border: none !important; box-shadow: none !important; }
/* ── Tom Select — bulletproof hiding of the underlying native <select> ──
   The hidden <select> is meant to be invisible (1px box, clip-path inset),
   but our own `.form-select.is-invalid` rules add padding-right + red
   border-color WITHOUT !important on Tom Select's hide-styles. When the
   native select still carries .is-invalid (set by browser autovalidation,
   stale state, or any other path), a ~20px red sliver peeks out at the
   left edge of the .ts-wrapper. Specificity here is (0,2,1) — higher
   than `.form-select.is-invalid` (0,2,0) — so this beats every validation
   rule we have. Locks size, padding, border, background, shadow regardless
   of the validity class on the element. */
select.ts-hidden-accessible,
select.ts-hidden-accessible.is-invalid,
select.ts-hidden-accessible.is-valid {
    border: 0 !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    background-image: none !important;
    box-shadow: none !important;
}
/* ── Tom Select — validation styling for the wrapper itself ─────────────
   Tom Select copies the original <select>'s classes onto .ts-wrapper, so
   when validation tags the wrapper with .is-invalid, two issues surface:

   1. The generic `.form-select.is-invalid` rule (line ~920) forces
      `padding-right: 0.75rem !important` to make room for an error icon.
      We turned the icon off (background-image: none), but the padding
      still pushes the inner .ts-control 12px in from the wrapper's right
      border — making the red outline look noticeably wider than the
      "select content" inside. Restore the wrapper's normal zero padding.

   2. Border-color: red, beating Tom Select's default grey wrapper border
      AND our `.ts-wrapper.form-select.focus` blue. The 4-class focus
      override below has specificity (0,4,0), one class more than the
      existing focus rule (0,3,0), so red wins when both apply. */
/* Invalid state — solid red border ONLY. No focus halo, no inner-element
   shadows. The "two red outlines" effect users reported came from the
   wrapper's 4px red focus halo box-shadow stacking on top of the 1px
   solid red border. Killing the halo leaves a single, unambiguous
   validation indicator. */
.ts-wrapper.form-select.is-invalid,
.ts-wrapper.form-select.is-invalid.focus {
    padding: 0 !important;
    border-color: var(--tblr-danger) !important;
    box-shadow: none !important;
}
/* Belt-and-braces: when the wrapper is .is-invalid, the inner .ts-control
   must never paint its own border, shadow, or outline. Tom Select
   bootstrap5's `.ts-wrapper.is-invalid.focus .ts-control` rule applies a
   red border + red box-shadow that visually stacks on top of OUR wrapper's
   red outline. This rule catches every invalid-state combination. */
.ts-wrapper.is-invalid .ts-control,
.ts-wrapper.is-invalid.focus .ts-control,
.ts-wrapper.is-invalid .ts-control > input,
.ts-wrapper.is-invalid.focus .ts-control > input {
    border: none !important;
    box-shadow: none !important;
    outline: none !important;
}
/* ── Fixed-width Tom Select wrappers in flex containers ─────────────────
   In a parent like `<div class="d-flex gap-2"><select class="flex-grow-1"/>
   <button/></div>`, the Tom Select wrapper inherits .flex-grow-1 from the
   original <select> — but the inner .ts-control + .item default to their
   natural content width, so a long selected label balloons the wrapper
   past the column allocation. min-width:0 lets the flex container shrink
   the wrapper to the available space; the .item then truncates with an
   ellipsis instead of forcing the parent wider. */
.ts-wrapper.form-select { min-width: 0 !important; }
.ts-wrapper.single.form-select .ts-control { min-width: 0; overflow: hidden; }
.ts-wrapper.single.form-select .ts-control .item {
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* ── Tom Select input placeholder — explicit grey so the empty-state
   prompt reads as a hint (not as black, as the typed value would). The
   Tom Select <input> sits inside .ts-control and doesn't carry the
   .form-control class, so Tabler's own .form-control::placeholder rule
   never lands on it — we have to target this directly. */
.ts-wrapper .ts-control > input::placeholder {
    color: var(--tblr-secondary, #6c757d) !important;
    opacity: 1 !important;
}
/* ── Empty-option placeholder text — muted grey, not value-black ────────
   The empty <option value=""> carries the prompt ("Select" / "Seçiniz").
   Render it in secondary-grey so it reads as a hint rather than a real
   chosen value. Covers the selected-item view inside .ts-control AND the
   row inside the open dropdown, including the body-attached dropdown
   (`body > .ts-dropdown` — Tom Select moves the popup there). The :hover
   + .active branches keep the row from flipping back to primary-blue
   when the dropdown's keyboard cursor or pointer lands on it. */
.ts-wrapper .ts-control .item[data-value=""],
.ts-wrapper .ts-dropdown .option[data-value=""],
.ts-wrapper .ts-dropdown .option[data-value=""]:hover,
.ts-wrapper .ts-dropdown .option[data-value=""].active,
body > .ts-dropdown .option[data-value=""],
body > .ts-dropdown .option[data-value=""]:hover,
body > .ts-dropdown .option[data-value=""].active {
    color: var(--tblr-secondary, #6c757d) !important;
    background: transparent !important;
}
/* Multi-select — selected items as azure badges */
.ts-wrapper.multi .ts-control .item { background: rgba(var(--tblr-azure-rgb), 0.1) !important; color: var(--tblr-azure) !important; border: none !important; border-radius: var(--tblr-border-radius, 4px) !important; padding: 2px 6px; font-size: 11px; font-weight: 500; line-height: 1.4; }
.ts-wrapper.multi .ts-control .item .remove { color: var(--tblr-azure) !important; font-size: 14px; margin-left: 4px; }
.ts-wrapper.multi .ts-control .item .remove:hover { color: var(--tblr-danger) !important; }
/* Single-select — plain text, keep arrow indicator. white-space: nowrap
   prevents the item from wrapping to a second flex-row inside the control
   (the original "line break in stock select" bug). The item is allowed
   to take its natural content width — no max-width / flex constraint —
   so labels like "Always in stock" / "Usually in stock" never get
   truncated with an ellipsis just because the column is narrow. */
.ts-wrapper.single .ts-control .item {
    background: transparent !important;
    color: inherit !important;
    border: none !important;
    padding: 0;
    font-size: 12px;
    white-space: nowrap;
}
/* Tom Select dropdown — inherits global dropdown style, no extra padding */
.ts-wrapper .ts-dropdown { padding: 0 !important; margin-top: 1px; overflow: hidden !important; }
/* Make the dropdown content's overflow allow horizontal scroll too if a
   very long label can't fit even after ellipsis — better than letting
   the label wrap. Vertical scroll for long lists stays. */
.ts-wrapper .ts-dropdown .ts-dropdown-content { border-radius: var(--tblr-border-radius) !important; padding: 0 !important; max-height: 213px; overflow-y: auto; overflow-x: hidden; }
.ts-wrapper .ts-dropdown .option {
    padding: 6px 10px; font-size: 12px; line-height: 1.5; cursor: pointer;
    /* Single-line options — prevents long labels from breaking onto
       a second line inside the option row. The dropdown itself grows
       to fit the widest option's content (Tom Select's default), so
       no truncation is needed; just stop the wrap. */
    white-space: nowrap;
}
/* Input text inside control */
.ts-wrapper .ts-control > input { font-size: 12px; }
.modal-header            { background-color: #f9fafb; }
.modal-body              { font-size: 13px; }
.modal-body .form-label,
.card-body .form-label   { font-size: 13px; }
.modal-body .form-control,
.modal-body .form-select,
.card-body .form-control,
.card-body .form-select  { font-size: 13px; }
.modal-body .form-hint   { font-size: 12px; }

/* ── Modal footer — Cancel button always pinned to the left ─────────────── */
.modal-footer .btn-ghost-secondary { margin-right: auto !important; }

/* ── Flow modal sizes — 4 fixed sizes used app-wide.
     Apply by adding the modifier to .modal-dialog (NOT to .modal).
       .modal-dialog.modal-confirm  — 400 × 200 (confirm / "are you sure?")
       .modal-dialog.modal-small   — 400 × 480 (short edit forms)
       .modal-dialog.modal-middle  — 640 × 480 (standard edit forms)
       .modal-dialog.modal-big     — 1024 × 480 (complex forms)
     Height is applied to the .modal-content inside so the dialog centers
     correctly. Body scrolls when content overflows the fixed height.     */
.modal-dialog.modal-confirm,
.modal-dialog.modal-small,
.modal-dialog.modal-small-tall,
.modal-dialog.modal-middle,
.modal-dialog.modal-middle-tall,
.modal-dialog.modal-big,
.modal-dialog.modal-big-tall,
.modal-dialog.modal-picker { max-width: 100%; }

.modal-dialog.modal-confirm     { width: 400px; }
.modal-dialog.modal-small,
.modal-dialog.modal-small-tall  { width: 400px; }
.modal-dialog.modal-middle,
.modal-dialog.modal-middle-tall { width: 640px; }
.modal-dialog.modal-big,
.modal-dialog.modal-big-tall    { width: 1024px; }
/* modal-picker — 1200×600 caps for the materials picker. max-width above
   keeps it from blowing past the viewport on smaller screens. */
.modal-dialog.modal-picker      { width: 1200px; }

.modal-dialog.modal-confirm > .modal-content { height: 200px; }
.modal-dialog.modal-small   > .modal-content { height: 480px; }
.modal-dialog.modal-middle  > .modal-content { height: 480px; }
.modal-dialog.modal-big     > .modal-content { height: 480px; }
.modal-dialog.modal-picker  > .modal-content { height: 600px; }
/* Material details modal — 13-row dl needs a touch more headroom than
   modal-middle's 480px. ID-scoped so other modal-middle users aren't
   affected. Bumped to 510 — the previous 500 still felt cramped. */
#materialDetailsModal .modal-dialog.modal-middle > .modal-content { height: 510px; }
/* Material picker table — allow text wrapping in body cells so columns
   auto-size to actual content instead of stretching to the longest
   unbroken material name. The Cost column (5th) and the action-button
   column (last) keep nowrap: a wrapped "1.234,56 €" reads as garbage,
   and the two action buttons must stay on a single row. */
#materialPickerModal .table.data-table td,
#materialPickerModal .table.data-table th { white-space: normal; }
#materialPickerModal .table.data-table td:nth-child(5),
#materialPickerModal .table.data-table th:nth-child(5) { white-space: nowrap; }
#materialPickerModal .table.data-table td:last-child,
#materialPickerModal .table.data-table th:last-child { white-space: nowrap; width: 1%; }

/* Tall variants — same widths as the standard sizes, height bumped to 640 */
.modal-dialog.modal-small-tall  > .modal-content,
.modal-dialog.modal-middle-tall > .modal-content,
.modal-dialog.modal-big-tall    > .modal-content { height: 640px; }

/* Many modals wrap header/body/footer in a <form> — make that form behave
   as a flex-column child so .modal-body can still flex-grow + scroll. */
.modal-dialog[class*="modal-"] > .modal-content { display: flex; flex-direction: column; }
.modal-dialog[class*="modal-"] > .modal-content > form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.modal-dialog[class*="modal-"] .modal-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.modal-dialog[class*="modal-"] .modal-header,
.modal-dialog[class*="modal-"] .modal-footer,
.modal-dialog[class*="modal-"] > .modal-content > .card-header { flex: 0 0 auto; }

/* .card-header used inside .modal-content (not wrapped in a .card) — Tabler's
   default .card > .card-header rule doesn't match, so give the header its own
   padding + bottom border so it reads as a proper header strip. Top corners
   inherit the modal-content's 8px radius so the grey bg doesn't poke out of
   the rounded modal corners. */
.modal-content > .card-header {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--tblr-border-color-translucent);
    min-height: 48px;
    display: flex;
    align-items: center;
    border-top-left-radius: var(--tblr-modal-border-radius, 8px);
    border-top-right-radius: var(--tblr-modal-border-radius, 8px);
}

/* Localization page — card-options inputs need to be 28px to match the
   btn28 next to them. The global `.form-control { height: 32px !important }`
   rule above is too greedy, so scope the override to this card header. */
.card-header .form-control-sm,
.card-header .form-select-sm {
    height: 28px !important;
    min-height: 28px !important;
    padding: 3px 8px !important;
    font-size: 12px !important;
}

/* On narrow viewports, drop the fixed width so the dialog stays usable */
@media (max-width: 1100px) {
    .modal-dialog.modal-big,
    .modal-dialog.modal-big-tall { width: 100%; max-width: calc(100% - 1rem); }
}
@media (max-width: 720px) {
    .modal-dialog.modal-middle,
    .modal-dialog.modal-middle-tall { width: 100%; max-width: calc(100% - 1rem); }
}
@media (max-width: 480px) {
    .modal-dialog.modal-confirm,
    .modal-dialog.modal-small,
    .modal-dialog.modal-small-tall { width: 100%; max-width: calc(100% - 1rem); }
}
/* On short viewports, release the fixed height so content stays usable */
@media (max-height: 540px) {
    .modal-dialog.modal-small        > .modal-content,
    .modal-dialog.modal-middle       > .modal-content,
    .modal-dialog.modal-big          > .modal-content { height: auto; max-height: calc(100vh - 2rem); }
}
@media (max-height: 700px) {
    .modal-dialog.modal-small-tall   > .modal-content,
    .modal-dialog.modal-middle-tall  > .modal-content,
    .modal-dialog.modal-big-tall     > .modal-content { height: auto; max-height: calc(100vh - 2rem); }
}

/* ── Modal header / footer borders — match the light card-header grey ──── */
.modal-header { border-bottom-color: #e6e7e9; }
.modal-footer { border-top-color:    #e6e7e9; }

/* ── Card-link adjacency reset — Tabler's stock rule
       `.card-link + .card-link { margin-left: var(--tblr-card-spacer-x); }`
       assumes the cards are arranged horizontally as a row of clickable
       tiles. In flow we stack card-links vertically (dashboard right
       column: Quote Pipeline + Materials), where the inherited 16px
       left margin makes the second card 16px narrower than its siblings.
       Zero it out — vertical spacing comes from `.mb-3` on each card. */
.card-link + .card-link { margin-left: 0 !important; }

/* ── Pricing-tier chip (FL-139) — read-only chip on the calculator's
       Order Info column. Looks like a form-control but with a muted
       background and default cursor to signal "not editable". The bold
       tier name + dimmed discount/VAT detail give the eye a clear
       primary/secondary split without needing extra markup. */
.pricing-tier-chip {
    background-color: #f6f8fb !important;
    color:            var(--tblr-body-color);
    cursor:           default;
    line-height:      1.4;
    display:          flex;
    align-items:      center;
    flex-wrap:        wrap;
}
.pricing-tier-chip:hover,
.pricing-tier-chip:focus { border-color: var(--tblr-border-color) !important; box-shadow: none !important; }

/* ── Process column — disabled-section visual cue ───────────────────────────
   Applied by calculator-render.js when the Print Type radio is toggled to
   "Blank". Native `disabled` on the inputs/buttons handles interaction
   blocking; this class greys out the section header text + container so
   the operator sees the whole block as inactive instead of just the
   individual controls. */
.process-section-off { opacity: 0.55; }
.process-section-off label { color: var(--tblr-secondary, #6c757d) !important; }

/* ── CMYK row — zero `.form-check-inline`'s trailing margin so K sits flush
   with the right edge of the Process column. The parent .d-flex.gap-2
   already provides inter-checkbox spacing; Bootstrap's default 1rem
   margin-right on `.form-check-inline` adds an unwanted gap AFTER the last
   checkbox and double-spaces between checkboxes. Scoped to .cmyk-row so
   other inline-checkbox layouts on the site keep their default spacing.
   The 5px flex gap tightens the spacing between adjacent CMYK boxes
   (overriding Bootstrap's gap-2 = 8px); `me-auto` on the first label
   absorbs the rest of the row width so the gap only affects the
   C/M/Y/K group on the right. */
.cmyk-row { gap: 15px !important; }
.cmyk-row .form-check-inline { margin-right: 0 !important; }

/* ── Calculator production-prediction card — slimmer header ─────────────────
   The pricing card's header is naturally tall because it carries the
   machine name + method label + OK/INCOMPATIBLE badge. The production
   card's header just says "Production", so the default card-header
   padding leaves a lot of empty space and reads as out-of-scale. Cut the
   vertical padding and drop the title's font-size so the prediction card
   header sits as a compact label strip rather than a full-height title bar. */
.machine-prediction-card > .card-header { padding-top: 0.4rem !important; padding-bottom: 0.4rem !important; }
.machine-prediction-card > .card-header .card-title { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4pt; color: var(--tblr-secondary, #6c757d); }

/* ── Form section titles — inside .modal-body and .card-body ─────────────── */
.modal-body .form-section-title,
.card-body  .form-section-title { font-size: 15px; font-weight: 600; color: var(--tblr-body-color); margin-bottom: 0.5rem; }
.modal-body .form-section-separator,
.card-body  .form-section-separator { margin: 1rem 0; border-top: 1px solid var(--tblr-border-color); opacity: 1; }

/* ── Footer separator — divider between page body and footer ────────────── */
.footer { border-top: 1px solid #e6e7e9; margin-top: 1.5rem; }

/* ── Validation — red border + hint only; no X icon, stay red on focus ────── */
.form-control.is-invalid,
.form-select.is-invalid,
.was-validated .form-control:invalid,
.was-validated .form-select:invalid {
    background-image: none !important;
    padding-right: 0.75rem !important;
}
/* High-specificity override — ensure invalid focus stays red (beats any .form-control:focus rule) */
html .form-control.is-invalid:focus,
html .form-select.is-invalid:focus,
html .form-control.is-invalid:focus-within,
html .was-validated .form-control:invalid:focus,
html .was-validated .form-select:invalid:focus {
    border-color: #d63939 !important;
    box-shadow: 0 0 0 4px rgba(214, 57, 57, 0.25) !important;
    outline: none !important;
}
/* Keep input-group look in sync when the inner input is invalid */
.input-group:has(.form-control.is-invalid) .input-group-text { border-color: var(--tblr-danger); }

/* Invalid-feedback inside an input-group must wrap to its own line */
.input-group { flex-wrap: wrap; }
.input-group > .invalid-feedback { width: 100%; order: 99; flex-basis: 100%; }
/* When invalid-feedback sits as a sibling AFTER the input-group, still show it */
.input-group:has(.form-control.is-invalid) + .invalid-feedback,
.input-group:has(.form-select.is-invalid) + .invalid-feedback { display: block; }

/* ── Button size convention ───────────────────────────────────────────────
   Three explicit heights — use the class that matches the context:
     .btn28 — inside tables / dense grids
     .btn32 — default elsewhere (matches bare .btn and 32px inputs)
     .btn36 — large CTAs, page-action primary buttons, auth forms
   Bare .btn defaults to 32px. Always pair colour variants (.btn-primary etc.)
   with an explicit size class where intent matters. */
.btn.btn28,
.btn-group > .btn.btn28 {
    height: 28px !important;
    padding: 0 8px !important;
    font-size: 12px !important;
    line-height: 1 !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0 !important;
}
.btn.btn32,
.btn-group > .btn.btn32 {
    height: 32px !important;
    padding: 0 10px !important;
    font-size: 12px !important;
    line-height: 1 !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0 !important;
}
.btn.btn36,
.btn-group > .btn.btn36 {
    height: 36px !important;
    padding: 0 12px !important;
    font-size: 13px !important;
    line-height: 1 !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0 !important;
}
/* Kill Tabler's built-in asymmetric icon margins inside buttons (-4px left,
   +8px right via calc) plus any me-*/ms-* utility margins. Spacing between
   icon and text is controlled solely by the `gap` set on each size class, so
   horizontal padding stays visually equal. */
.btn .icon, .btn .icon-start, .btn .icon-left,
.btn .icon-end, .btn .icon-right, .btn > svg { margin: 0 !important; }
/* Clamp min-width to the actual icon width so Tabler's 20px bounding-box
   overage is eliminated — no hidden whitespace to the right of the SVG.
   Gap and button padding are untouched. */
.btn28 .icon, .btn28 > svg { width: 14px; height: 14px; min-width: 14px !important; flex-shrink: 0; }
.btn32 .icon, .btn32 > svg { width: 16px; height: 16px; min-width: 16px !important; flex-shrink: 0; }
.btn36 .icon, .btn36 > svg { width: 18px; height: 18px; min-width: 18px !important; flex-shrink: 0; }
.btn28 .spinner-border-sm { width: 12px; height: 12px; border-width: 1.5px; }
.btn32 .spinner-border-sm { width: 14px; height: 14px; border-width: 1.5px; }
.btn36 .spinner-border-sm { width: 16px; height: 16px; border-width: 1.5px; }

/* ── Mobile responsive fixes ─────────────────────────────────────────────── */
@media (max-width: 767.98px) {
    /* Reduce permission matrix column widths on mobile */
    .permissions-matrix th,
    .permissions-matrix td { min-width: 80px; font-size: 0.75rem; }

    /* Modal full-width on mobile */
    .modal-dialog { margin: 0.5rem; max-width: calc(100% - 1rem); }

    /* Stack form elements */
    .modal .row .col-sm-3 { margin-bottom: 0.25rem; }
}

/* ── Inline-style replacements (audit FL-52) ─────────────────────────────── */

/* Company-settings logo preview frame — fixed height for consistent
   placeholder/image swap on the singleton settings page. */
.company-logo-preview { max-height: 120px; }
.company-logo-placeholder { height: 120px; }

/* Localization editor — original-string column reserves 45% so long Turkish
   translations get the wider edit cell. */
.col-localization-key { width: 45%; }

/* Customers table — Mikro company names can be very long (full corporate titles
   with "SANAYİ VE TİCARET LİMİTED ŞİRKETİ" etc.). Cap the name cell width and
   truncate with ellipsis; full name still visible on hover via title attribute.
   Tightened to 280px now that Tier + 2-button Actions cell share the row. */
.customer-name-cell      { max-width: 280px; }
.customer-name-text      { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.customer-actions-cell   { white-space: nowrap; }

/* Source icons — compact alternative to ERP/local chips. One icon per row tells
   the user where the record came from; the tooltip carries the long form. */
.source-icon             { display: inline-flex; align-items: center; }
.source-icon .icon       { width: 16px; height: 16px; }

/* Quote show — price table inside an item card. Two fixes:
   1) Suppress the last tbody row's bottom border so it doesn't double with the
      cost-breakdown section's top edge.
   2) Give cells consistent vertical padding (Tabler's card-table goes flush at
      card edges; we still want a little breathing room top/bottom). */
.quote-price-table > tbody > tr:last-child > td { border-bottom: 0; }
.quote-price-table > thead > tr > th,
.quote-price-table > tbody > tr > td            { padding-top: .55rem; padding-bottom: .55rem; }

/* Price cells — operator picks ONE per item. Drafts get a pointer cursor +
   hover tint; the picked cell shows a solid primary-tinted background +
   white check icon in the corner. Issued quotes lock the selection in
   place but still highlight the chosen cell so the agreed price reads
   first at a glance. */
.price-cell                      { transition: background-color .12s ease, color .12s ease; position: relative; }
.price-cell                      { position: relative; }
.price-cell-clickable            { cursor: pointer; }
.price-cell-clickable:hover      { background-color: var(--tblr-primary-lt); }
/* Selected — operator's pick. Blue tint + blue dot in TOP-RIGHT corner. */
.price-cell-selected             { background-color: var(--tblr-primary-lt) !important; }
.price-cell-selected::after      { content: ""; position: absolute; top: 4px; right: 4px;
                                   width: 10px; height: 10px; border-radius: 50%;
                                   background: var(--tblr-primary); }
/* Best price — informational accent on the cheapest cell per qty row across
   all machine cards. Green dot in TOP-LEFT corner so it can coexist with
   the selection dot in the top-right. */
.price-cell-best::before         { content: ""; position: absolute; top: 4px; left: 4px;
                                   width: 8px; height: 8px; border-radius: 50%;
                                   background: var(--tblr-success); }

/* Locked / read-only state for ERP-managed input fields in the customer modal. */
.form-control[readonly].bg-secondary-lt,
.form-select[readonly].bg-secondary-lt { cursor: not-allowed; }

/* ══════════════════════════════════════════════════════════════════════════
   FL-123 — bulk select column + bulk-actions toolbar.
   Used by Flow.BulkTable on any index page (materials/customers/quotes).
   ══════════════════════════════════════════════════════════════════════════ */

/* Compact checkbox column — both header (th) and body (td) cells. The cell
   has just enough width for the input plus the table's normal vertical
   padding, with the checkbox itself centered horizontally. */
.bulk-select-cell {
    width: 32px;
    padding-left: 12px !important;
    padding-right: 4px !important;
    text-align: center;
}
.bulk-select-cell .form-check-input {
    margin: 0;
    cursor: pointer;
    vertical-align: middle;
}

/* Selected-row highlight — subtle primary tint with a left accent stripe.
   Uses box-shadow inset for the accent so it doesn't disturb table layout. */
tr.bulk-selected {
    background-color: rgba(32, 107, 196, 0.06);
    box-shadow: inset 3px 0 0 var(--tblr-primary);
}

/* Bulk-actions slot in card-actions — make the count badge stand out a bit
   more than a regular badge by giving it a small "pill" feel. */
[data-bulk-count-display] {
    font-size: 12px;
    font-weight: 500;
    padding: 2px 8px;
    vertical-align: middle;
}


/* ════════════════════════════════════════════════════════════════════════
   Merged from public/vendor/calculator-style.css (2026-05-17)

   Used on the calculator playground AND the quote-edit form. Was a
   separate stylesheet; merged so dropdown / validation / input rules
   stop fighting each other across two files (the "0.8 px ghost border
   on dropdowns" bug came from a higher-specificity rule in
   calculator-style.css winning against the global one in this file).

   Dropped from the original on merge:
     - .ts-wrapper.is-invalid .ts-control { border-color: red }
       — superseded by the bulletproof border:none rule above.
     - .form-control.is-invalid / .form-select.is-invalid
       — duplicated by the validation rules above.
     - html body .ts-dropdown { background / box-shadow }
       — duplicated by body > .ts-dropdown above; the conflicting
       1px border that caused today's bug is also gone.
     - .ts-dropdown .option / .option.active / .no-results
       — duplicated by the dropdown option rules above.
   ════════════════════════════════════════════════════════════════════════ */

/* Calculator visualization — "watch every movement" debug layout. */
.calc-stage-card {
    border-left: 3px solid var(--tblr-primary);
    margin-bottom: 12px;
    font-size: 0.85rem;
}
.calc-stage-card.has-error   { border-left-color: var(--tblr-danger); }
.calc-stage-card.has-warning { border-left-color: var(--tblr-warning); }
.calc-stage-card .card-header {
    padding: 8px 12px;
    background: var(--tblr-bg-surface-secondary);
    border-bottom: 1px solid var(--tblr-border-color-translucent);
    font-weight: 600;
    color: var(--tblr-body-color);
}
.calc-stage-card .card-body { padding: 10px 12px; }

/* Category blocks — 3px coloured left border + tinted bg. */
.calc-block {
    border-radius: 4px;
    padding: 8px 10px 8px 12px;
    margin-bottom: 6px;
    border-left: 3px solid transparent;
}
.calc-block:last-child { margin-bottom: 0; }
.calc-block-label {
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    margin-bottom: 3px;
    color: var(--tblr-body-color);
    opacity: 0.7;
}
.calc-block-content {
    font-family: 'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace;
    font-size: 0.78rem;
    white-space: pre-wrap;
    word-break: break-word;
    color: var(--tblr-body-color);
}
.calc-block-content .text-muted { color: var(--tblr-secondary) !important; opacity: 0.85; }
.calc-block-content strong { color: var(--tblr-body-color); font-weight: 700; }
.calc-block-input    { background: rgba(32, 107, 196, 0.12); border-left-color: rgb(32, 107, 196); }
.calc-block-logic    { background: rgba(247, 165, 4, 0.14);  border-left-color: rgb(247, 165, 4); }
.calc-block-formula  { background: var(--tblr-bg-surface-secondary); border-left-color: var(--tblr-border-color); }
.calc-block-output   { background: rgba(47, 179, 68, 0.14);  border-left-color: rgb(47, 179, 68); }

pre.json-block {
    font-size: 0.72rem;
    max-height: 220px;
    overflow: auto;
    background: var(--tblr-bg-surface);
    color: var(--tblr-body-color);
    padding: 8px 10px;
    border-radius: 4px;
    border: 1px solid var(--tblr-border-color-translucent);
    margin: 0;
}

/* Calculation logic — one chain at a time, centred. Each stage is a process
   node: inputs grouped left, outputs right, process box centred, joined by an
   SVG connector overlay (drawCfLinks) that fans inputs into the box, outputs
   out of it, and links consecutive process boxes vertically. */
.cf-legend { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 18px; font-size: 11.5px; color: var(--tblr-secondary); margin-bottom: 14px; }
.cf-legend span { display: inline-flex; align-items: center; gap: 5px; }
.cf-legend .cf-sw { width: 9px; height: 9px; border-radius: 2px; border: 1.5px solid; flex: none; }
.cf-legend .cf-sw-ext { border-color: var(--tblr-border-color); background: transparent; }

/* Chain selector — centred, a touch larger. */
.cf-tabs { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-bottom: 18px; }
.cf-tab {
    display: inline-flex; align-items: center; gap: 9px;
    border: 1px solid var(--tblr-border-color); border-radius: 9px;
    padding: 9px 16px; background: var(--tblr-bg-surface); cursor: pointer;
    font-size: 13.5px; color: var(--tblr-body-color); line-height: 1.3;
}
.cf-tab:hover { border-color: var(--tblr-primary); }
.cf-tab.active { border-color: var(--tblr-primary); box-shadow: inset 0 0 0 1px var(--tblr-primary); }
.cf-tab-nm { font-weight: 500; }
.cf-tab-px { font-weight: 600; color: var(--tblr-primary); }

/* The stage stack expands to its widest node and scrolls horizontally if a
   logic row needs more than the page width (rows are never wrapped). */
.cf-stage { width: 100%; overflow-x: auto; overflow-y: hidden; padding-bottom: 10px; --cf-line: #c2c8d2; }
.cf-body { position: relative; display: flex; flex-direction: column; row-gap: 40px; align-items: stretch; width: max-content; min-width: 100%; }

/* Machine group — one bordered box per pass holding that machine's production
   stages (Time/Labor → consumables → Waste), with the machine name as header.
   Waste/consumables aren't separate steps; they belong to the machine. */
.cf-group { display: flex; flex-direction: column; row-gap: 40px; align-items: stretch; border: 1.5px solid var(--tblr-border-color); border-radius: 12px; padding: 16px 10px; background: rgba(130, 140, 150, .04); }
.cf-group-h { text-align: center; font-weight: 700; font-size: 12.5px; letter-spacing: .05em; text-transform: uppercase; color: var(--tblr-primary); }
[data-bs-theme="dark"] .cf-group { background: rgba(255, 255, 255, .03); }

/* SVG connector overlay — behind the boxes, never intercepts clicks. */
svg.cf-links { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 0; }

/* A stage, centred mid-page: [inputs] (gap) [process] (gap) [outputs]. The
   fixed-width side columns keep the process box at a constant centre so the
   vertical connectors line up; the gaps hold the fanned connector curves. */
.cf-node { position: relative; z-index: 1; display: flex; justify-content: center; align-items: center; width: 100%; column-gap: 80px; }
.cf-io { width: 252px; display: flex; flex-direction: column; gap: 9px; }
.cf-in { align-items: flex-end; }
.cf-out { align-items: flex-start; }

.cf-chip {
    display: inline-flex; flex-direction: column; flex: none;
    border: 1.5px solid var(--tblr-body-color);
    border-radius: 4px; padding: 4px 9px; min-width: 78px; max-width: 250px;
    background: var(--tblr-bg-surface);
}
/* carried values get a light tint of their own colour set inline; external
   inputs get a faint neutral wash — both light enough not to dim the text. */
.cf-chip.cf-ext { border-color: var(--tblr-border-color); background: rgba(130, 140, 150, .07); }
/* Title darker than a muted label but kept below the value in the hierarchy,
   theme-aware (dark on light, light on dark) via body colour + opacity. */
.cf-chip .cf-k { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .02em; color: var(--tblr-body-color); line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cf-chip .cf-valrow { display: inline-flex; align-items: baseline; gap: 3px; white-space: nowrap; }
.cf-chip .cf-v { font-size: 13px; font-weight: 700; color: var(--tblr-body-color); white-space: nowrap; font-variant-numeric: tabular-nums; }
.cf-chip .cf-u { font-size: 10.5px; font-weight: 600; color: var(--tblr-secondary); }
/* Dark theme: lift the connector lines and the neutral external-chip wash off
   the dark surface (the light-mode values vanish on it). */
[data-bs-theme="dark"] .cf-stage { --cf-line: #4a5260; }
[data-bs-theme="dark"] .cf-chip.cf-ext { background: rgba(255, 255, 255, .045); }

/* Process box — fits its logic up to a readable cap, then wraps at logical
   boundaries (formula expressions stay intact via .cf-nw). Clear gap between
   steps; variables bold, the rest regular. */
.cf-proc {
    flex: none; width: max-content; min-width: 220px; max-width: 440px;
    border: 2px solid var(--tblr-border-color);
    border-radius: 7px; padding: 10px 14px;
    background: var(--tblr-bg-surface);
}
.cf-proc-t { font-size: 14.5px; font-weight: 600; color: var(--tblr-primary); line-height: 1.3; text-align: center; }
.cf-proc-f { margin-top: 8px; }
.cf-proc-row { font-size: 12.5px; color: var(--tblr-secondary); line-height: 1.5; margin-bottom: 9px; white-space: normal; overflow-wrap: normal; word-break: keep-all; }
.cf-proc-row:last-child { margin-bottom: 0; }
.cf-proc-row .cf-nw { white-space: nowrap; }
/* A worked value's name, in grey [brackets] next to the live number. */
.cf-proc-row .cf-wn { color: var(--tblr-secondary); font-weight: 400; font-size: 11px; }
.cf-proc-row b { font-weight: 700; color: var(--tblr-body-color); }
/* Live value inside a worked calculation — bold; coloured inline when it is a
   carried (colour-traced) value, body colour otherwise. */
.cf-proc-row .cf-wv { font-weight: 700; color: var(--tblr-body-color); font-variant-numeric: tabular-nums; }

/* Worked calculations stacked vertically, each in its own box (the box keeps a
   multi-line sum visually grouped). Side-by-side was tried but the content
   didn't fit the narrow process column, so the boxes go full-width, one per
   row. */
.cf-calc-grid { margin-top: 8px; display: flex; flex-direction: column; gap: 10px; align-items: stretch; }
.cf-calc-box {
    border: 1px solid var(--tblr-border-color); border-radius: 6px;
    padding: 6px 10px; background: rgba(130, 140, 150, .05);
}
[data-bs-theme="dark"] .cf-calc-box { background: rgba(255, 255, 255, .04); }
/* A single-line (non-sum) calc box just holds its one worked row flush. */
.cf-calc-box > .cf-proc-row { margin-bottom: 0; }
/* Additive sum: label header, one signed term per line, then label = result. */
.cf-calc-h { font-size: 12px; font-weight: 700; color: var(--tblr-body-color); margin-bottom: 4px; white-space: nowrap; }
.cf-calc-term { font-size: 12.5px; color: var(--tblr-secondary); line-height: 1.55; display: flex; gap: 6px; white-space: nowrap; }
.cf-calc-sg { color: var(--tblr-secondary); font-weight: 700; width: .8em; flex: none; text-align: center; }
.cf-calc-ex { white-space: nowrap; }
.cf-calc-res { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--tblr-border-color); font-size: 12.5px; font-weight: 600; color: var(--tblr-body-color); white-space: nowrap; }
/* Shared worked-value styling inside calc boxes (mirrors the .cf-proc-row set). */
.cf-calc-box .cf-wv { font-weight: 700; color: var(--tblr-body-color); font-variant-numeric: tabular-nums; }
.cf-calc-box .cf-wn { color: var(--tblr-secondary); font-weight: 400; font-size: 11px; }
.cf-calc-box b { font-weight: 700; color: var(--tblr-body-color); }

/* Ink row container (used in the quote-edit view — the calculator's
   .special-row uses its own flat inline layout). `.finishing-row` USED
   to be in this rule too, but the calculator now lays finishings out as
   single inline rows matching ink-row simplicity (type dropdown + preset
   dropdown + Remove button), so the card chrome was dropped on 2026-05-17. */
.ink-row {
    border: 1px solid var(--tblr-border-color-translucent);
    background: var(--tblr-bg-surface-secondary);
    border-radius: 4px;
    padding: 8px;
    margin-bottom: 6px;
}

/* Machine comparison cards. */
.machine-result-card .machine-header {
    background: var(--tblr-bg-surface-secondary);
    border-bottom: 1px solid var(--tblr-border-color-translucent);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
}
[data-bs-theme="dark"] .machine-result-card .machine-header { background: rgba(255, 255, 255, 0.04); }
.machine-result-card.machine-result-incompatible              { border-color: rgba(220, 53, 69, 0.25); }
.machine-result-card.machine-result-incompatible .machine-header { background: rgba(220, 53, 69, 0.06); }

/* Tier price table inside a flush card-body (p-0). */
.machine-result-card .card-body.p-0 .table {
    margin-bottom: 0;
    border-collapse: collapse;
}
.machine-result-card .card-body.p-0 .table > tbody > tr > * { border-bottom: 1px solid var(--tblr-border-color-translucent); }
.machine-result-card .card-body.p-0 .table > tbody > tr:last-child > * { border-bottom: 0; }
.machine-result-card .card-body.p-0 .table thead th {
    background: var(--tblr-bg-surface-secondary);
    font-weight: 600;
    padding: 8px 14px;
    border-bottom: 1px solid var(--tblr-border-color-translucent);
}
.machine-result-card .card-body.p-0 .table tbody td { padding: 8px 14px; }

/* Algorithm-flow header — horizontal chip list of all stages. */
.algorithm-flow {
    padding: 8px 10px;
    background: var(--tblr-bg-surface-secondary);
    border-radius: 4px;
    border: 1px solid var(--tblr-border-color-translucent);
}
.algorithm-flow-inner { font-size: 0.75rem; line-height: 1.4; }
.algorithm-chip       { font-weight: 500; }
.algorithm-arrow      { font-size: 0.85rem; opacity: 0.6; }

/* Tier price card — playground summary section. */
.tier-price-card {
    border: 1px solid var(--tblr-border-color-translucent);
    background: var(--tblr-bg-surface);
    border-radius: 4px;
    padding: 12px;
}
.tier-price-card .price-big      { font-size: 1.5rem; font-weight: 700; color: var(--tblr-primary); }
.tier-price-card .price-per-unit { font-size: 0.85rem; color: var(--tblr-secondary); }

/* Dark-mode tint supplements for calc blocks. */
[data-bs-theme="dark"] .calc-block-input  { background: rgba(110, 168, 254, 0.18); }
[data-bs-theme="dark"] .calc-block-logic  { background: rgba(255, 200, 80, 0.18); }
[data-bs-theme="dark"] .calc-block-output { background: rgba(86, 211, 100, 0.18); }
[data-bs-theme="dark"] .compare-card.compare-error { background: rgba(234, 88, 165, 0.10); }

/* CMYK process-color letter cues — used in calculator + quote-edit. */
.cmyk-c { color: #00bcd4; }
.cmyk-m { color: #e91e63; }
.cmyk-y { color: #f7a504; }
.cmyk-k { color: var(--tblr-body-color); }
[data-bs-theme="dark"] .cmyk-c { color: #4dd0e1; }
[data-bs-theme="dark"] .cmyk-m { color: #f06292; }
[data-bs-theme="dark"] .cmyk-y { color: #ffd54f; }
[data-bs-theme="dark"] .cmyk-k { color: var(--tblr-body-color); }

/* Special-color row layout — compact, narrow-column friendly. */
.special-row .ink-color-code        { min-width: 0; }
.special-row .form-check.form-switch { padding-left: 2.25em; }
.special-row .form-check-label.small {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

/* Calculator-internal Tom Select wrappers — kept at z-index 1 so they
   sit BELOW the body-attached dropdown popup (z-index 10000) but ABOVE
   sibling form rows when stacking with the rest of the inputs card. */
.calc-stage-card .ts-wrapper,
form#calcForm .ts-wrapper { z-index: 1; }

/* `no-chevron` — opt-out of the .form-select down-arrow background image
   for search-driven Tom Selects (Customer, Substrate). The chevron implies
   "click to toggle a dropdown" but these are typed-search fields where the
   user types to filter; the arrow is misleading. The class lives on the
   ORIGINAL <select> and Tom Select copies it onto the .ts-wrapper. We
   also pull back Tabler's chevron-reserved padding-right (~36px → 12px)
   so the placeholder doesn't look strangely left-aligned. */
.ts-wrapper.no-chevron,
.ts-wrapper.no-chevron .ts-control {
    background-image: none !important;
}
.ts-wrapper.no-chevron .ts-control {
    padding-right: 12px !important;
}

/* Inputs card per-column section titles (Order Info / Label / Process). */
.form-section-title {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--tblr-secondary);
    margin: 0 0 0.75rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--tblr-border-color);
}
/* Process-column row rhythm — Label Type, CMYK, Pantone …, VDP all match
   the 32 px form-control grid used by the left column so rows align
   horizontally without vertical shifts.
   The form-checks inside switch from Bootstrap's float-based layout
   (which pulls the input out with margin-left: -1.5em and reserves
   1.5em of padding-left, pushing the row taller) to a flex layout so
   each label fits inside the 32 px row without any margin / padding
   surprises. We keep Bootstrap defaults intact outside .process-row so
   the rest of the page is undisturbed. */
.process-row {
    min-height: 32px;
}
.process-row .form-check,
.process-row .form-check-inline {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-height: 0;
    padding-left: 0;
    padding-right: 0;
    margin-bottom: 0;
    margin-right: 0;   /* otherwise Bootstrap's 1 rem on .form-check-inline
                          pushes the last radio away from the column's
                          right edge — the Blank label / No label / K
                          checkbox should all sit flush like the
                          Pantone / Finishings buttons do. */
}
.process-row .form-check-input {
    float: none;
    margin: 0;
    flex-shrink: 0;
    /* Bigger than Bootstrap's default 1em — Label Type / Yes-No / CMYK
       boxes are easier to hit without breaking the 32 px row rhythm
       (the form-check is inline-flex with min-height: 0, so the bigger
       box just sits centred). */
    width: 1.25em;
    height: 1.25em;
}
.process-row .form-check-label {
    line-height: 1;
    margin: 0;
}
.cmyk-row { min-height: 32px; }
/* Chevron rotates when the collapsing card-header is collapsed.
   Bootstrap sets aria-expanded="false" on the trigger element. */
.calc-collapse-chevron                                            { transition: transform 0.2s ease; }
[data-bs-toggle="collapse"][aria-expanded="false"] .calc-collapse-chevron { transform: rotate(-90deg); }

/* ── Input status icon — error / warning triangle inside the input ──
   Each input that needs a status indicator is wrapped in a
   `.input-icon-wrap` div. The status icon is a sibling <span> that's
   absolutely-positioned at the input's right edge, hidden by default,
   and revealed by CSS sibling selectors when the input picks up
   `.is-invalid` (red triangle) or `.has-warning` (yellow triangle).
   The icon's `data-tooltip` attribute carries the message; the custom
   CSS tooltip rule below paints the popup on hover. Inputs in these
   wrappers also get extra right padding so the typed value doesn't
   underrun the icon. Type=number inputs additionally need to leave
   room for the native ↑/↓ spinner buttons that sit at the right edge,
   so the icon shifts left and the padding doubles. */
.input-icon-wrap { position: relative; }
.input-icon-wrap > input.form-control { padding-right: 28px !important; }
/* type=number — Chrome paints the native ↑/↓ spinner at the content-area
   right edge, so the spinner moves INWARD by `padding-right`. We want the
   spinner on the far right and the triangle LEFT of it, so keep
   padding-right SMALL (just a hair of breathing room) and push the
   triangle to where it sits LEFT of the spinner's natural position. */
.input-icon-wrap > input[type="number"].form-control { padding-right: 8px !important; }
.input-icon-wrap > .input-status-icon {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    line-height: 1;
    font-size: 14px;
    font-family: "Segoe UI Symbol", "Apple Color Emoji", system-ui, sans-serif;
    /* Force text presentation on the ⚠ glyph (otherwise some browsers render
       it as a full-colour emoji that ignores `color`). */
    font-variant-emoji: text;
    pointer-events: auto;
    cursor: help;
    display: none;
    user-select: none;
    /* The calc form puts `.ts-wrapper { z-index: 1 }` for stacking with
       sibling rows. Sit at z=2 so the triangle paints ABOVE the Tom Select
       wrapper (which is a DOM sibling, not an ancestor). For plain inputs
       this is also harmless — the input itself has z-index auto. */
    z-index: 2;
}
/* When the wrapped input is type=number, the native ↑/↓ spinner buttons
   sit on the right edge. With padding-right: 8px the spinner sits ~8–25
   px from the right edge. The icon goes at right: 30px so it's clearly
   LEFT of the spinner with a visible gap, while still appearing inside
   the input box (not clipped by it). */
.input-icon-wrap > input[type="number"] ~ .input-status-icon {
    right: 30px;
}
.input-icon-wrap > input.is-invalid ~ .input-status-icon {
    display: inline-block;
    color: var(--tblr-danger);
}
.input-icon-wrap > input.has-warning:not(.is-invalid) ~ .input-status-icon {
    display: inline-block;
    color: var(--tblr-warning);
}
.input-icon-wrap > input.has-warning:not(.is-invalid) {
    border-color: var(--tblr-warning) !important;
}

/* Tom Select case — when the wrapped element is a Tom Select instead of a
   native input, `setFieldError` puts .is-invalid on the .ts-wrapper (not
   on the hidden <select>), so the sibling selector needs to target that.
   We also shift the icon left of Tom Select's own chevron (right ~12 px). */
.input-icon-wrap > .ts-wrapper ~ .input-status-icon {
    right: 28px;
}
.input-icon-wrap > .ts-wrapper.is-invalid ~ .input-status-icon {
    display: inline-block;
    color: var(--tblr-danger);
}
.input-icon-wrap > .ts-wrapper.has-warning:not(.is-invalid) ~ .input-status-icon {
    display: inline-block;
    color: var(--tblr-warning);
}

/* ── Custom hover tooltip for the input-status-icon ────────────────────
   Native `title` attribute has a 1–2 s delay before Chrome paints the
   bubble. We want immediate feedback. Custom tooltip via ::after on
   the icon, content pulled from `data-tooltip="..."` so the browser
   doesn't paint its own tooltip too. Empty data-tooltip is treated
   as "no tooltip" — the CSS gates the pseudo on non-empty attr.

   Stacking-context note: the calc form sets `.ts-wrapper { z-index: 1 }`
   (line ~1462) so that Tom Select cards layer correctly relative to
   the rest of the form. The tooltip needs to paint ABOVE those wrappers
   when it extends into the adjacent row. We lift the entire
   `.input-icon-wrap` (which is `position: relative`) to z-index 2000
   while a tooltip is showing via :has(), guaranteeing the pseudo
   outranks every sibling stacking context. */
.input-icon-wrap:has(.input-status-icon[data-tooltip]:not([data-tooltip=""]):hover) {
    z-index: 2000;
}
.input-status-icon[data-tooltip]:not([data-tooltip=""]):hover::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 8px);
    right: -6px;
    background: rgba(35, 38, 45, 0.95);
    color: #fff;
    padding: 6px 10px;
    border-radius: 4px;
    font-size: 11px;
    line-height: 1.4;
    font-weight: 400;
    font-family: var(--tblr-body-font-family);
    white-space: normal;
    width: max-content;
    max-width: 260px;
    z-index: 2000;
    pointer-events: none;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    /* Fade-in animation runs each time the tooltip appears on hover. */
    animation: tooltipFadeIn 0.15s ease-out;
}
/* Little arrow pointing down to the icon — small CSS triangle. */
.input-status-icon[data-tooltip]:not([data-tooltip=""]):hover::before {
    content: '';
    position: absolute;
    bottom: 100%;
    right: 4px;
    border: 4px solid transparent;
    border-top-color: rgba(35, 38, 45, 0.95);
    z-index: 2001;
    pointer-events: none;
    animation: tooltipFadeIn 0.15s ease-out;
}
@keyframes tooltipFadeIn {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Calculate-button loading state — hide the calculator icon and paint
   a spinner in its place. Keeping the "Calculate" text visible means
   the button keeps its full width during the in-flight request, so the
   layout doesn't jump. Spinner reuses the @keyframes ts-loading-spin
   defined for the Tom Select remote-search chevron. */
#compareBtn.loading .icon { display: none; }
#compareBtn.loading::before {
    content: '';
    display: inline-block;
    /* Match the .icon SVG's effective layout footprint. The icon span
       has me-1 (4px margin-right) but empirically the margin is being
       absorbed somewhere in the inline flow — measured: hiding the icon
       shrinks the button by exactly 20px (the SVG's width), not 24. So
       the pseudo only needs to fill 20px total, no margin. box-sizing:
       border-box keeps the visible spinner at 20px outer regardless of
       border width. */
    box-sizing: border-box;
    width: 20px;
    height: 20px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: ts-loading-spin 0.7s linear infinite;
    vertical-align: -5px;
}

/* Toast notifications — preserve newlines in the message body so
   multi-error validation toasts render one item per line. `pre-line`
   keeps explicit \n as a line break while collapsing other whitespace,
   which is exactly what we want for the "Fix the following:" toast. */
#notifyContainer .alert small { white-space: pre-line; }

/* ── Calculator workflow strip — per-pass-group box visualization ─────────
   Plan 3 — the chain-enumerator calculator renders one .chain-result-card
   per viable chain (printing → lamination → finishing → cutting → output).
   The workflow strip sits between the card header and the price table; it
   surfaces the chain visually as boxes (one per pass_group) joined by →
   arrows. Adjacent steps on the same machinery with non-primary
   capabilities collapse into one box so e.g. an Offset press doing
   primary-print + inline-varnish + inline-cut appears as ONE box with
   the three subtypes joined by middle dots.

   Flex layout so the strip wraps cleanly on narrow viewports and scales
   to any number of passes without manual sizing. */
.workflow-strip {
    display: flex;
    align-items: center; /* boxes + arrows vertically centered; each box is
                            its own content height (no forced equal height) */
    gap: 8px;
    flex-wrap: wrap;
    padding: 8px 12px !important;
    background: var(--tblr-bg-surface-secondary, #f4f6fa);
    /* No border-bottom — the next card-body's border-top is the single
       divider; both together painted a double border under the strip. */
}
.workflow-strip-box {
    display: flex;
    flex-direction: column;
    padding: 6px 10px;
    background: #fff;
    border: 1px solid var(--tblr-border-color);
    border-radius: 3px;
    /* Grow equally so the boxes (and the arrows between them) fill the full
       width of the strip instead of left-packing with empty space on the
       right. min-width keeps them readable before wrapping on narrow cards. */
    flex: 1 1 0;
    min-width: 140px;
}
/* Raw-material lead box — same chrome as the machine boxes (the blue
   accent border was removed per design). */
/* Per-qty cost list inside a workflow box: qty left, cost right. Pushed
   to the box bottom (margin-top auto) so all boxes' cost lists baseline-
   align even when subtitles wrap to different heights. */
.wf-cost-rows {
    margin-top: auto;
    padding-top: 4px;
    border-top: 1px dashed var(--tblr-border-color);
}
.wf-cost-row {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    font-size: 10.5px;
    line-height: 1.6;
}
.wf-cost-row .wf-cost {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
/* Rich per-phase box detail (single-quantity debug view): time / cost
   components / waste lengths, each as a label-left value-right row, with
   small section heads and indented sub-rows. */
.wf-d-body {
    margin-top: 6px;
    padding-top: 5px;
    border-top: 1px dashed var(--tblr-border-color);
    width: 100%;
}
.wf-d-head {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .02em;
    color: var(--tblr-secondary, #66758c);
    margin-top: 5px;
    margin-bottom: 1px;
}
.wf-d-head:first-child { margin-top: 0; }
/* Value carried in a section head (e.g. exact production length). Escapes
   the head's uppercase so unit symbols keep their case ("m" not "M"). */
.wf-d-head .wf-d-head-v {
    text-transform: none;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.wf-d-row {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 10.5px;
    line-height: 1.5;
}
.wf-d-row.wf-d-sub { padding-left: 8px; }
.wf-d-row .wf-d-v { font-variant-numeric: tabular-nums; white-space: nowrap; }
.wf-d-row.wf-d-total {
    margin-top: 4px;
    padding-top: 3px;
    border-top: 1px solid var(--tblr-border-color);
    font-size: 11.5px;
}
.wf-d-row.wf-d-total .wf-d-v { color: var(--tblr-primary, #206bc4); }
/* Applicable-but-zero cost lines stay visible in amber, so a zeroed
   machine parameter (per-color setup, die cost, …) is catchable. */
.wf-d-row.wf-d-zero .wf-d-v { color: var(--tblr-warning, #f59f00); }
/* Price table: fixed layout pins each money column (th.price-col) to a
   set width; the quantity column (no width) absorbs the rest, so price
   columns don't stretch to fill the card. */
.calc-price-table { table-layout: fixed; width: 100%; }
.calc-price-table th.price-col { width: 160px; }
/* Price per label — the per-piece figure under each tier total. Cobalt accent,
   readable weight so it reads as a key number, not a muted footnote. */
.calc-per-label { font-size: 12px; font-weight: 600; color: var(--tblr-primary); line-height: 1.35; white-space: nowrap; }
/* BOM / Floor per-label — muted grey (cost figures, not quotable prices). */
.calc-per-label.calc-per-label-muted { font-weight: 500; color: var(--tblr-secondary); }
/* Production card — indented per-process time rows under each qty line. */
.production-pass-row {
    padding-left: 12px;
    font-size: 11px;
    margin-bottom: 1px;
}
.production-pass-row + .production-qty-row {
    margin-top: 6px;
}
/* Production card — material & waste block. Detail rows sit under each
   quantity header; the waste-breakdown sub-rows indent + subdue. */
.production-mat-row { line-height: 1.5; }
.production-mat-row .text-end { font-variant-numeric: tabular-nums; }
.production-mat-sub {
    padding-left: 12px;
    font-size: 11px;
    opacity: 0.85;
}
.production-mat-qty:first-of-type { margin-top: 0 !important; }
.wf-machine-name {
    font-weight: 600;
    font-size: 12px;
}
.wf-subtitle {
    font-size: 10px;
    margin-top: 2px;
}
.wf-arrow {
    font-size: 16px;
    padding: 0 4px;
}
.chain-incompatible .workflow-strip {
    opacity: 0.5;
}

/* Chain card header — slimmer than the default card-header so the strip
   sits visually-connected just below. Title also drops a notch so the
   chain label (which can run long with 3-4 machines) stays readable
   without crowding the success/incompatible chip on the right. */
.chain-result-card > .card-header {
    padding: 8px 12px !important;
}
.chain-result-card > .card-header .card-title {
    font-size: 13px;
    font-weight: 600;
}
/* Price table (QUANTITY / BOM / FLOOR / tier columns) lives in a
   `card-body p-0` so the table can run full-width; without per-cell
   padding the 4px .table-sm default leaves columns cramped and the
   outer columns flush against the card edge — out of line with the
   workflow strip above (8px 12px inset). Give comfortable cell padding
   and inset the first/last columns to 12px so the table reads as one
   tidy grid aligned with the rest of the card. */
.chain-result-card .table > :is(thead, tbody) > tr > * {
    padding: 0.45rem 0.6rem;
    vertical-align: middle;
}
.chain-result-card .table > :is(thead, tbody) > tr > *:first-child { padding-left: 12px; }
.chain-result-card .table > :is(thead, tbody) > tr > *:last-child  { padding-right: 12px; }

/* ── Machinery / machine-template edit modal — capabilities row ─────────
   The 6-col capabilities table inside the edit modal sits on top of a
   modal-big-tall dialog (1024px). Even at that width the Phase select
   needs ~130px to keep the chevron from eating "Lamination", and the
   Subtype select needs ~190px so the longest seeded label
   ("Semi-rotary kiss-cut" / "Semi-rotary die-cut" = 20 chars) renders
   without truncation. Scoped to .modal so the same .cap-* class
   doesn't bloat any selector on a non-modal page. */
.modal .cap-phase   { min-width: 130px; }
.modal .cap-subtype { min-width: 190px; }
