﻿/* ============================================================
   HaiKom — design system on top of Bootstrap 5.3

   Loaded AFTER bootstrap.min.css, so the token overrides here win.

   Principle: Bootstrap does the heavy lifting (grid, buttons, forms,
   dropdowns, offcanvas, modals). This sheet contributes only what
   Bootstrap has no opinion about — the dense back-office language
   the order console is built from: the action rail, the data table,
   the section card, and the empty states.

   Contents
     1  Tokens — neutral ramp, brand, semantics, elevation, radius
     2  Base — typography and page surface
     3  Bootstrap density tuning
     4  Buttons — re-skin the stock variants against the tokens
     5  Toolbar — the action rail  (.hk-toolbar / .hk-order-toolbar)
     6  Page header                (.hk-page-*)
     7  Section card               (.hk-order-section)
     8  Data table                 (.hk-table)
     9  Row actions                (.hk-actions)
    10  States — empty / denied / loading   (.hk-state)
    11  Form grid                  (.hk-form-*)
    12  Small shared helpers
    13  Navigation progress        (.hk-navigating)
    14  Account forms              (.hk-otp-input)
   ============================================================ */

/* ── 1. Tokens ──────────────────────────────────────────────────────────────
   The neutral ramp is deliberately cool: the brand (#3e606f) is a desaturated
   slate-teal, and Bootstrap's stock grays are faintly warm, which makes the
   chrome fight the brand. These are mixed toward the same hue so borders and
   surfaces sit quietly under it. */
:root {
    --hk-n-0:   #ffffff;
    --hk-n-25:  #fafbfc;
    --hk-n-50:  #f4f6f8;
    --hk-n-100: #ebeef1;
    --hk-n-200: #dfe4e9;
    --hk-n-300: #c8d1d9;
    --hk-n-400: #97a4b0;
    --hk-n-500: #6b7b88;
    --hk-n-600: #4e5d6a;
    --hk-n-700: #384550;
    --hk-n-800: #232e37;
    --hk-n-900: #141c22;

    /* Brand */
    --bs-primary: #3e606f;
    --bs-primary-rgb: 62, 96, 111;

    /* Accent — referenced by .btn-accent */
    --hk-accent: #ff9800;
    --hk-accent-rgb: 255, 152, 0;
    --hk-accent-hover: #f57c00;

    /* Sequence marker — the purple that tags sequence-bound work on the planning screens
       (produce attention dots, timeline bars). */
    --hk-sequence: #7b5ea7;

    /* Planning blocks — what a bar or an appointment on the production screens is made of. Carried over
       from the WebForms scheduler so operators read the plan the same way: a production moment is light
       blue, a group (a delivery covering several jobs included) purple, a plain delivery or transport
       dusty pink, a custom moment grey, and a prioritised moment green. The selected pair is the same
       colour 75 steps darker on every channel — the old page's darkening, kept so the two look alike. */
    --hk-plan-production: #b4d4e2;
    --hk-plan-production-selected: #698997;
    --hk-plan-priority: #b4e2b5;
    --hk-plan-priority-selected: #69976a;
    --hk-plan-group: #d5b4e2;
    --hk-plan-group-selected: #8a6997;
    --hk-plan-delivery: #e2b4b4;
    --hk-plan-delivery-selected: #976969;
    --hk-plan-custom: #cccccc;
    --hk-plan-custom-selected: #818181;

    /* Semantic status — carried over from the legacy Foundation palette so
       status colours stay recognisable to operators who know the old UI,
       darkened enough to pass contrast on white. */
    --bs-success: #2e9e5b;
    --bs-success-rgb: 46, 158, 91;
    --bs-warning: #d9880a;
    --bs-warning-rgb: 217, 136, 10;
    --bs-danger: #c0442f;
    --bs-danger-rgb: 192, 68, 47;
    --bs-info: #2f6f9f;
    --bs-info-rgb: 47, 111, 159;

    /* Bootstrap 5.3 bakes the subtle/emphasis pairs in at build time, which
       defeats a :root override of the base colour. Re-derive them so they track
       the brand — including a tenant's runtime override of --bs-primary. */
    --bs-primary-text-emphasis: color-mix(in srgb, var(--bs-primary) 65%, black);
    --bs-primary-bg-subtle: color-mix(in srgb, var(--bs-primary) 10%, white);
    --bs-primary-border-subtle: color-mix(in srgb, var(--bs-primary) 28%, white);

    --bs-success-text-emphasis: color-mix(in srgb, var(--bs-success) 65%, black);
    --bs-success-bg-subtle: color-mix(in srgb, var(--bs-success) 12%, white);
    --bs-success-border-subtle: color-mix(in srgb, var(--bs-success) 30%, white);

    --bs-warning-text-emphasis: color-mix(in srgb, var(--bs-warning) 70%, black);
    --bs-warning-bg-subtle: color-mix(in srgb, var(--bs-warning) 14%, white);
    --bs-warning-border-subtle: color-mix(in srgb, var(--bs-warning) 32%, white);

    --bs-danger-text-emphasis: color-mix(in srgb, var(--bs-danger) 65%, black);
    --bs-danger-bg-subtle: color-mix(in srgb, var(--bs-danger) 10%, white);
    --bs-danger-border-subtle: color-mix(in srgb, var(--bs-danger) 28%, white);

    --bs-info-text-emphasis: color-mix(in srgb, var(--bs-info) 65%, black);
    --bs-info-bg-subtle: color-mix(in srgb, var(--bs-info) 10%, white);
    --bs-info-border-subtle: color-mix(in srgb, var(--bs-info) 28%, white);

    /* Surfaces — map Bootstrap's neutrals onto the ramp. */
    --bs-body-bg: var(--hk-n-0);
    --bs-body-color: var(--hk-n-800);
    --bs-emphasis-color: var(--hk-n-900);
    /* One step darker than the ramp's midpoint: the 500 neutral is 4.36:1 on white and 3.75:1 on the page
       ground, under WCAG AA for the small captions this colour carries. The 600 neutral clears it on both. */
    --bs-secondary-color: var(--hk-n-600);
    /* Bootstrap's theme "secondary" is a separate token from the muted text colour above, and the app never
       remapped it — .text-secondary reads --bs-secondary-rgb, so 123 muted labels rendered Bootstrap's stock
       #6c757d while every rule in this file used the n-600 neutral. That stock grey was already under AA on
       the sunken ground (4.33:1) and the deeper page ground takes it to 4.03:1, so point both at the neutral
       the rest of the app's muted text already uses: 5.82:1 on the page, 7.4:1 on a card. The rgb pair is
       written out because the utilities need the triplet, as --hk-on-primary-rgb above does. */
    --bs-secondary: var(--hk-n-600);
    --bs-secondary-rgb: 78, 93, 106;
    --bs-secondary-bg: var(--hk-n-100);
    --bs-tertiary-bg: var(--hk-n-50);
    --bs-border-color: var(--hk-n-200);
    --bs-border-color-translucent: var(--hk-n-200);

    /* App surfaces beyond Bootstrap's vocabulary. The page is white and the section bodies are the tinted
       ground: the content the user works in is what carries the shade, not the space around it. A surface
       nested inside a section body — a table, a rail, a sub-section — steps back up to white, so depth
       reads inward from the page rather than outward from it.
       The tint is deliberately faint — n-50, three L* below white. It only has to say "the content is in
       here"; any heavier and a screen of stacked sections reads as grey rather than as white with content
       on it. Every tinted ground in the app is this one token, so a section body, a table foot and a
       sub-section body are the same grey and nothing has to be matched by eye. */
    --hk-app-bg: var(--hk-n-0);
    --hk-surface-sunken: var(--hk-n-50);
    --hk-surface-raised: var(--hk-n-0);

    /* A selected row. The brand is a desaturated slate, so a tint of it lands on grey — the selection then
       reads as "this row is disabled" rather than "this row is picked", which is what it did. Built on the
       info blue instead: an actual blue at every tenant, and the one hue in the palette that already means
       "the system is telling you something" rather than "this belongs to the brand". Deliberate deviation
       from §12's read-everything-from-the-brand rule — selection is a state, not branding. */
    --hk-row-selected-bg: color-mix(in srgb, var(--bs-info) 16%, white);
    --hk-row-selected-bg-hover: color-mix(in srgb, var(--bs-info) 24%, white);
    --hk-row-selected-border: color-mix(in srgb, var(--bs-info) 38%, white);
    /* Text and controls on a primary-filled surface (section bands). White, as Bootstrap's .btn-primary
       already assumes for every tenant; the rgb triplet feeds Bootstrap's link-colour variables. */
    --hk-on-primary: var(--hk-n-0);
    --hk-on-primary-rgb: 255, 255, 255;

    /* The module sidebar — the dark rail the order and admin shells hang their navigation on. Built on
       the deep end of the same neutral ramp, so it stays cool under the brand instead of turning navy;
       the text steps are the ramp's light end (n-300 on n-900 is 10.6:1, n-400 is 6.4:1). Hover and
       selection are white washes, so they need no colour of their own; the accent that marks the
       current item and the tenant mark is the primary lightened — a tenant's runtime override of
       --bs-primary recolours it with the rest. */
    --hk-sidebar-bg: var(--hk-n-900);
    --hk-sidebar-bg-2: var(--hk-n-800);
    --hk-sidebar-border: color-mix(in srgb, var(--hk-n-0) 9%, transparent);
    --hk-sidebar-text: var(--hk-n-300);
    --hk-sidebar-text-strong: var(--hk-n-0);
    --hk-sidebar-muted: var(--hk-n-400);
    --hk-sidebar-hover: color-mix(in srgb, var(--hk-n-0) 6%, transparent);
    --hk-sidebar-active: color-mix(in srgb, var(--hk-n-0) 11%, transparent);
    --hk-sidebar-accent: color-mix(in srgb, var(--bs-primary) 45%, var(--hk-n-0));
    --hk-sidebar-on-accent: var(--hk-n-900);
    /* The menu's own scrollbar: a thumb in the same white wash as the borders, a step stronger while the
       pointer is over the menu. The track stays transparent, so the thumb is all that shows. */
    --hk-sidebar-scrollbar: color-mix(in srgb, var(--hk-n-0) 18%, transparent);
    --hk-sidebar-scrollbar-hover: color-mix(in srgb, var(--hk-n-0) 32%, transparent);

    /* Links. No underline: in a back-office screen the links are ids and names sitting in dense tables, and
       an underline under every one of them turns a column into a ruled page. The brand colour plus the
       heavier weight carries "this is a link" on its own, and both survive a tenant's primary override. */
    --bs-link-color: var(--bs-primary);
    --bs-link-color-rgb: var(--bs-primary-rgb);
    --bs-link-hover-color: color-mix(in srgb, var(--bs-primary) 78%, black);
    /* Bootstrap writes `text-decoration: underline` straight into its `a` rule rather than reading a
       variable, so the underline has to come off in §2 — there is nothing to override here. */
    --hk-link-font-weight: 600;

    /* Elevation — short, tight shadows. A back-office surface should feel
       printed rather than floating; only overlays get real depth. */
    --hk-shadow-xs: 0 1px 1px rgba(20, 28, 34, 0.04);
    --hk-shadow-sm: 0 1px 2px rgba(20, 28, 34, 0.07);
    --hk-shadow-md: 0 2px 6px -1px rgba(20, 28, 34, 0.10), 0 1px 2px rgba(20, 28, 34, 0.05);
    --hk-shadow-lg: 0 12px 32px -8px rgba(20, 28, 34, 0.24);

    /* Radius */
    --bs-border-radius: 0.375rem;
    --bs-border-radius-sm: 0.25rem;
    --bs-border-radius-lg: 0.5rem;
    --bs-border-radius-xl: 0.75rem;

    /* Density */
    --bs-body-font-size: 0.8125rem;
    --bs-body-line-height: 1.45;
    --hk-row-py: 0.375rem;
    --hk-row-px: 0.625rem;
    /* The text in every grid's cells: HkGrid, the Radzen grid (radzen-overrides.css §5) and .hk-table (§8) read
       it, so the three kinds stay alike side by side. A step under the body text, since a grid carries many
       columns, and at the 12px floor for informative text — go no smaller. Controls in cells keep their own size. */
    --hk-grid-font-size: 0.75rem;

    /* Form controls — the metrics of a single input, wherever it comes from.
       Bootstrap's .form-control / .form-select read these in §3 and
       radzen-overrides.css maps Radzen's own --rz-input-* onto them, so a
       Radzen dropdown and a native <select> are the same height to the pixel.
       Change a control's density here and both families follow. */
    --hk-control-font-size: 0.8125rem;
    --hk-control-line-height: 1.45;
    --hk-control-padding-y: 0.3125rem;
    --hk-control-padding-x: 0.5625rem;
    --hk-control-border-color: var(--hk-n-300);
    /* A field the user cannot type in — readonly or disabled, which look alike on purpose (§5). A step
       darker than the section body it sits on, so it reads as filled-in-and-closed rather than as an empty
       box, and the missing carve below says the same thing again: a locked field lies flat on the page
       where an editable one is cut into it. */
    --hk-control-locked-bg: var(--hk-n-100);
    /* The carve. Foundation gave every field `inset 0 1px 2px rgba(10,10,10,.1)`, and losing it is half
       of why a Blazor field reads flatter than the WebForms one it replaced: a rectangle drawn on the
       page rather than a well cut into it. Kept shallow — this density cannot carry Foundation's depth. */
    --hk-control-shadow: inset 0 1px 2px rgba(20, 28, 34, 0.06);
    /* Derived, so it cannot drift from the padding above. Border-box height of
       a single-line control: text + both paddings + both borders. */
    --hk-control-height: calc(var(--hk-control-line-height) * var(--hk-control-font-size)
                              + 2 * var(--hk-control-padding-y)
                              + 2 * var(--bs-border-width));

    /* Focus ring — one definition, used by every interactive surface here. */
    --hk-focus-ring: 0 0 0 0.15rem rgba(var(--bs-primary-rgb), 0.35);
    --hk-focus-border-color: color-mix(in srgb, var(--bs-primary) 55%, white);
}

/* ── 2. Base ────────────────────────────────────────────────────────────────
   Roboto, the face the Radzen controls already draw in: the Radzen theme
   (App.razor) declares it with @font-face on the Roboto Flex file the package
   serves from _content/Radzen.Blazor/fonts, so nothing is fetched from outside
   and a locked-down machine gets it like any other file of the app. One face
   for Bootstrap and Radzen alike — before, the lists and date pickers were
   Roboto inside Segoe UI pages. The platform UI stack stays behind it. */
:root {
    --bs-font-sans-serif: Roboto, system-ui, -apple-system, "Segoe UI",
                          "Helvetica Neue", Arial, "Noto Sans", sans-serif;
    --bs-body-font-family: var(--bs-font-sans-serif);
    --bs-font-monospace: ui-monospace, "Cascadia Mono", "Segoe UI Mono",
                         "Roboto Mono", Menlo, Consolas, monospace;
}

body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* A content link — an id in a grid cell, a name in a summary — is the brand colour and a little heavier
   than its surroundings, with no underline (§1). The exclusions are everything that is only an <a> for
   want of a better element: buttons, menu entries, tabs, pager links and the dark sidebar's own items,
   each of which already carries its own weight and colour. An <a> without an href is not a link either. */
a[href]:not(.btn):not(.nav-link):not(.dropdown-item):not(.page-link):not(.alert-link):not(.hk-sidebar-link) {
    font-weight: var(--hk-link-font-weight);
    text-decoration: none;
}

/* Numerals in data contexts must align in a column. Bootstrap has no opinion
   here and proportional digits make price columns unscannable. */
.hk-num,
.hk-grid-table td.text-end,
[data-hk-numeric] {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    /* An amount never breaks: "4 159,74 kr" split over two lines in a narrow column is two numbers. */
    white-space: nowrap;
}

/* One visible focus treatment everywhere, including for the ghost controls
   below which have no resting border to thicken. */
:focus-visible {
    outline: none;
    box-shadow: var(--hk-focus-ring);
    border-radius: var(--bs-border-radius-sm);
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── 3. Bootstrap density tuning ────────────────────────────────────────────
   Stock padding is tuned for marketing-weight pages. These bring the common
   components to back-office density without dropping to the -sm variants,
   which would also shrink the type below a comfortable reading size. */

/* One button box everywhere. The icon is centred by flex, not by baseline
   nudging — which is what left glyphs floating a pixel high or low next to
   their labels — and the .btn-sm the module was written with is folded into
   the same metrics, so a button and the input beside it are the same height
   to the pixel (both resolve to --hk-control-height). */
.btn {
    --bs-btn-padding-y: 0.3125rem;
    --bs-btn-padding-x: 0.75rem;
    --bs-btn-font-size: 0.8125rem;
    --bs-btn-line-height: 1.45;
    --bs-btn-border-radius: var(--bs-border-radius);
    --bs-btn-font-weight: 500;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
}

.btn-sm {
    --bs-btn-padding-y: 0.3125rem;
    --bs-btn-padding-x: 0.75rem;
    --bs-btn-font-size: 0.8125rem;
    --bs-btn-border-radius: var(--bs-border-radius);
}

/* Bootstrap's .btn-group-sm re-declares its own (larger-type) metrics at higher
   specificity; bring it back to the app's scale. Used by the grid pager. */
.btn-group-sm > .btn {
    --bs-btn-padding-y: 0.25rem;
    --bs-btn-padding-x: 0.5625rem;
    --bs-btn-font-size: 0.8125rem;
}

.form-control,
.form-select {
    padding: var(--hk-control-padding-y) var(--hk-control-padding-x);
    font-size: var(--hk-control-font-size);
    line-height: var(--hk-control-line-height);
    border-color: var(--hk-control-border-color);
    box-shadow: var(--hk-control-shadow);
}

/* The padding shorthand above wiped the clearance Bootstrap reserves for the
   select's chevron, so a long option ran underneath it. */
.form-select {
    padding-right: 2.125rem;
}

/* A field the user cannot type in, whether it is readonly or disabled. Bootstrap tints :disabled and
   leaves [readonly] white, which left the two kinds of "you cannot change this here" (§5) looking like two
   different things — a read-only order date read as an empty field somebody forgot to fill in.
   Both are the faint locked fill and, more to the point, both lose the carve: an editable field is cut
   into the page, a locked one lies flat on it. .form-control-plaintext is excluded, being deliberately
   not a box at all. */
.form-control[readonly]:not(.form-control-plaintext),
.form-select[readonly]:not(.form-control-plaintext),
.form-control:disabled,
.form-select:disabled {
    background-color: var(--hk-control-locked-bg);
    box-shadow: none;
}

/* Auto-growing text areas: one row to start with, and the box grows with what is typed instead of
   handing the user a scrollbar inside a three-row window. `rows` stays in the markup as the pre-support
   fallback — a browser without field-sizing renders exactly what it did before. The cap keeps a pasted
   wall of text from pushing the rest of the form off the screen; past it the scrollbar comes back. */
textarea.form-control {
    field-sizing: content;
    min-height: calc(var(--hk-control-line-height) * var(--hk-control-font-size)
                     + 2 * var(--hk-control-padding-y) + 2 * var(--bs-border-width));
    max-height: 14rem;
}

/* A "…" menu closing an input-group (the order's customer field) keeps its rounded corners.
   Bootstrap squares off the right edge of everything that is not the group's last child, and its own
   markup puts the <ul class="dropdown-menu"> after the toggle — so the button stops being last. Bootstrap
   exempts `.dropdown-toggle` from that rule, but our "…" deliberately does not carry that class: it would
   add the caret a three-dots menu must not have (§3). So the exemption is restated here for the case the
   class was standing in for — a button whose only remaining sibling is the menu it opens.

   The selector mirrors Bootstrap's shape on purpose. Theirs is six classes deep — the container, its
   :not(.has-validation) and four more :not()s — and a shorter selector simply loses to it however late it
   is declared, since source order only settles a tie. This one lands a step above, so it wins on its own
   merits rather than on where the file happens to sit. */
.input-group:not(.has-validation) > .btn:not(.dropdown-toggle):not(.form-floating):has(+ .dropdown-menu:last-child) {
    border-top-right-radius: var(--bs-border-radius);
    border-bottom-right-radius: var(--bs-border-radius);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--hk-focus-border-color);
    box-shadow: var(--hk-control-shadow), var(--hk-focus-ring);
}

/* Bootstrap's own .is-invalid:focus rule is a class more specific than the one above and replaces the
   shadow outright, so a focused invalid field dropped the carve while the fields around it kept it.
   Restate it with the carve composed in front of Bootstrap's danger ring, which is repeated here because
   Bootstrap writes it as a literal rather than a variable. (.is-valid and .was-validated have no callers.) */
.form-control.is-invalid:focus,
.form-select.is-invalid:focus {
    box-shadow: var(--hk-control-shadow), 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
}

.form-control::placeholder {
    color: var(--hk-n-400);
}

/* The -sm control collapses into the standard one. The order module was written
   with form-control-sm while HkDynamicForm renders standard controls, so the two
   sat at different heights inside the same form grid; one metric ends that. */
.form-control-sm,
.form-select-sm {
    padding: var(--hk-control-padding-y) var(--hk-control-padding-x);
    font-size: var(--hk-control-font-size);
    border-radius: var(--bs-border-radius);
}

.form-select-sm {
    padding-right: 2.125rem;
}

/* input-group-sm re-declares larger type at higher specificity than the -sm
   classes above (it is how the customer picker's buttons drifted to 14px text). */
.input-group-sm > .form-control,
.input-group-sm > .form-select,
.input-group-sm > .input-group-text,
.input-group-sm > .btn {
    padding: var(--hk-control-padding-y) var(--hk-control-padding-x);
    font-size: var(--hk-control-font-size);
    border-radius: var(--bs-border-radius);
}

.input-group-sm > .form-select {
    padding-right: 2.125rem;
}

.form-label {
    margin-bottom: 0.25rem;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--hk-n-600);
}

.form-check-input:focus {
    box-shadow: var(--hk-focus-ring);
}

.form-check-input:checked {
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);
}

.alert {
    --bs-alert-padding-x: 0.875rem;
    --bs-alert-padding-y: 0.625rem;
    --bs-alert-margin-bottom: 0.875rem;
    --bs-alert-border-radius: var(--bs-border-radius);
    font-size: 0.8125rem;
}

/* Bootstrap hangs the dismiss button from the alert's top edge and makes its own 1.25rem padding the
   offset — measured against Bootstrap's alert padding, not the denser one set above. In our alerts the
   cross therefore sat below the middle, and a hit area taller than the alert read as an oversized glyph.
   Centre it instead, and let the padding be the hit area rather than the offset. */
.alert-dismissible .btn-close {
    top: 50%;
    padding: 0.75rem 1rem;
    transform: translateY(-50%);
}

.card {
    --bs-card-spacer-y: 0.875rem;
    --bs-card-spacer-x: 0.875rem;
    --bs-card-title-spacer-y: 0.375rem;
    --bs-card-border-color: var(--bs-border-color);
    --bs-card-border-radius: var(--bs-border-radius);
    --bs-card-cap-bg: var(--hk-surface-sunken);
}

.row {
    --bs-gutter-x: 1rem;
}

.badge {
    font-weight: 600;
    font-size: 0.6875rem;
    letter-spacing: 0.01em;
    padding: 0.25em 0.5em;
}

.dropdown-menu {
    --bs-dropdown-font-size: 0.8125rem;
    --bs-dropdown-border-color: var(--bs-border-color);
    --bs-dropdown-border-radius: var(--bs-border-radius);
    --bs-dropdown-link-active-bg: var(--bs-primary);
    --bs-dropdown-padding-y: 0.25rem;
    --bs-dropdown-item-padding-y: 0.3125rem;
    box-shadow: var(--hk-shadow-md);
}

.breadcrumb {
    --bs-breadcrumb-font-size: 0.8125rem;
    --bs-breadcrumb-divider-color: var(--hk-n-400);
    --bs-breadcrumb-item-active-color: var(--bs-body-color);
}

.table {
    --bs-table-border-color: var(--bs-border-color);
}

/* Tabs: the selected tab is marked in the brand colour, not only by its white face — the label turns primary and
   a 2px accent runs along its top. Through Bootstrap's own tab variables, so a tenant's primary recolours it. */
.nav-tabs {
    --bs-nav-tabs-link-active-color: var(--bs-primary);
    --bs-nav-link-color: var(--bs-secondary-color);
    --bs-nav-link-hover-color: var(--bs-body-color);
    --bs-nav-link-font-weight: 500;
}

.nav-tabs .nav-link.active {
    font-weight: 600;
    box-shadow: inset 0 2px 0 var(--bs-primary);
}

/* ── 4. Buttons ─────────────────────────────────────────────────────────────
   Bootstrap 5.3 hardcodes the --bs-btn-* stack inside each variant, so a
   :root override of --bs-primary never reaches them. Re-declaring the stack
   per variant makes every button track the brand, including a tenant's
   runtime override. Hover/active shades are derived, never hand-picked. */

.btn-primary {
    --bs-btn-bg: var(--bs-primary);
    --bs-btn-border-color: var(--bs-primary);
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-primary) 86%, black);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-primary) 82%, black);
    --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
    --bs-btn-active-bg: color-mix(in srgb, var(--bs-primary) 78%, black);
    --bs-btn-active-border-color: color-mix(in srgb, var(--bs-primary) 74%, black);
    --bs-btn-disabled-bg: var(--bs-primary);
    --bs-btn-disabled-border-color: var(--bs-primary);
    box-shadow: var(--hk-shadow-xs);
}

.btn-outline-primary {
    --bs-btn-color: var(--bs-primary);
    --bs-btn-border-color: color-mix(in srgb, var(--bs-primary) 45%, white);
    --bs-btn-hover-bg: var(--bs-primary);
    --bs-btn-hover-border-color: var(--bs-primary);
    --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
    --bs-btn-active-bg: var(--bs-primary);
    --bs-btn-active-border-color: var(--bs-primary);
    --bs-btn-disabled-color: var(--bs-primary);
    --bs-btn-disabled-border-color: var(--bs-primary);
}

/* The stock outline-secondary is a mid-gray border on white — the single most
   over-used button in the app. Softened to the ramp so a row of them reads as
   one object instead of a fence. */
.btn-outline-secondary {
    --bs-btn-color: var(--hk-n-700);
    --bs-btn-border-color: var(--hk-n-300);
    --bs-btn-bg: var(--hk-n-0);
    --bs-btn-hover-color: var(--hk-n-900);
    --bs-btn-hover-bg: var(--hk-n-100);
    --bs-btn-hover-border-color: var(--hk-n-400);
    --bs-btn-active-color: var(--hk-n-900);
    --bs-btn-active-bg: var(--hk-n-200);
    --bs-btn-active-border-color: var(--hk-n-400);
    --bs-btn-disabled-color: var(--hk-n-400);
    --bs-btn-disabled-border-color: var(--hk-n-200);
    --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}

.btn-success {
    --bs-btn-bg: var(--bs-success);
    --bs-btn-border-color: var(--bs-success);
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-success) 86%, black);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-success) 82%, black);
    --bs-btn-focus-shadow-rgb: var(--bs-success-rgb);
    --bs-btn-active-bg: color-mix(in srgb, var(--bs-success) 78%, black);
    --bs-btn-active-border-color: color-mix(in srgb, var(--bs-success) 74%, black);
    --bs-btn-disabled-bg: var(--bs-success);
    --bs-btn-disabled-border-color: var(--bs-success);
}

.btn-warning {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--bs-warning);
    --bs-btn-border-color: var(--bs-warning);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-warning) 86%, black);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-warning) 82%, black);
    --bs-btn-focus-shadow-rgb: var(--bs-warning-rgb);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: color-mix(in srgb, var(--bs-warning) 78%, black);
    --bs-btn-active-border-color: color-mix(in srgb, var(--bs-warning) 74%, black);
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--bs-warning);
    --bs-btn-disabled-border-color: var(--bs-warning);
}

.btn-danger {
    --bs-btn-bg: var(--bs-danger);
    --bs-btn-border-color: var(--bs-danger);
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-danger) 86%, black);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-danger) 82%, black);
    --bs-btn-focus-shadow-rgb: var(--bs-danger-rgb);
    --bs-btn-active-bg: color-mix(in srgb, var(--bs-danger) 78%, black);
    --bs-btn-active-border-color: color-mix(in srgb, var(--bs-danger) 74%, black);
    --bs-btn-disabled-bg: var(--bs-danger);
    --bs-btn-disabled-border-color: var(--bs-danger);
}

.btn-outline-danger {
    --bs-btn-color: var(--bs-danger);
    --bs-btn-border-color: color-mix(in srgb, var(--bs-danger) 40%, white);
    --bs-btn-hover-bg: var(--bs-danger);
    --bs-btn-hover-border-color: var(--bs-danger);
    --bs-btn-focus-shadow-rgb: var(--bs-danger-rgb);
    --bs-btn-active-bg: var(--bs-danger);
    --bs-btn-active-border-color: var(--bs-danger);
}

/* Stock outline-warning is yellow-on-white — unreadable. Text uses the darkened
   emphasis tone; the fill only arrives on hover. Same treatment for success. */
.btn-outline-warning {
    --bs-btn-color: var(--bs-warning-text-emphasis);
    --bs-btn-border-color: color-mix(in srgb, var(--bs-warning) 50%, white);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--bs-warning);
    --bs-btn-hover-border-color: var(--bs-warning);
    --bs-btn-focus-shadow-rgb: var(--bs-warning-rgb);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--bs-warning);
    --bs-btn-active-border-color: var(--bs-warning);
}

.btn-outline-success {
    --bs-btn-color: var(--bs-success-text-emphasis);
    --bs-btn-border-color: color-mix(in srgb, var(--bs-success) 45%, white);
    --bs-btn-hover-bg: var(--bs-success);
    --bs-btn-hover-border-color: var(--bs-success);
    --bs-btn-focus-shadow-rgb: var(--bs-success-rgb);
    --bs-btn-active-bg: var(--bs-success);
    --bs-btn-active-border-color: var(--bs-success);
}

.btn-link {
    --bs-btn-color: var(--bs-link-color);
    --bs-btn-hover-color: var(--bs-link-hover-color);
    --bs-btn-font-weight: 500;
    text-decoration: none;
}

.btn-link:hover {
    text-decoration: underline;
}

/* Quiet action: no chrome until pointed at. The toolbar is built from these. */
.btn-ghost {
    --bs-btn-color: var(--hk-n-600);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: var(--hk-n-900);
    --bs-btn-hover-bg: var(--hk-n-100);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: var(--hk-n-900);
    --bs-btn-active-bg: var(--hk-n-200);
    --bs-btn-active-border-color: transparent;
    --bs-btn-disabled-color: var(--hk-n-400);
    --bs-btn-disabled-border-color: transparent;
    --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}

.btn-accent {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--hk-accent);
    --bs-btn-border-color: var(--hk-accent);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--hk-accent-hover);
    --bs-btn-hover-border-color: var(--hk-accent-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--hk-accent-hover);
    --bs-btn-active-border-color: var(--hk-accent-hover);
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--hk-accent);
    --bs-btn-disabled-border-color: var(--hk-accent);
}

/* Icon-only square button — used for row actions and compact controls.
   The flex centring and glyph size come from .btn / .btn > .bi above. */
.btn-icon {
    --bs-btn-padding-x: 0.375rem;
    --bs-btn-padding-y: 0.25rem;
    min-width: 1.75rem;
    line-height: 1;
}

/* The glyph rides the button's flex centring (whitespace between the <i> and
   the label is ignored in flex layout, so the gap above is the whole spacing).
   Slightly larger than the text so it reads as a symbol, not a smudge. */
.btn > .bi {
    flex: 0 0 auto;
    font-size: 0.9375rem;
    line-height: 1;
}

/* ── 5. Toolbar — the action rail ───────────────────────────────────────────
   The order console's defining control. A page can offer twenty actions, and
   twenty outlined buttons in a row read as a fence: uniform, unscannable,
   nothing findable. So the rail is one recessed object, its actions are quiet
   until pointed at, and meaning comes from grouping rather than from twenty
   competing borders. Exactly one action per rail should be emphasised.

   Existing markup gets this for free: buttons written as .btn-outline-secondary
   inside a rail are re-skinned to ghost below, so no page has to change to
   benefit. Add .hk-toolbar-sep between intent groups where it helps. */

.hk-toolbar,
.hk-order-toolbar {
    /* The rail's ground and the step away from it that its own children need in order to read
       against it — a ghost button's hover fill, a filter field's box. Declared as a pair because
       they are one decision: wherever the ground inverts (a rail on the section body's own ground,
       §7), the children have to invert with it or they land on their own colour and vanish. */
    --hk-rail-bg: var(--hk-surface-sunken);
    --hk-rail-contrast: var(--hk-surface-raised);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem;
    padding: 0.25rem;
    margin-bottom: 0.875rem;
    background: var(--hk-rail-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
}

/* A plain row of standalone buttons — the strip under a form ("Add delivery",
   "Validate addresses", import/export). Unlike the rail it has no chrome of its
   own; vertical rhythm comes from the section body (§7) or a context margin. */
.hk-btn-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

/* Quiet by default, inside a rail only — the same class keeps its outline
   everywhere else on the page. */
.hk-toolbar .btn-outline-secondary,
.hk-order-toolbar .btn-outline-secondary {
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-color: var(--hk-n-600);
    --bs-btn-hover-color: var(--hk-n-900);
    --bs-btn-hover-bg: var(--hk-rail-contrast);
    --bs-btn-hover-border-color: var(--bs-border-color);
    --bs-btn-active-color: var(--hk-n-900);
    --bs-btn-active-bg: var(--hk-n-200);
    --bs-btn-active-border-color: var(--bs-border-color);
    --bs-btn-disabled-color: var(--hk-n-400);
    --bs-btn-disabled-border-color: transparent;
}

/* The upload trigger variant: its button sits under an invisible file input, so
   hover is driven from the trigger label (colours in HkFileUpload.razor.css). */
.hk-toolbar .btn-outline-secondary:hover,
.hk-order-toolbar .btn-outline-secondary:hover,
.hk-toolbar .hk-file-upload-trigger:hover .btn-outline-secondary:not(.disabled),
.hk-order-toolbar .hk-file-upload-trigger:hover .btn-outline-secondary:not(.disabled) {
    box-shadow: var(--hk-shadow-sm);
}

/* A "more" menu with nothing in it (every item hidden by a permission or an
   empty list) shows no toggle — design.md §3: no dropdown if nothing needs hiding. */
.hk-toolbar > .dropdown:not(:has(.dropdown-item)) {
    display: none;
}

/* Destructive actions stay neutral at rest and only declare themselves on
   hover — a red button sitting permanently in the rail reads as a warning
   about the page rather than a description of the action. */
.hk-toolbar .btn-outline-danger,
.hk-order-toolbar .btn-outline-danger {
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-color: var(--hk-n-600);
    --bs-btn-hover-bg: var(--bs-danger-bg-subtle);
    --bs-btn-hover-color: var(--bs-danger-text-emphasis);
    --bs-btn-hover-border-color: var(--bs-danger-border-subtle);
    --bs-btn-active-bg: var(--bs-danger-bg-subtle);
    --bs-btn-active-color: var(--bs-danger-text-emphasis);
    --bs-btn-active-border-color: var(--bs-danger-border-subtle);
    --bs-btn-disabled-color: var(--hk-n-400);
    --bs-btn-disabled-border-color: transparent;
}

/* The emphasised action keeps its fill and gains a hairline of separation. */
.hk-toolbar .btn-primary,
.hk-order-toolbar .btn-primary {
    box-shadow: var(--hk-shadow-sm);
}

/* Hairline between intent groups. */
/* The divider between a rail's intent groups. A step darker than a plain border: it has to carry across a
   row of buttons that already have outlines of their own, and at the border colour it read as a rendering
   artefact rather than as a deliberate break. */
.hk-toolbar-sep {
    flex: 0 0 auto;
    align-self: stretch;
    width: 1px;
    min-height: 1.25rem;
    margin: 0.125rem 0.3125rem;
    background: var(--hk-n-300);
}

/* Pushes everything after it to the far end of the rail. */
.hk-toolbar-spacer {
    flex: 1 1 auto;
}

.hk-toolbar .form-control,
.hk-toolbar .form-select,
.hk-order-toolbar .form-control,
.hk-order-toolbar .form-select {
    width: auto;
    min-width: 9rem;
    /* background-color, not the shorthand: the shorthand wiped Bootstrap's chevron image off every
       select in a toolbar, so it rendered as a plain text field. */
    background-color: var(--hk-rail-contrast);
}

/* A checkbox in a rail sits on the rail's centre line with its label. Bootstrap floats the box into a
   1.5em indent and drops it by a fixed .25em from the top of the line, which only centres it against text
   at the body size; at the rail's smaller size the box rode above its label. A flex row centres both. */
.hk-toolbar .form-check,
.hk-order-toolbar .form-check {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    min-height: 0;
    margin-bottom: 0;
    padding-left: 0;
}

.hk-toolbar .form-check .form-check-input,
.hk-order-toolbar .form-check .form-check-input {
    float: none;
    flex-shrink: 0;
    margin: 0;
}

/* A rail pinned above a long page while it scrolls. It sticks BELOW the
   layout's own sticky topbar — the layout publishes its height as
   --hk-sticky-top — and stays under it in the stacking order, so the rail can
   never draw over the chrome. */
.hk-toolbar-sticky {
    position: sticky;
    top: var(--hk-sticky-top, 0px);
    z-index: 5;
}

/* The seam. However exactly the two heights land — a border, a rounded rem, a browser's subpixel — a
   pinned rail that starts one pixel too low lets the page scroll through the crack above it, which is
   what it looked like. This paints the page's own ground over that strip, so there is nothing to see
   through. It sits above the rail, in the clear space the page header already leaves, so it changes
   nothing while the rail is unpinned. */
.hk-toolbar-sticky::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: 0.75rem;
    background: var(--hk-app-bg);
}

/* ── 6. Page header ─────────────────────────────────────────────────────────
   One title treatment, replacing eight near-identical per-page definitions. */

/* The head stacks eyebrow → title → sub. It lives here rather than in a scoped
   sheet because four pages referenced the previous scoped version across
   component boundaries, where Blazor's [b-*] attribute could never match it —
   so three of them rendered their header unstyled. */

.hk-page-head {
    margin-bottom: 1.25rem;
}

/* Names the module the page belongs to, above its own title. */
.hk-page-eyebrow {
    margin-bottom: 0.25rem;
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--bs-primary);
}

.hk-page-title {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 650;
    letter-spacing: -0.011em;
    color: var(--bs-emphasis-color);
}

.hk-page-sub {
    margin: 0.25rem 0 0;
    font-size: 0.8125rem;
    color: var(--bs-secondary-color);
}

/* A head whose title shares its line with counts or an action. */
.hk-page-head-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem 1rem;
}

/* The sub-view head: back button, title and the primary action on one line —
   the bar every admin editor opens with. Was written as a raw utility string
   (d-flex gap-2 mb-3 align-items-center) in ~50 views; the class owns it now,
   so the whole family can be retuned in one place. */
.hk-view-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.hk-view-head > h1,
.hk-view-head > h2,
.hk-view-head > h3,
.hk-view-head > h4,
.hk-view-head > h5,
.hk-view-head > h6 {
    margin: 0;
    font-size: 1.0625rem;
    font-weight: 650;
    letter-spacing: -0.011em;
    color: var(--bs-emphasis-color);
}

/* ── 7. Section card ────────────────────────────────────────────────────────
   Chrome for each editor section (General / Buyer / Delivery / Payment /
   Order items / Attachments / Jobs) and for the panels on the detail views. */

.hk-order-section {
    margin-bottom: 0.75rem;
    background: var(--hk-surface-raised);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    box-shadow: var(--hk-shadow-xs);
    overflow: hidden;
}

/* The brand-filled band from the design guidelines' table headers: primary fill, white micro-caps.
   Built on --bs-primary so a tenant's runtime override recolours every section with it. */
.hk-order-section-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.875rem;
    font-size: 0.75rem;
    font-weight: 650;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--hk-on-primary);
    background: var(--bs-primary);
    border-bottom: 1px solid var(--bs-primary);
    /* Everything inside the band reads in the on-primary colour. Bootstrap's text utilities, links and
       buttons take their colours from these variables, so re-pointing them here needs no !important. */
    --bs-secondary-color: color-mix(in srgb, var(--hk-on-primary) 82%, transparent);
    --bs-link-color-rgb: var(--hk-on-primary-rgb);
    --bs-link-hover-color-rgb: var(--hk-on-primary-rgb);
}

/* The listing's grid section: its summary is the row count and the total, which is data the page must not
   swallow. The shared summary clips to one line (that is right for a collapsed editor section's key facts,
   where the title has to stay readable), so here it wraps instead — at every width, not below a breakpoint:
   a long count label plus a total ("Anzahl der Bestellungen: 1.284 · 12.345.678,00 SEK") outgrows a tablet
   just as it does a phone, and an ellipsis there would eat the numbers the user came for. */
.hk-listing-section > .hk-order-section-head {
    flex-wrap: wrap;
}

.hk-listing-section > .hk-order-section-head > .hk-order-section-summary {
    overflow: visible;
    white-space: normal;
    /* The band steps its summary back through opacity, which no child can undo — so the step-back moves onto
       the count below and the total keeps the head's full white. */
    opacity: 1;
}

/* The row count is context; the total is the number the filter was for, so it keeps the weight it carried
   beside the page title before the card. The separator between them is quiet like the count, and carries its
   own spaces, so it needs no margin of its own. */
.hk-listing-section .hk-listing-count,
.hk-listing-section .hk-listing-sep {
    opacity: 0.82;
}

.hk-listing-section .hk-listing-total {
    font-weight: 600;
}

/* On a phone the band stacks: the numbers take their own line under the title. */
@media (max-width: 575.98px) {
    .hk-listing-section > .hk-order-section-head > .hk-order-section-summary {
        flex-basis: 100%;
        margin-left: 0;
    }
}

.hk-order-section-head .btn-link {
    --bs-btn-color: var(--hk-on-primary);
    --bs-btn-hover-color: var(--hk-on-primary);
    --bs-btn-active-color: var(--hk-on-primary);
}

/* An outline button in the band: white outline at rest, inverts to a white fill on hover. */
.hk-order-section-head .btn-outline-secondary {
    --bs-btn-color: var(--hk-on-primary);
    --bs-btn-border-color: color-mix(in srgb, var(--hk-on-primary) 55%, transparent);
    --bs-btn-hover-color: var(--bs-primary);
    --bs-btn-hover-bg: var(--hk-on-primary);
    --bs-btn-hover-border-color: var(--hk-on-primary);
    --bs-btn-active-color: var(--bs-primary);
    --bs-btn-active-bg: var(--hk-on-primary);
    --bs-btn-active-border-color: var(--hk-on-primary);
    --bs-btn-disabled-color: color-mix(in srgb, var(--hk-on-primary) 55%, transparent);
    --bs-btn-disabled-border-color: transparent;
}

/* Secondary text in a section header (counts, hints) — not part of the label. Inherits the head's
   white and steps back through opacity, so it stays readable on any tenant's primary. */
.hk-order-section-summary {
    margin-left: 0.5rem;
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    color: inherit;
    opacity: 0.82;
}

/* The body is a ground, not a surface. Foundation's .accordion-content — the band every job and order
   field sat on in WebForms — was #f5f5f5 under near-white fields, so a control read as a shape lying on
   something. With the page now white (§1) this is the only tinted ground on the screen, which is what
   makes a section read as the thing you work in rather than as a rectangle drawn around some fields.
   Anything inside that is itself a surface — a table, a rail, a sub-section — takes the raised ground
   below and reads as sitting on the body. */
.hk-order-section-body {
    padding: 0.75rem;
    background: var(--hk-surface-sunken);
}

/* A rail inside a section is white; the page's own rail, which sits on the white page, is the grey one.
   That is the whole rule: the rail always contrasts with the ground it lies on, so it inverts once it is
   inside a section body. The rail's ground and the contrast its children draw against are one pair (§5),
   so both swap together — setting only the ground would leave a ghost button's hover fill and a filter
   field's box on the colour the rail now is.
   Descendant, not child: a rail is usually rendered by a component of its own (the delivery address
   fields, the distribution matrix), so it is rarely a direct child of the body, and the sub-section's body
   is a second ground it can sit on. Matching only direct children left those rails grey inside a white
   section, which is exactly the inconsistency this rule exists to prevent. */
.hk-order-section-body .hk-toolbar,
.hk-order-section-body .hk-order-toolbar,
.hk-section-plain-body .hk-toolbar,
.hk-section-plain-body .hk-order-toolbar {
    --hk-rail-bg: var(--hk-surface-raised);
    --hk-rail-contrast: var(--hk-surface-sunken);
}

/* Vertical rhythm between the blocks a section stacks — a form grid, a button
   strip, a table, a note. Each file used to space these with its own utility
   (or none, which is how a button row ended up touching the selects above it);
   one flow rule replaces all of that. Sibling margins collapse against it, so
   a block that carries its own margin doesn't double up. .form-check rows are
   exempt — a checkbox group reads as one unit, not as separate blocks. */
.hk-order-section-body > * + *:not(.form-check):not(.hk-modal):not(.hk-modal-backdrop) {
    margin-top: 0.75rem;
}

/* A section whose body is a table: the table draws its own edges, so the
   padding would only push it away from them. */
.hk-order-section-body.is-flush {
    padding: 0;
}

/* The admin module view marker. It carries no surface: the module content sits
   on --hk-app-bg and the sections (.hk-order-section) and grids are the page's
   only cards, as on the order console. It used to be a white card of its own,
   which put every admin section card inside a page card — two frames where the
   order pages draw one (design.md §13). */
.hk-admin-module {
    display: block;
}

/* The explanatory line: what a block of settings is for, what a field expects, and the empty states that
   say a list has nothing in it. Italic, because all of these are the interface talking about itself rather
   than showing the user their data — the one place in the app where the slant is the distinction. */
.hk-order-note {
    margin: 0;
    font-size: 0.8125rem;
    font-style: italic;
    color: var(--bs-secondary-color);
}

/* ── 8. Data table ──────────────────────────────────────────────────────────
   One definition for every hand-rolled table in the module. Fourteen files
   previously repeated this rule set verbatim under six different class names
   (.hk-items-table, .hk-log-table, .hk-credit-table, .hk-cq-table,
   .hk-attach-table, .hk-oa-table); all of them now render from here. */

/* The raised ground is explicit, not inherited: a table is a surface on the section body (§7), which is
   the sunken neutral — on a transparent table the rows would dissolve into the body they sit on. The
   header row shares the table's raised ground and is set off by its hairline and micro-caps alone; a
   sunken header merged with the body around the table. Only the footer band is sunken. The Radzen grid
   states the same in --rz-grid-background-color / --rz-grid-header-background-color. */
.hk-table {
    width: 100%;
    margin: 0;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--hk-grid-font-size);
    background: var(--hk-surface-raised);
}

.hk-table th {
    position: relative;
    padding: 0.4375rem var(--hk-row-px);
    text-align: left;
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
    background: var(--hk-surface-raised);
    border-bottom: 1px solid var(--bs-border-color);
    white-space: nowrap;
    vertical-align: middle;
}

.hk-table td {
    padding: var(--hk-row-py) var(--hk-row-px);
    border-bottom: 1px solid var(--bs-border-color);
    vertical-align: middle;
}

.hk-table tbody tr:last-child > td {
    border-bottom: 0;
}

.hk-table tbody tr:hover > td {
    background: var(--hk-n-25);
}

/* ── The selected row ──
   One colour for every grid in the app: HkGrid's .hk-grid-row-selected, the Radzen grid (repainted in
   radzen-overrides.css §5) and the hand-written .hk-table screens that mark a row .is-selected.
   Bootstrap 5.3 paints a table row's hover as an inset box-shadow over the cell, so a plain background on
   the cell would show through it muddied. --bs-table-bg-state is the variable that shadow reads, which is
   why the selection is set there instead: the selected row keeps its colour while hovered, one step
   stronger, and the two states compose instead of fighting. */
.hk-grid-row-selected > *,
.hk-table tr.is-selected > td {
    --bs-table-bg-state: var(--hk-row-selected-bg);
    background: var(--hk-row-selected-bg);
}

.table-hover > tbody > .hk-grid-row-selected:hover > *,
.hk-table tbody tr.is-selected:hover > td {
    --bs-table-bg-state: var(--hk-row-selected-bg-hover);
    background: var(--hk-row-selected-bg-hover);
}

.hk-table .hk-num {
    text-align: right;
    white-space: nowrap;
}

/* A table whose cells align to their first line (logs, attachment lists) —
   two dialogs used to carry identical scoped copies of this rule. */
.hk-table.is-top td {
    vertical-align: top;
}

/* An editable numeric cell: the input keeps the column's right alignment. */
.hk-table .hk-num input {
    min-width: 6rem;
    text-align: right;
}

/* Totals / summary row. */
.hk-table tfoot td {
    font-weight: 650;
    background: var(--hk-surface-sunken);
    border-top: 1px solid var(--hk-n-300);
    border-bottom: 0;
}

/* A subordinate row (order sub-items) reads as belonging to the row above. */
.hk-table tr.is-child > td {
    color: var(--bs-secondary-color);
    background: var(--hk-n-25);
}

.hk-table-total {
    margin-top: 0.625rem;
    padding: 0.5rem 0.625rem;
    text-align: right;
    font-weight: 650;
    color: var(--bs-emphasis-color);
    background: var(--hk-surface-sunken);
    border-radius: var(--bs-border-radius-sm);
}

/* Secondary line under a cell's primary value. */
.hk-cell-meta {
    display: block;
    font-size: 0.75rem;
    color: var(--bs-secondary-color);
}

/* Selection / tool columns shrink to their content. */
.hk-table .hk-check,
.hk-items-select {
    width: 1%;
    text-align: center;
    white-space: nowrap;
}

/* Bootstrap's own .table is used on about a dozen screens alongside .hk-table
   (HkGrid renders one). Giving it the same cell metrics and header treatment
   means the two implementations are indistinguishable on screen, so a page can
   use whichever fits without the app looking like it was built by two teams.
   Stock .table is vertical-align:top with .5rem padding — that is how grid
   rows ended up taller and top-aligned next to the hand-rolled tables. */
.table {
    vertical-align: middle;
}

.table > :not(caption) > * > * {
    padding: var(--hk-row-py) var(--hk-row-px);
}

.table > thead > tr > th {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
    background: var(--hk-surface-raised);
    border-bottom-color: var(--bs-border-color);
    white-space: nowrap;
}

/* Bootstrap's striping is a heavier texture than this density wants; hover alone
   is enough to track a row, and it keeps status tints readable in the cells. */
.table-striped > tbody > tr:nth-of-type(odd) > * {
    --bs-table-bg-type: transparent;
}

.table-hover > tbody > tr:hover > * {
    --bs-table-bg-state: var(--hk-n-25);
}

/* Column priority, measured against the table's own width rather than the
   viewport's. design.md §6 asks the less important columns to drop on a narrow
   screen, and names Bootstrap's d-none d-md-table-cell for it — but those key
   off the viewport, and a table is just as often short of room because of where
   it sits: the job page puts one in a col-lg-5 that is 259px wide at a 1024px
   viewport, where no d-* class hides anything and the price table would need a
   2050px viewport to fit. The wrapper is the container, so a column drops when
   the table is actually cramped, whatever made it cramped.
   Two tiers: p2 goes first (detail you can open the row to read), p1 follows
   (useful, not identifying). The identifying column and the command columns
   carry neither and are never hidden.
   A column may only drop when the reader can get the value back — the dialog
   the row opens, and only for someone who may open it, so the priority follows
   that permission (PrintFiles, JobPrintPartGrid). A table with no row to open
   carries no priority at all and scrolls instead (PriceCalculationTable).
   One side effect to know: container-type: inline-size implies contain: layout,
   so this wrapper becomes a containing block for position: fixed descendants.
   With its own overflow-x that clips them, which defeats the
   data-bs-popper-config '{"strategy":"fixed"}' escape hatch design.md §3 gives a
   hand-written menu. No table holds one today; keep such a menu outside the
   wrapper. */
.hk-table-cq {
    container: hk-table / inline-size;
}

@container hk-table (max-width: 33rem) {
    .hk-col-p2 {
        display: none;
    }
}

@container hk-table (max-width: 22rem) {
    .hk-col-p1 {
        display: none;
    }
}

/* ── 9. Row actions ─────────────────────────────────────────────────────────
   Was the defect that started this pass: the cell was pinned to a 2rem width
   with up to eight controls inside, so they wrapped into a vertical stack and
   drove the row four lines tall. A row's actions are one horizontal cluster
   that never wraps, and the column takes exactly the width it needs. */

/* The cell stays a real table-cell — giving it display:flex would take it out
   of table layout and break column sizing. nowrap alone keeps the cluster on
   one line, and width:1% lets auto table layout shrink it to its content. */
.hk-actions,
.hk-table .hk-actions {
    width: 1%;
    white-space: nowrap;
    vertical-align: middle;
}

/* Icon controls in a row: square, quiet, and only tinted on hover. */
.hk-actions .btn {
    --bs-btn-padding-x: 0.3125rem;
    --bs-btn-padding-y: 0.1875rem;
    --bs-btn-color: var(--hk-n-500);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: var(--hk-n-900);
    --bs-btn-hover-bg: var(--hk-n-100);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-bg: var(--hk-n-200);
    --bs-btn-active-border-color: transparent;
    --bs-btn-disabled-color: var(--hk-n-300);
    --bs-btn-disabled-border-color: transparent;
    min-width: 1.5rem;
    line-height: 1;
    vertical-align: middle;
    text-decoration: none;
}

.hk-actions .btn > .bi {
    font-size: 0.875rem;
}

/* The gap between the buttons of a cluster lives here, not as me-1 on every button. */
.hk-actions .btn + .btn {
    margin-left: 0.25rem;
}

.hk-actions .btn.text-danger:hover {
    --bs-btn-hover-color: var(--bs-danger);
    --bs-btn-hover-bg: var(--bs-danger-bg-subtle);
}

/* A destructive row action written as .btn-outline-danger keeps the same
   contract: neutral at rest (the re-skin above), danger-tinted on hover. */
.hk-actions .btn-outline-danger:hover {
    --bs-btn-hover-color: var(--bs-danger-text-emphasis);
    --bs-btn-hover-bg: var(--bs-danger-bg-subtle);
}

/* An in-place-edit row's Save keeps its fill — the one emphasised action a
   row may carry; everything else in the cluster stays quiet. */
.hk-actions .btn-primary {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--bs-primary);
    --bs-btn-border-color: var(--bs-primary);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-primary) 86%, black);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-primary) 82%, black);
}

/* The row checkbox sits with the actions but is not one of them. */
.hk-actions .form-check-input {
    margin: 0 0.25rem 0 0;
    flex: 0 0 auto;
}

/* ── 10. States ─────────────────────────────────────────────────────────────
   Empty, permission-denied and loading placeholders. Eight files previously
   defined this same centred column independently. An empty screen states what
   is missing; it does not apologise. */

.hk-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 3rem 1.5rem;
    text-align: center;
    color: var(--bs-secondary-color);
}

.hk-state > .bi,
.hk-state i {
    display: block;
    margin-bottom: 0.25rem;
    font-size: 2rem;
    color: var(--hk-n-300);
}

.hk-state p {
    margin: 0;
    font-size: 0.875rem;
}

/* The inline form of the loading state, for a dialog body or a section that loads separately
   from its page (HkLoading Compact). The block form reuses .hk-state above. */
.hk-loading-inline {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 1.5rem 0;
    color: var(--bs-secondary-color);
    font-size: 0.875rem;
}

/* A screen that failed, rather than one that is merely empty: the icon carries the
   warning colour, and the reference id is set in the code face so it can be read
   back over the phone. */
.hk-error-panel > .bi,
.hk-error-panel i {
    color: var(--bs-warning-text-emphasis, #997404);
}

.hk-error-panel-message {
    max-width: 34rem;
    color: var(--bs-body-color);
}

.hk-error-panel-reference code {
    color: inherit;
    font-size: 0.8125rem;
    overflow-wrap: anywhere;
}

.hk-error-panel-actions {
    margin-top: 0.5rem;
}

/* ── 11. Form grid ──────────────────────────────────────────────────────────
   One definition serves both form families: HkDynamicForm (config-driven
   InputElementCollection fields) and the hand-written sections and dialogs.

   The WebForms original this replaces — EditProperties — let every call site
   choose between two arrangements, and 41 of the order module's 46 instances
   chose the second:

     default  label beside the field, one per row
              (LabelColumnCssClass="small-12 medium-4 large-3 column")
     grid     label above the field, N per row
              (GroupCssClass="small-up-1 medium-up-2 large-up-N")

   Both live here, chosen by a container class. A row never decides its own
   arrangement, so the two families can no longer drift apart. */

.hk-process-job {
    display: flex;
    flex-direction: column;
}

/* ── The two containers ──
   Foundation capped the column count per breakpoint. The same behaviour without
   hard-coded breakpoints: tracks are at least --hk-form-col-min wide, and never
   narrower than an Nth of the container, so N is the ceiling and the container's
   own width — not the viewport's — decides. Nested forms therefore behave. */

.hk-form-rows,
.hk-form-grid {
    --hk-form-gap-x: 1.25rem;
    --hk-form-gap-y: 0.5rem;
    display: grid;
    gap: var(--hk-form-gap-y) var(--hk-form-gap-x);
    align-items: start;
}

.hk-form-rows {
    grid-template-columns: 1fr;
}

.hk-form-grid {
    --hk-form-cols: 4;
    --hk-form-col-min: 14rem;
    grid-template-columns: repeat(auto-fill, minmax(max(var(--hk-form-col-min),
        (100% - (var(--hk-form-cols) - 1) * var(--hk-form-gap-x)) / var(--hk-form-cols)), 1fr));
}

/* Column caps, mapping 1:1 onto Foundation's `-up-N`. */
.hk-form-grid-1 { --hk-form-cols: 1; }
.hk-form-grid-2 { --hk-form-cols: 2; }
.hk-form-grid-3 { --hk-form-cols: 3; }
.hk-form-grid-6 { --hk-form-cols: 6; }

/* Twelve columns — a form whose fields were given deliberate, unequal widths, as
   the order's header sections were. A field spans 3 unless it says otherwise, so
   only the narrow (2) and the wide (6) ones carry a class.

   Twelve columns on a large screen, everything half width on a medium one, and one
   field per row on a phone. Below the first step the spans are dropped — a 2-of-12
   field at tablet width would be unusable — which is what `medium-6 small-12` did.

   The steps were Foundation's own (1024 / 640), because that is where these layouts
   come from. They are Bootstrap's now — xl 1200, then lg 992, which is also the width
   §11 already gives as the point where the app goes to one column, so these forms and
   the rest of the page now break in the same places. A quarter of a 1024px row is about
   250px, and a field carrying a control plus a row of buttons — the order's customer
   picker — had nowhere left to put the control by then. */
.hk-form-grid-12 {
    grid-template-columns: repeat(12, minmax(0, 1fr));
}

.hk-form-grid-12 > .hk-form-row,
.hk-form-grid-12 > .hk-form-fieldset > .hk-form-row { grid-column: span 3; }

.hk-form-grid-12 > .hk-form-col-2,
.hk-form-grid-12 > .hk-form-fieldset > .hk-form-col-2 { grid-column: span 2; }

.hk-form-grid-12 > .hk-form-col-6,
.hk-form-grid-12 > .hk-form-fieldset > .hk-form-col-6 { grid-column: span 6; }

/* ── The row break ──
   A break ends the current row so the next group starts on a fresh one. It is deliberately NOT a grid
   item: an empty item sits in a row of its own, and a grid puts its row gap on both sides of that row, so
   a break cost two gaps where every other row boundary costs one — the wider band read as a section
   boundary the form never meant. (A negative margin does not fix it: track sizing floors an item's
   contribution at zero, so the row collapses but both gaps stay.) Out of layout, and the next field
   simply starts in column 1, which is the same result for the same gap as everywhere else.
   Naming a start line drops the span the field would have inherited above, so the spans are restated —
   except for .hk-form-span, which sets both its own lines further down. */
.hk-form-grid > .hk-form-break,
.hk-form-grid > .hk-form-fieldset > .hk-form-break {
    display: none;
}

.hk-form-grid > .hk-form-break + *,
.hk-form-grid > .hk-form-fieldset > .hk-form-break + * {
    grid-column-start: 1;
}

.hk-form-grid-12 > .hk-form-break + *:not(.hk-form-span),
.hk-form-grid-12 > .hk-form-fieldset > .hk-form-break + *:not(.hk-form-span) {
    grid-column-end: span 3;
}

.hk-form-grid-12 > .hk-form-break + .hk-form-col-2,
.hk-form-grid-12 > .hk-form-fieldset > .hk-form-break + .hk-form-col-2 {
    grid-column-end: span 2;
}

.hk-form-grid-12 > .hk-form-break + .hk-form-col-6,
.hk-form-grid-12 > .hk-form-fieldset > .hk-form-break + .hk-form-col-6 {
    grid-column-end: span 6;
}

@media (max-width: 1199.98px) {
    .hk-form-grid-12 {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .hk-form-grid-12 > .hk-form-row,
    .hk-form-grid-12 > .hk-form-col-2,
    .hk-form-grid-12 > .hk-form-col-6,
    .hk-form-grid-12 > .hk-form-fieldset > .hk-form-row,
    .hk-form-grid-12 > .hk-form-fieldset > .hk-form-col-2,
    .hk-form-grid-12 > .hk-form-fieldset > .hk-form-col-6 {
        grid-column: span 1;
    }

    /* The restated spans above outlive this breakpoint on source order alone, so they are stepped down
       here too — otherwise the one field after each break kept its desktop width on a tablet. */
    .hk-form-grid-12 > .hk-form-break + *:not(.hk-form-span),
    .hk-form-grid-12 > .hk-form-fieldset > .hk-form-break + *:not(.hk-form-span) {
        grid-column-end: span 1;
    }
}

@media (max-width: 991.98px) {
    .hk-form-grid-12 {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ── A field ──
   In .hk-form-rows it is a label|field pair; in .hk-form-grid the container
   flips it to a stacked cell. Standalone (no container) keeps the pair, so a
   lone row in a dialog still renders correctly. */

.hk-form-row {
    display: grid;
    grid-template-columns: minmax(8rem, 12rem) 1fr;
    align-items: center;
    gap: 0.375rem 1rem;
}

/* The row is a grid too, so its own children are grid items and inherit the same `min-width: auto` trap as
   the row did: a control that cannot shrink below its min-content pushes straight out of the row. An
   input-group with several buttons is exactly that, which is why the customer field still overhung its
   column after the row itself was allowed to shrink. Clearing it here lets the control give way — the
   dropdown ellipsises, the buttons wrap — instead of the field growing wider than the column it lives in. */
.hk-form-row > * {
    min-width: 0;
}

.hk-form-rows > .hk-form-row:not(:last-child),
:not(.hk-form-rows):not(.hk-form-grid) > .hk-form-row {
    margin-bottom: 0.625rem;
}

/* ── The seam between two field blocks ──
   A screen that writes some fields by hand and lets HkDynamicForm render the rest puts two containers
   next to each other — the edit-user dialog's name/e-mail block and the user's dynamic properties under
   it. To the reader that is one list of fields, so the seam has to cost what every other row boundary
   costs. It did not: the rhythm inside a block is a margin on the rows, and the last row deliberately
   drops it (it would otherwise leave a trailing gap under the block), so nothing spanned the seam and
   the two blocks touched — the last hand-written field sat noticeably tighter to the first dynamic one
   than to the field above it. The boundary goes on the block instead, and adds up to the same 1.125rem
   a row boundary does: the container's 0.5rem row gap plus the row's own 0.625rem. */
.hk-form-rows + .hk-form-rows,
.hk-form-rows + .hk-form,
.hk-form + .hk-form-rows {
    margin-top: 1.125rem;
}

.hk-form-grid > .hk-form-row,
.hk-form-grid > .hk-form-fieldset > .hk-form-row {
    grid-template-columns: 1fr;
    align-items: start;
    gap: 0.1875rem;
    /* A grid item's min-width is `auto`, meaning min-content — so a row refused to shrink below the widest
       thing in it and spilled into the column beside it instead. The tracks are minmax(0, 1fr) and shrink
       willingly; it was the field that would not. With this a field is never wider than its column, and an
       input-group with several buttons wraps them onto a second line rather than overrunning its neighbour. */
    min-width: 0;
}

/* A field that owns the full width: the whole grid in a grid container, a
   stacked label|field in a rows container (rich text, tables, nested blocks). */
.hk-form-row-block,
.hk-form-row.is-block {
    grid-template-columns: 1fr;
}

.hk-form-grid > .hk-form-row-block,
.hk-form-grid > .hk-form-row.is-block,
.hk-form-grid > .hk-form-group-title,
.hk-form-grid > .hk-form-span,
.hk-form-grid > .hk-form-fieldset > .hk-form-row-block,
.hk-form-grid > .hk-form-fieldset > .hk-form-row.is-block,
.hk-form-grid > .hk-form-fieldset > .hk-form-group-title,
.hk-form-grid > .hk-form-fieldset > .hk-form-span {
    grid-column: 1 / -1;
}

/* Anything that is not a field but shares the container — a button strip, a
   note, a nested table — spans it rather than taking a field's slot. */
.hk-form-span {
    min-width: 0;
}

/* A group of fields that can be switched off together. `disabled` on a fieldset
   disables every control inside it, which makes locking default-deny: a field
   added later is locked until someone deliberately puts it outside. It lays out
   as if it were not there, so the grid sees its rows as its own children. */
.hk-form-fieldset {
    display: contents;
}

/* The row break (.hk-form-break) is handled with the column rules above, where the spans it has to
   restate live. */

/* Both generated and hand-written field wrappers must be able to shrink. */
.hk-form-field {
    min-width: 0;
}

.hk-form-label {
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--hk-n-600);
}

.hk-form-value {
    font-weight: 600;
    color: var(--bs-emphasis-color);
}

.hk-text-block {
    white-space: pre-wrap;
    word-break: break-word;
}

.hk-mandatory {
    margin-left: 0.125rem;
    color: var(--bs-danger);
}

.hk-validation {
    grid-column: 2;
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    color: var(--bs-danger);
}

.hk-form-grid > .hk-form-row .hk-validation,
.hk-form-grid > .hk-form-fieldset > .hk-form-row .hk-validation,
.hk-form-row-block .hk-validation,
.hk-form-row.is-block .hk-validation {
    grid-column: 1;
}

.hk-checkbox-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
}

.hk-checkbox {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    margin: 0;
    font-size: 0.8125rem;
}

/* ── The stacked micro-label field ──
   A caption over its control, used by the job cards, the press-sheet editor and
   a dozen dialogs. Eleven scoped sheets used to repeat this identical rule
   under eleven names (.hk-price-field, .hk-pp-field, .hk-ps-field, …); the
   scoped classes remain only where they add a width. Usually written as a
   <label> wrapping its control, hence the margin reset. */
.hk-field {
    display: flex;
    flex-direction: column;
    gap: 0.1875rem;
    margin: 0;
    font-size: 0.75rem;
    color: var(--bs-secondary-color);
}

/* A secondary heading inside a dialog or panel body — an underlined run-in
   title separating blocks of fields (print-file parts, preflight reports). */
.hk-subhead {
    margin: 0.875rem 0 0.5rem;
    padding-bottom: 0.25rem;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--bs-emphasis-color);
    border-bottom: 1px solid var(--bs-border-color);
}

.hk-subhead:first-child {
    margin-top: 0;
}

/* Explanatory line under a dialog's title. */
.hk-dialog-info {
    margin-bottom: 0.875rem;
    font-size: 0.8125rem;
    color: var(--bs-secondary-color);
}

/* Below the narrowest field width there is no room for a label column. */
@media (max-width: 575.98px) {
    .hk-form-row { grid-template-columns: 1fr; }
    .hk-validation { grid-column: 1; }
}

/* ── 12. Small shared helpers ───────────────────────────────────────────────*/

/* Status badges (HkStatusBadge). The tone comes from the status code (OrderStatusTone, OutputFlowStatusTone),
   never from the translated text, and each tone is a Bootstrap subtle background with its emphasis text, so
   the pairs follow the tenant palette and keep their contrast. Text is always present: colour never carries
   the meaning alone.

   A pill with the word and nothing else: 12px at medium weight is the smallest the status word stays readable
   at across a grid. Two layers give it presence without noise — a tint about twice as deep as Bootstrap's
   bg-subtle (which read pale beside the section bands) and a hairline in the same hue, so the pill has an edge
   on a white cell. The word is the emphasis text colour, so it clears AA on the tint. Every step is a mix of
   the tone's own token, so a tenant's palette carries through. */
.hk-status-badge {
    display: inline-block;
    padding: 0.2em 0.65em;
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 0;
    line-height: 1.4;
    white-space: nowrap;
    border: 1px solid transparent;
    border-radius: 999px;
}

.hk-status-neutral {
    background: var(--bs-secondary-bg);
    color: var(--bs-body-color);
    border-color: var(--hk-n-300);
}

.hk-status-info {
    background: color-mix(in srgb, var(--bs-info) 18%, var(--hk-n-0));
    color: var(--bs-info-text-emphasis);
    border-color: color-mix(in srgb, var(--bs-info) 40%, var(--hk-n-0));
}

.hk-status-primary {
    background: color-mix(in srgb, var(--bs-primary) 18%, var(--hk-n-0));
    color: var(--bs-primary-text-emphasis);
    border-color: color-mix(in srgb, var(--bs-primary) 40%, var(--hk-n-0));
}

.hk-status-success {
    background: color-mix(in srgb, var(--bs-success) 20%, var(--hk-n-0));
    color: var(--bs-success-text-emphasis);
    border-color: color-mix(in srgb, var(--bs-success) 42%, var(--hk-n-0));
}

.hk-status-warning {
    background: color-mix(in srgb, var(--bs-warning) 22%, var(--hk-n-0));
    color: var(--bs-warning-text-emphasis);
    border-color: color-mix(in srgb, var(--bs-warning) 45%, var(--hk-n-0));
}

.hk-status-danger {
    background: color-mix(in srgb, var(--bs-danger) 18%, var(--hk-n-0));
    color: var(--bs-danger-text-emphasis);
    border-color: color-mix(in srgb, var(--bs-danger) 40%, var(--hk-n-0));
}

/* The order editor shell — referenced by the editor markup but never defined,
   so the page had no rhythm of its own. */
.hk-order-editor {
    display: flex;
    flex-direction: column;
}

/* The record pages set their title inline in the shell (below the rail), where
   .hk-page-title's zero margin left it touching the first section card. */
.hk-order-editor > .hk-page-title {
    margin: 0.125rem 0 1rem;
}

/* Report cells may contain newlines. WebForms converted them to <br/> and
   rendered the cell unencoded; preserving them in CSS keeps the layout and
   removes the markup-injection path. */
.hk-report-text {
    white-space: pre-wrap;
    word-break: break-word;
}

/* Utilities that exist because the same inline style was repeated across many
   components and Bootstrap has no equivalent. Anything Bootstrap does cover
   (spacing, display, flex, text alignment) uses its utilities instead. */

.hk-clickable {
    cursor: pointer;
}

/* Section heading on a settings screen — an underlined 1rem run-in title.
   Two scoped sheets carried private copies of this rule (SMTP, system settings)
   and three screens rebuilt it from utilities (logs, sorting, planning); one
   definition ends the drift. */
.hk-section-title {
    margin: 1.25rem 0 1rem;
    padding-bottom: 0.375rem;
    /* The same micro-caps as HkSection's nested group title (design.md §10: uppercase section headers, done in
       CSS), so a settings tab and an order section read as one product. */
    font-size: 0.75rem;
    font-weight: 650;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
    border-bottom: 1px solid var(--bs-border-color);
}

.hk-section-title:first-child {
    margin-top: 0;
}

/* The explanatory line under a section title. Pulled up against the title's
   own margin so the pair reads as one unit before the content starts. */
.hk-section-sub {
    margin: -0.625rem 0 1rem;
    font-size: 0.8125rem;
    color: var(--bs-secondary-color);
}

/* The micro-caps label used above grouped controls and beside status readouts. */
.hk-label-micro {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
}

/* ── 13. Navigation progress ────────────────────────────────────────────────
   Enhanced navigation keeps the outgoing page on screen while the next one is
   fetched, so without this a slow page is indistinguishable from a frozen one.
   The class is toggled by hk-nav-progress.js off Blazor's enhanced-navigation
   events; the bar is deliberately indeterminate — the fetch reports no
   progress, and a fake percentage would be a lie about how far along it is. */

.hk-navigating::before {
    content: "";
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: 2000;
    height: 3px;
    background: linear-gradient(90deg, transparent, var(--bs-primary, #3e606f), transparent);
    background-size: 40% 100%;
    background-repeat: no-repeat;
    animation: hk-nav-progress 1.1s ease-in-out infinite;
}

@keyframes hk-nav-progress {
    from { background-position: -40% 0; }
    to   { background-position: 140% 0; }
}

/* Reduced motion still needs the "something is happening" signal, just not the travel. */
@media (prefers-reduced-motion: reduce) {
    .hk-navigating::before {
        background: var(--bs-primary, #3e606f);
        animation: none;
        opacity: 0.6;
    }
}

/* ── 14. Account forms ──────────────────────────────────────────────────────
   Login, one-time code, registration and recovery are static SSR with a thin
   fetch layer (hk-login.js). Bootstrap already covers the alerts, the busy
   spinner and the validation states; this adds only the code field. */

/* One input for the whole code — autofill and password managers understand
   autocomplete="one-time-code", which split boxes defeat. The spacing makes
   the digits read as a code rather than a number. */
.hk-otp-input {
    font-size: 1.5rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.4em;
    text-align: center;
}

.hk-otp-input::placeholder {
    font-size: 1rem;
    letter-spacing: normal;
}

/* ── Storefront information page ────────────────────────────────────────────
   The page body is tenant HTML built by InformationPageService and rendered raw,
   so no scoped stylesheet reaches it; these are the WebForms infopage-* rules. */
.hk-info-page .hk-info-row {
    row-gap: 1rem;
}

.hk-info-page img,
.hk-catalog-information img {
    max-width: 100%;
    height: auto;
}

.hk-info-page .infopage-image-container {
    position: relative;
}

.hk-info-page .infopage-image-container a {
    text-decoration: none;
}

.hk-info-page .infopage-image {
    display: block;
    width: 100%;
    height: auto;
}

.hk-info-page .infopage-image-text-container {
    position: absolute;
    right: 10px;
    bottom: 10px;
}

.hk-info-page .infopage-image-label,
.hk-info-page .infopage-image-text {
    margin: 0;
    padding: 0 20px;
    background-color: #000;
    color: #fff;
}

.hk-info-page .infopage-image-label {
    font-size: .8rem;
}

.hk-info-page .infopage-image-label-below,
.hk-info-page .infopage-image-text-below,
.hk-info-page .infopage-image-price-below {
    margin: .25rem 0 0;
    text-align: center;
    line-height: 1.125rem;
    color: var(--bs-body-color);
    text-decoration: none;
}

.hk-info-page .infopage-image-text-below {
    font-size: .75rem;
}

/* Shared by the modal, information dialog and media picker. */
.hk-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(20, 28, 34, 0.45);
    backdrop-filter: blur(2px);
    z-index: 1050;
}

/* Shared administration shell; original module classes remain as extension hooks. */
.hk-module-shell {
    display: flex;
    min-height: 100vh;
    background: var(--hk-app-bg);
    /* One source for the topbar's height: the bar sets itself from it and the sticky rail parks under it.
       These used to be two numbers in two places — the bar's own min-height and a hand-copied offset — and
       nothing made them agree, which is how a sliver of the page came to show through between them. */
    --hk-topbar-h: 3.5rem;
    --hk-sticky-top: calc(var(--hk-topbar-h) + 1px);
    /* One source for the sidebar column: the sidebar sizes itself from it and the content
       claims it back when the sidebar is collapsed away. */
    --hk-module-sidebar-w: 16rem;
    /* Cap on the content column. The form grid divides whatever width it gets into equal
       columns, so without a cap the fields grow absurdly wide on a large screen. */
    --hk-module-content-max: 1800px;
}

/* The dark rail. Below lg it is a Bootstrap offcanvas, which paints its own --bs-offcanvas-bg, so that is
   re-pointed here too. The Sidebar component reads the --hk-nav-* pair set below and falls back to the
   light values elsewhere (the application and shop layouts keep their white menus). */
.hk-module-sidebar {
    --bs-offcanvas-width: var(--hk-module-sidebar-w);
    --bs-offcanvas-bg: var(--hk-sidebar-bg);
    --bs-offcanvas-color: var(--hk-sidebar-text);
    --hk-nav-text: var(--hk-sidebar-text);
    --hk-nav-text-strong: var(--hk-sidebar-text-strong);
    --hk-nav-muted: var(--hk-sidebar-muted);
    --hk-nav-hover: var(--hk-sidebar-hover);
    --hk-nav-active: var(--hk-sidebar-active);
    --hk-nav-accent: var(--hk-sidebar-accent);
    --hk-nav-border: var(--hk-sidebar-border);
    width: var(--hk-module-sidebar-w);
    flex: 0 0 var(--hk-module-sidebar-w);
    background: linear-gradient(180deg, var(--hk-sidebar-bg-2) 0%, var(--hk-sidebar-bg) 100%);
    color: var(--hk-sidebar-text);
    display: flex;
    flex-direction: column;
    border-right: 1px solid var(--hk-sidebar-border);
}

/* Focus on the dark rail: the primary-tinted ring is invisible against the rail, so it turns white. */
.hk-module-sidebar :focus-visible {
    box-shadow: 0 0 0 0.15rem color-mix(in srgb, var(--hk-n-0) 55%, transparent);
}

@media (min-width: 992px) {
    .hk-module-sidebar {
        position: sticky;
        top: 0;
        height: 100vh;
        /* The collapse slides the whole sidebar off to the left; its width never changes, so the
           content inside does not reflow while animating. */
        transition: margin-left 0.28s cubic-bezier(0.4, 0, 0.2, 1),
                    opacity 0.2s ease;
    }

    /* Collapsed by the desktop hamburger (hk-sidebar.js toggles the class and remembers it). */
    .hk-module-collapsed .hk-module-sidebar {
        margin-left: calc(-1 * var(--hk-module-sidebar-w));
        opacity: 0;
        pointer-events: none;
    }

    /* Restoring a remembered state must not replay the animation. Two classes outrank the base rule, so no
       !important is needed; the global reduced-motion block in §2 already covers users who prefer no motion. */
    .hk-module-no-anim .hk-module-sidebar,
    .hk-module-no-anim .hk-module-burger span,
    .hk-module-no-anim .hk-module-content {
        transition: none;
    }
}

.hk-module-brand {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Same height as .hk-module-topbar so the two bottom borders meet as one line. */
    height: 3.5rem;
    flex-shrink: 0;
    padding: 0 1.25rem;
    border-bottom: 1px solid var(--hk-sidebar-border);
}

/* A tenant's logo is drawn for a white page — usually dark marks on a transparent ground — so on the rail it
   sits on a small white plate rather than being inverted, which would ruin a coloured mark. */
.hk-module-logo {
    max-height: 2.25rem;
    max-width: 12rem;
    padding: 0.25rem 0.5rem;
    background: var(--hk-n-0);
    border-radius: var(--bs-border-radius);
    object-fit: contain;
}

/* The mobile offcanvas close button, drawn white for the dark rail (Bootstrap's own .btn-close-white filter). */
.hk-module-brand .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
}

.hk-module-tenant {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    margin: 0.75rem;
    padding: 0.625rem 0.75rem;
    background: var(--hk-sidebar-hover);
    border: 1px solid var(--hk-sidebar-border);
    border-radius: var(--bs-border-radius-lg);
}

.hk-module-tenant-mark {
    width: 2rem;
    height: 2rem;
    border-radius: var(--bs-border-radius);
    background: var(--hk-sidebar-accent);
    color: var(--hk-sidebar-on-accent);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.hk-module-tenant-info {
    min-width: 0;
    line-height: 1.25;
}

.hk-module-name {
    display: block;
    color: var(--hk-sidebar-text-strong);
    font-size: 0.8125rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.hk-module-tenant-sub {
    display: block;
    color: var(--hk-sidebar-muted);
    font-size: 0.75rem; /* 12px floor for informative text */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The menu scrolls on its own between the tenant card and the foot. Its gutter is reserved whether or not
   the menu overflows, so opening one more group never narrows every row by a scrollbar's width — that
   jump was the rows re-laying-out around a scrollbar that had just appeared. The right padding is smaller
   than the left because the gutter already keeps the rows off the edge. */
.hk-module-nav-scroll {
    flex: 1;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
    padding: 0.75rem 0.25rem 0.75rem 0.5rem;
    /* Rows scrolling out fade through the top and bottom padding instead of being cut off flat. The mask
       only carries alpha, so the colour is any opaque one. */
    mask-image: linear-gradient(to bottom, transparent, black 0.75rem, black calc(100% - 0.75rem), transparent);
}

/* The browser's light default scrollbar is the one thing on the dark rail that is not ours. Chromium and
   Safari expose the parts, so the thumb is drawn as a 6px rounded bar; Firefox has no parts and gets the
   standard thin scrollbar in the same colours instead. The standard properties are set only where the parts
   do not exist, because a Chromium that sees them switches its own parts off. */
.hk-module-nav-scroll::-webkit-scrollbar {
    width: 0.625rem;
}

.hk-module-nav-scroll::-webkit-scrollbar-track {
    background: transparent;
}

.hk-module-nav-scroll::-webkit-scrollbar-thumb {
    background: var(--hk-sidebar-scrollbar);
    border-radius: 1rem;
    /* The thumb is inset from the 10px lane by transparent borders, leaving a 6px bar. */
    border: 2px solid transparent;
    background-clip: padding-box;
}

.hk-module-nav-scroll:hover::-webkit-scrollbar-thumb {
    background-color: var(--hk-sidebar-scrollbar-hover);
}

@supports not selector(::-webkit-scrollbar) {
    .hk-module-nav-scroll {
        scrollbar-width: thin;
        scrollbar-color: var(--hk-sidebar-scrollbar) transparent;
    }

    .hk-module-nav-scroll:hover {
        scrollbar-color: var(--hk-sidebar-scrollbar-hover) transparent;
    }
}

.hk-module-foot {
    padding: 0.75rem;
    border-top: 1px solid var(--hk-sidebar-border);
}

.hk-module-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.hk-module-topbar {
    display: flex;
    align-items: center;
    background: var(--bs-body-bg);
    border-bottom: 1px solid var(--bs-border-color);
    padding: 0.75rem 1.5rem;
    /* A fixed height, not a floor: the sticky rail parks at exactly this plus the border, so a bar that
       grew with its content would slide under the rail and a bar that did not fill it would leave a gap. */
    height: var(--hk-topbar-h);
    position: sticky;
    top: 0;
    z-index: 10;
}

.hk-module-toggle {
    color: var(--bs-body-color);
    font-size: 1.25rem;
}

/* Desktop sidebar toggle: a hamburger that morphs into a "show menu" arrow when collapsed. */
.hk-module-collapse {
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    margin: -0.25rem 0.75rem -0.25rem -0.5rem;
    padding: 0;
    border: 0;
    border-radius: var(--bs-border-radius);
    background: transparent;
    color: var(--bs-body-color);
    cursor: pointer;
    transition: background-color 0.15s ease, transform 0.15s ease;
}

.hk-module-collapse:hover {
    background: var(--bs-tertiary-bg);
}

.hk-module-collapse:active {
    transform: scale(0.92);
}

.hk-module-burger {
    position: relative;
    display: block;
    width: 1.125rem;
    height: 0.875rem;
}

.hk-module-burger span {
    position: absolute;
    right: 0;
    width: 100%;
    height: 2px;
    border-radius: 1px;
    background: currentColor;
    transform-origin: right center;
    transition: top 0.25s cubic-bezier(0.4, 0, 0.2, 1),
                width 0.25s cubic-bezier(0.4, 0, 0.2, 1),
                transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

.hk-module-burger span:nth-child(1) {
    top: 0;
}

.hk-module-burger span:nth-child(2) {
    top: calc(50% - 1px);
}

.hk-module-burger span:nth-child(3) {
    top: calc(100% - 2px);
}

/* Collapsed: the outer bars fold onto the middle one as an arrow head pointing at the hidden menu's return. */
.hk-module-collapsed .hk-module-burger span:nth-child(1),
.hk-module-collapsed .hk-module-burger span:nth-child(3) {
    top: calc(50% - 1px);
    width: 55%;
}

.hk-module-collapsed .hk-module-burger span:nth-child(1) {
    transform: rotate(45deg);
}

.hk-module-collapsed .hk-module-burger span:nth-child(3) {
    transform: rotate(-45deg);
}

.hk-module-crumb {
    font-size: 0.8125rem;
}

.hk-module-crumb .breadcrumb-item.active {
    color: var(--bs-body-color);
    font-weight: 600;
}

.hk-module-content {
    padding: 1.5rem;
    max-width: var(--hk-module-content-max);
}

@media (min-width: 992px) {
    /* Collapsing the sidebar hands its column to the content. Without this the capped block only
       shifts left and the freed width is left empty on the right. */
    .hk-module-content {
        /* Same duration and easing as the sidebar slide, so the content edge travels with the
           sidebar instead of snapping to its new cap a frame early. */
        transition: max-width 0.28s cubic-bezier(0.4, 0, 0.2, 1);
    }

    .hk-module-collapsed .hk-module-content {
        max-width: calc(var(--hk-module-content-max) + var(--hk-module-sidebar-w));
    }
}

/* Shared document surface for inline and modal PDF previews. */

.hk-document-frame {
    display: block;
    width: 100%;
    height: 70vh;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    background: var(--bs-body-bg);
}

/* Print-part fields are shared by the job card and both print editors. */
.hk-print-part-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: 0.625rem;
    margin-bottom: 0.625rem;
}

.hk-print-part-card {
    padding: 0.75rem;
    margin-bottom: 0.75rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-sm);
}

.hk-print-part-properties {
    margin: 0.625rem 0;
    padding-top: 0.625rem;
    border-top: 1px solid var(--bs-border-color);
}

.hk-print-part-wide {
    grid-column: 1 / -1;
}

.hk-print-part-dimensions {
    display: flex;
    gap: 0.375rem;
}

/* Compact previews in cart and customer-order tables. */
.hk-table-thumbnail {
    max-width: 4rem;
    max-height: 4rem;
    width: auto;
    height: auto;
}

/* Intrinsically sized field rows shared by price and imposition editors. */
.hk-field-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.625rem;
    margin-bottom: 0.5rem;
}

/* ── 15. Dialog body ────────────────────────────────────────────────
   The blocks a dialog stacks — an alert, a form, a note, a table — space themselves, the same way a
   section body spaces its own (§7). Without it every dialog had to space them by hand, and the ones that
   forgot had two blocks touching. Sibling margins collapse against it, so a block carrying its own
   margin does not double up, and .form-check rows are exempt for the same reason as in a section: a
   checkbox group is one unit, not separate blocks. A nested dialog is not a block of this one.

   The rule is deliberately weak — the :where() contributes nothing, so the whole selector weighs one
   class. Anything that needs a different boundary can say so with a plain two-class rule and win, which
   is how §11's seam between two field containers keeps the row pitch instead of this block gap. */
.hk-modal-body > * + :where(*:not(.form-check, .hk-modal, .hk-modal-backdrop)) {
    margin-top: 0.75rem;
}

/* A sub-section (HkSection Plain) is a block like any other. Its own sheet zeroes its margin, and that
   scoped rule (a class plus the scope attribute) outweighs the one above, so sub-sections placed straight
   in a dialog body touched each other and whatever came before them — the supplier dialog's two groups,
   the reporting dialog's Reports. Three classes, so it wins whichever sheet loads last. */
.hk-modal-body > * + .hk-section.hk-section-plain {
    margin-top: 0.75rem;
}
