/* ============================================================
   NexFreightERP Design System — Single Entry Point
   File: wwwroot/css/app.css
   Navigation styles from nav.css are integrated below.

   Architecture:
   01. Design tokens — light
   02. Design tokens — dark
   03. Typography
   04. Base
   05. Application shell
   06. Shared components
   07. Master list pages
   08. Home / dashboard
   09. Authentication
   10. Reports
   11. Quotation workspace
   12. Accessibility & responsive
   13. Blazor / framework

   This file is intentionally the only CSS file referenced by the app.
   ============================================================ */

/* ============================================================
   NexFreightERP design system
   Brand: ocean-teal. Overrides Fluent UI design tokens so every
   Fluent component picks up the brand ramp automatically.
   ============================================================ */

/* ============================================================
   01. DESIGN TOKENS — LIGHT
   ============================================================ */
/* ---------- Brand token overrides (light mode) ---------- */
body {
    --colorBrandBackground: #0E6B5C !important;
    --colorBrandBackgroundHover: #0A4F44 !important;
    --colorBrandBackgroundPressed: #094D42 !important;
    --colorBrandBackgroundSelected: #0A4F44 !important;
    --colorBrandBackground2: #B2E8DF !important;
    --colorBrandBackground2Hover: #A2E1D7 !important;
    --colorBrandBackground2Pressed: #8DDACE !important;
    --colorBrandBackgroundTint: #E2F2EE !important;
    --colorBrandForeground1: #0A4F44 !important;
    --colorBrandForeground2: #0D695A !important;
    --colorBrandForeground2Hover: #0A4F44 !important;
    --colorBrandForeground2Pressed: #094D42 !important;
    --colorBrandForegroundLink: #0A4F44 !important;
    --colorBrandForegroundLinkHover: #0D695A !important;
    --colorBrandForegroundLinkPressed: #094D42 !important;
    --colorBrandForegroundLinkSelected: #0A4F44 !important;
    --colorBrandStroke1: #0E6B5C !important;
    --colorBrandStroke1Hover: #0A4F44 !important;
    --colorBrandStroke1Pressed: #094D42 !important;
    --colorBrandStroke1Selected: #0A4F44 !important;
    --colorBrandStroke2: #BFE0D7 !important;
    --colorBrandStroke2Hover: #A2E1D7 !important;
    --colorBrandStroke2Pressed: #8DDACE !important;
    --colorBrandStrokeFocus1: #0E6B5C !important;
    --colorBrandStrokeFocus2: #FFFFFF !important;
    --colorCompoundBrandBackground: #0E6B5C !important;
    --colorCompoundBrandBackgroundHover: #0C5A4E !important;
    --colorCompoundBrandBackgroundPressed: #094D42 !important;
    --colorCompoundBrandForeground1: #0B594D !important;
    --colorCompoundBrandForeground1Hover: #0D695A !important;
    --colorCompoundBrandForeground1Pressed: #094D42 !important;
    --colorCompoundBrandStroke: #0E6B5C !important;
    --colorCompoundBrandStrokeHover: #0C5A4E !important;
    --colorCompoundBrandStrokePressed: #094D42 !important;
    --colorNeutralForegroundOnBrand: #FFFFFF !important;
    --colorBrandForegroundInverted: #0E6B5C !important;
    --colorBrandForegroundInvertedHover: #0C5A4E !important;
    --colorBrandForegroundInvertedPressed: #094D42 !important;
    --colorBrandForegroundInvertedSelected: #0C5A4E !important;
    --colorBrandForegroundInvertedLink: #0E6B5C !important;
    --colorBrandForegroundInvertedLinkHover: #0C5A4E !important;
    --colorBrandForegroundInvertedLinkPressed: #094D42 !important;
    --colorBrandForegroundInvertedLinkSelected: #0C5A4E !important;
    --colorBrandForegroundOnLight: #0E6B5C !important;
    --colorBrandForegroundOnLightHover: #0C5A4E !important;
    --colorBrandForegroundOnLightPressed: #094D42 !important;
    --colorBrandForegroundOnLightSelected: #0C5A4E !important;
    --colorBrandShadowAmbient: rgba(14, 107, 92, 0.24) !important;
    --colorBrandShadowKey: rgba(14, 107, 92, 0.36) !important;
    --colorBrandShadowAmbientHover: rgba(14, 107, 92, 0.28) !important;
    --colorBrandShadowKeyHover: rgba(14, 107, 92, 0.42) !important;
    --colorBrandShadowAmbientPressed: rgba(14, 107, 92, 0.24) !important;
    --colorBrandShadowKeyPressed: rgba(14, 107, 92, 0.36) !important;

    /* Neutral palette + radius/shadow scale, remapped to Azure Portal's
       neutral grey ramp (the header keeps the brand gradient separately —
       see .app-header, which re-points these tokens to white for its own
       scope and is unaffected by the change here). */
    --colorNeutralBackground1: #FFFFFF !important;
    --colorNeutralBackground2: #F3F2F1 !important;
    --colorNeutralBackground3: #EDEBE9 !important;
    --colorNeutralForeground1: #201F1E !important;
    --colorNeutralForeground2: #323130 !important;
    --colorNeutralForeground3: #605E5C !important;
    --colorNeutralStroke1: #C8C6C4 !important;
    --colorNeutralStroke2: #E1DFDD !important;
    /* Left as Fluent's own computed default (a mid-grey tuned for its
       original darker neutral ramp) this read as a near-invisible hairline
       under text inputs against the lighter Azure background — it draws
       the bottom border every fluent-text-input/-dropdown uses at rest. */
    --colorNeutralStrokeAccessible: #605E5C !important;

    --borderRadiusSmall: 2px !important;
    --borderRadiusMedium: 2px !important;
    --borderRadiusLarge: 4px !important;
    --borderRadiusXLarge: 6px !important;

    --shadow2: 0 1.6px 3.6px rgba(0, 0, 0, .08) !important;
    --shadow4: 0 1.6px 3.6px rgba(0, 0, 0, .08) !important;
    --shadow8: 0 6.4px 14.4px rgba(0, 0, 0, .13) !important;
    --shadow16: 0 6.4px 14.4px rgba(0, 0, 0, .13) !important;

    --qf-paper: #FAF9F8;
    --qf-surface: #FFFFFF;

    /* Semantic report tokens */
    --report-background: #FFFFFF;
    --report-foreground: #201F1E;
    --report-border: #EDEBE9;
    --report-header-background: #F3F2F1;
    --report-muted-foreground: #605E5C;

    /* Status palette (amber warning / green success), matching Azure
       Portal's status hues instead of Fluent's default palette. */
    --colorPaletteYellowBackground1: #FFF4CE !important;
    --colorPaletteYellowBorder1: #F1D28F !important;
    --colorPaletteYellowForeground1: #8A5D00 !important;
    --colorPaletteGreenBackground1: #DFF6DD !important;
    --colorPaletteGreenForeground1: #107C10 !important;

    /* Disabled controls (e.g. Save/Cancel before anything changed) were
       left on Fluent's own computed disabled tokens, which read as
       correct-but-very-faint against the lighter Azure surfaces. Pin them
       to Azure Portal's own disabled contrast so disabled text/borders
       stay legible instead of nearly invisible. */
    --colorNeutralForegroundDisabled: #A19F9D !important;
    --colorNeutralStrokeDisabled: #C8C6C4 !important;
    --colorNeutralBackgroundDisabled: #F3F2F1 !important;
}

/* ============================================================
   02. DESIGN TOKENS — DARK
   ============================================================ */
/* ---------- Brand token overrides (dark mode) ---------- */
body[data-theme="dark"] {
    --colorBrandBackground: #3FBF9E !important;
    --colorBrandBackgroundHover: #6FD4B8 !important;
    --colorBrandBackgroundPressed: #399D84 !important;
    --colorBrandBackgroundSelected: #6FD4B8 !important;
    --colorBrandBackground2: #1D3731 !important;
    --colorBrandBackground2Hover: #23423B !important;
    --colorBrandBackground2Pressed: #172E29 !important;
    --colorBrandBackgroundTint: #16302A !important;
    --colorBrandForeground1: #3FBF9E !important;
    --colorBrandForeground2: #6FD4B8 !important;
    --colorBrandForeground2Hover: #3FBF9E !important;
    --colorBrandForeground2Pressed: #399D84 !important;
    --colorBrandForegroundLink: #3FBF9E !important;
    --colorBrandForegroundLinkHover: #6FD4B8 !important;
    --colorBrandForegroundLinkPressed: #399D84 !important;
    --colorBrandForegroundLinkSelected: #3FBF9E !important;
    --colorBrandStroke1: #3FBF9E !important;
    --colorBrandStroke1Hover: #6FD4B8 !important;
    --colorBrandStroke1Pressed: #399D84 !important;
    --colorBrandStroke1Selected: #6FD4B8 !important;
    --colorBrandStroke2: #1D3731 !important;
    --colorBrandStroke2Hover: #23423B !important;
    --colorBrandStroke2Pressed: #172E29 !important;
    --colorBrandStrokeFocus1: #3FBF9E !important;
    --colorBrandStrokeFocus2: #121212 !important;
    --colorCompoundBrandBackground: #3FBF9E !important;
    --colorCompoundBrandBackgroundHover: #6FD4B8 !important;
    --colorCompoundBrandBackgroundPressed: #399D84 !important;
    --colorCompoundBrandForeground1: #3FBF9E !important;
    --colorCompoundBrandForeground1Hover: #6FD4B8 !important;
    --colorCompoundBrandForeground1Pressed: #399D84 !important;
    --colorCompoundBrandStroke: #3FBF9E !important;
    --colorCompoundBrandStrokeHover: #6FD4B8 !important;
    --colorCompoundBrandStrokePressed: #399D84 !important;
    --colorNeutralForegroundOnBrand: #040D0B !important;
    --colorBrandForegroundInverted: #3FBF9E !important;
    --colorBrandForegroundInvertedHover: #56C6AA !important;
    --colorBrandForegroundInvertedPressed: #399D84 !important;
    --colorBrandForegroundInvertedSelected: #56C6AA !important;
    --colorBrandForegroundInvertedLink: #3FBF9E !important;
    --colorBrandForegroundInvertedLinkHover: #56C6AA !important;
    --colorBrandForegroundInvertedLinkPressed: #399D84 !important;
    --colorBrandForegroundInvertedLinkSelected: #56C6AA !important;
    --colorBrandForegroundOnLight: #3FBF9E !important;
    --colorBrandForegroundOnLightHover: #56C6AA !important;
    --colorBrandForegroundOnLightPressed: #399D84 !important;
    --colorBrandForegroundOnLightSelected: #56C6AA !important;
    --colorBrandShadowAmbient: rgba(63, 191, 158, 0.24) !important;
    --colorBrandShadowKey: rgba(63, 191, 158, 0.36) !important;
    --colorBrandShadowAmbientHover: rgba(63, 191, 158, 0.28) !important;
    --colorBrandShadowKeyHover: rgba(63, 191, 158, 0.42) !important;
    --colorBrandShadowAmbientPressed: rgba(63, 191, 158, 0.24) !important;
    --colorBrandShadowKeyPressed: rgba(63, 191, 158, 0.36) !important;

    --colorNeutralBackground1: #1F1F1F !important;
    --colorNeutralBackground2: #252423 !important;
    --colorNeutralBackground3: #292827 !important;
    --colorNeutralForeground1: #F3F2F1 !important;
    --colorNeutralForeground2: #E1DFDD !important;
    --colorNeutralForeground3: #A19F9D !important;
    --colorNeutralStroke1: #3B3A39 !important;
    --colorNeutralStroke2: #323130 !important;
    --colorNeutralStrokeAccessible: #A19F9D !important;
    --shadow2: 0 1.6px 3.6px rgba(0, 0, 0, .3) !important;
    --shadow4: 0 1.6px 3.6px rgba(0, 0, 0, .3) !important;
    --shadow8: 0 6.4px 14.4px rgba(0, 0, 0, .45) !important;
    --shadow16: 0 6.4px 14.4px rgba(0, 0, 0, .45) !important;
    --qf-paper: #181717;
    --qf-surface: #1F1F1F;

    /* Semantic report tokens */
    --report-background: #1F1F1F;
    --report-foreground: #F3F2F1;
    --report-border: #3B3A39;
    --report-header-background: #252423;
    --report-muted-foreground: #A19F9D;

    --colorPaletteYellowBackground1: #433519 !important;
    --colorPaletteYellowBorder1: #684D1A !important;
    --colorPaletteYellowForeground1: #FCE100 !important;
    --colorPaletteGreenBackground1: #0E3B0E !important;
    --colorPaletteGreenForeground1: #6BB700 !important;

    /* Same reasoning as the light-mode block above: pin disabled contrast
       to Azure Portal's dark disabled colors instead of Fluent's computed
       default, which read as too faint against these surfaces. */
    --colorNeutralForegroundDisabled: #605E5C !important;
    --colorNeutralStrokeDisabled: #3B3A39 !important;
    --colorNeutralBackgroundDisabled: #292827 !important;
}

/* No prefers-color-scheme token fallback here, deliberately.
   The library signals dark with body[data-theme="dark"] and signals light
   by REMOVING the attribute entirely — so light mode and "not initialised
   yet" are indistinguishable. A `body:not([data-theme="light"])` guard
   inside a prefers-color-scheme block therefore also matches a user who
   is on a dark OS but has switched this app to light, handing them the
   dark ramp. Tokens live only in the two blocks above; the rule below is
   background-only, which is safe to apply before the theme resolves and
   prevents the white flash the docs describe. */
/* Removed: this painted the body dark from an OS preference alone while every
   foreground token stayed on the light ramp (the dark tokens apply only under
   body[data-theme="dark"]). That left near-black text on a near-black body on
   every page. The anti-flash background must not be applied unless the matching
   foreground ramp is applied with it, so it is dropped rather than guarded. */

/* ============================================================
   03. TYPOGRAPHY
   ============================================================ */
/* ---------- Typography ---------- */

body {
    --fontFamilyBase: "Public Sans", "Segoe UI", system-ui, sans-serif !important;
    --fontFamilyMonospace: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace !important;
    --fontFamilyNumeric: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace !important;
    font-size: 13.5px;
    /*line-height: 1.45;*/
}

h1, h2, h3, h4, h5, h6,
fluent-text[weight="bold"],
fluent-text[weight="semibold"],
.fluent-card-header,
fluent-tab::part(tab) {
    font-family: "Archivo", "Segoe UI", system-ui, sans-serif;
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* Numeric/monetary/code values: mono, right-aligned, tabular figures. */
.qf-money,
input[type="number"] {
    font-variant-numeric: tabular-nums;
}

input[type="number"] {
    text-align: right;
}

/* ============================================================
   04. BASE
   ============================================================ */
/* ---------- Base ---------- */

html,
body {
    height: 100%;
}

/* FluentUI injects a reboot stylesheet at runtime that paints body with the
   surface colour; !important keeps the page one shade back from the cards.
   Flat #FBFBFB app-wide -- a prior radial-gradient tint here was too
   subtle to read as an actual change, so this is a plain solid colour
   instead. */
body {
    background: var(--qf-paper, #FBFBFB) !important;
    color: var(--colorNeutralForeground1);
}

.app-brand-gradient {
    background: linear-gradient(135deg, #0E6B5C 0%, #1D6FB8 55%, #3F5CC0 100%);
}

/* ============================================================
   NAVIGATION
   Existing navigation selectors intentionally preserved.
   ============================================================ */

 

@media (max-width: 768px) {
    .fluent-navitem,
    .fluent-navcategoryitem {
        margin: 2px 4px;
    }

    .fluent-navitem.active {
        padding-left: 10px;
    }

    .fluent-navcategoryitem span {
        font-size: var(--fontSizeBase200);
    }
}

@media (max-width: 480px) {
    .fluent-navitem,
    .fluent-navcategoryitem {
        margin: 1px 2px;
    }

    .fluent-navsubitemgroup {
        padding-left: 4px;
        margin-left: 4px;
        border-left-width: 1px;
    }
}

/* ============================================================
   05. APPLICATION SHELL
   ============================================================ */
/* ---------- App shell ---------- */

/* The header sits on a brand gradient, so the foreground tokens are
   re-pointed to white for everything inside it. No !important needed: the
   library sets these tokens on <html> via its adopted stylesheet, and custom
   properties inherit, so setting them on this ancestor already wins by
   proximity for every descendant. */
/* Fluent UI Layout handles header styling */

/* Header foreground scope */
/* The header sits on the brand gradient, so foreground tokens are re-pointed
   to white for everything inside it. Custom properties inherit and the
   library sets them on <html>, so setting them on this ancestor already wins
   by proximity -- no !important needed.

   The hover/pressed tokens below are the v5 names. An earlier revision used
   --neutral-fill-hover / --neutral-foreground-rest and friends, which are
   FluentUI *v4* token names and do not exist in v5 (verified: zero hits in
   the v5 bundle), so button hover silently did nothing. */
.app-header {
    /* !important is required here: the global `body { ... !important }`
       token block above (light) and `body[data-theme="dark"] { ... !important }`
       both set --colorNeutralForeground1 / --colorBrandForeground1 etc. with
       !important. CSS resolves !important declarations before specificity,
       so a non-important rule here -- even though .app-header is more
       specific than body -- previously lost to that global !important and
       the header rendered with the app's normal dark text/icon color
       instead of white. Matching !important here restores intent. */
    --colorNeutralForeground1: #FFFFFF !important;
    --colorNeutralForeground2: rgba(255, 255, 255, 0.90) !important;
    --colorNeutralForeground3: rgba(255, 255, 255, 0.78) !important;
    --colorBrandForeground1: #FFFFFF !important;
    --colorBrandForeground2: #FFFFFF !important;
    --colorBrandForegroundLink: #FFFFFF !important;

    /* Transparent/subtle buttons read these on hover and press. Foreground
       stays white: the hover fill is a translucent white veil over a dark
       gradient, so dark text on it would be unreadable. */
    --colorSubtleBackgroundHover: rgba(255, 255, 255, 0.16) !important;
    --colorSubtleBackgroundPressed: rgba(255, 255, 255, 0.24) !important;
    --colorSubtleBackgroundSelected: rgba(255, 255, 255, 0.20) !important;
    --colorNeutralForeground2Hover: #FFFFFF !important;
    --colorNeutralForeground2Pressed: #FFFFFF !important;
    --colorNeutralForeground2BrandHover: #FFFFFF !important;
    --colorNeutralForeground2BrandPressed: #FFFFFF !important;

    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0 12px;
    box-sizing: border-box;
    color: #FFFFFF;
    background: linear-gradient(110deg, #075246 0%, #0B6B61 48%, #15518C 100%);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.24);
}

/* Belt-and-braces: FluentLayoutItem[area="header"] itself paints
   --colorBrandBackground (the plain teal-green, not our gradient) behind
   whatever we put in it. With no Padding="Padding.All0" on that
   FluentLayoutItem, its own default padding left a few px gap top and
   bottom where that green showed through as a thin strip above/below the
   gradient bar -- fixed on the FluentLayoutItem itself in MainLayout.razor,
   this just makes sure nothing can peek through again if that default
   padding ever changes. */
fluent-layout-item[area="header"] {
    background: transparent;
}

.app-header * {
    color: inherit;
}

.app-header fluent-button {
    --colorNeutralForeground1: #FFFFFF !important;
    --colorNeutralForeground2: #FFFFFF !important;
    --colorNeutralForeground2Hover: #FFFFFF !important;
    --colorNeutralForeground2Pressed: #FFFFFF !important;
}

.app-header fluent-layout-hamburger {
    --colorNeutralForeground1: #FFFFFF !important;
    --colorNeutralForeground2: #FFFFFF !important;
    --colorNeutralForeground3: #FFFFFF !important;
    --colorNeutralBackground1: rgba(255, 255, 255, 0.10) !important;
    --colorNeutralBackground1Hover: rgba(255, 255, 255, 0.20) !important;
    --colorNeutralBackground1Pressed: rgba(255, 255, 255, 0.28) !important;
    border-radius: var(--borderRadiusMedium);
}

/* Belt-and-braces hover: the button's fill lives in its shadow root, and the
   token above is the supported route, but this also covers the plain <a> and
   hamburger in the bar so every interactive item in the header reacts alike. */
.app-header fluent-button:hover,
.app-header fluent-layout-hamburger:hover {
    background-color: rgba(255, 255, 255, 0.16);
    border-radius: var(--borderRadiusMedium);
}

    .app-header fluent-button:active,
    .app-header fluent-layout-hamburger:active {
        background-color: rgba(255, 255, 255, 0.24);
    }

/* Zero padding on .app-header (fixed above) meant the hamburger sat flush
   against the left edge and Sign Out flush against the right, with the four
   right-side icons packed edge-to-edge at the FluentStack's 4px gap -- all
   of which reads as "cramped/unstyled" rather than a deliberate toolbar.
   A thin divider before Sign Out separates "view controls" from "account
   action" without needing to touch the gap value in MainLayout.razor. */
.app-header .app-header-divider {
    width: 1px;
    align-self: stretch;
    margin: 8px 4px;
    background: rgba(255, 255, 255, 0.28);
}

/* Fix overlapping datagrid headers: prevent wrapping and show ellipsis.
   These rules used to key off a bare `fluent-data-grid` element selector,
   which never matched anything -- the grid renders as
   <table class="fluent-data-grid" style="display:grid">, not a
   <fluent-data-grid> custom element, so the whole block was dead and long
   header labels painted straight over the next column instead of eliding.

   The label itself lives three levels deep -- <th> > a flex row (column-
   options button + title) > div[col-title] > div[col-title-text] holding
   the text -- and flex/grid items default to min-width:auto, which floors
   every level at its content's min-content size regardless of the parent's
   own width. Each level needs min-width:0 to allow shrinking; only the
   innermost div gets the actual ellipsis treatment. */
.fluent-data-grid thead th {
    overflow: hidden;
}

.fluent-data-grid thead th > div {
    min-width: 0;
}

.fluent-data-grid thead th div[col-title] {
    overflow: hidden;
    min-width: 0;
}

.fluent-data-grid thead th div[col-title-text] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

/* Make header smaller on very narrow viewports */
@media (max-width: 800px) {
    .fluent-data-grid thead th {
        font-size: 0.85rem;
        padding: 6px 8px;
    }
}

 /*HeaderBrand note...
   width:20%/height:12%; percentages on an SVG in a flex row resolve against
   an ambiguous containing block and it rendered 253x74 -- 30px past the 44px
   the header LayoutItem declares. HeaderBrand.razor replaces it in the bar
   with a fixed, legible lockup; Logo.razor still serves the auth pages.
   Colour comes from the foreground token, which .app-header re-points to
   white, so the mark and wordmark track the theme. */



/*  width:20%/height:12%; percentages on an SVG in a flex row resolve against
   an ambiguous containing block and it rendered 253x74 -- 30px past the 44px
   the header LayoutItem declares. HeaderBrand.razor replaces it in the bar
   with a fixed, legible lockup; Logo.razor still serves the auth pages.
   Colour comes from the foreground token, which .app-header re-points to
   white, so the mark and wordmark track the theme. */
.app-brand {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    margin-inline-end: 4px;
    color: var(--colorNeutralForeground1);
}

.app-brand-mark {
    flex-shrink: 0;
}

.app-brand-text {
    font-size: var(--fontSizeBase400);
    font-weight: var(--fontWeightSemibold);
    line-height: var(--lineHeightBase400);
    letter-spacing: .01em;
    white-space: nowrap;
}

.app-header-title {
    font-weight: 600;
    font-size: var(--fontSizeBase400);
    letter-spacing: 0.2px;
    white-space: nowrap;
}

/* No overflow here, and none on the layout item below: the nav does not
   scroll at all. */
/* Nav labels are black rather than the app's dark-green foreground.

   !important is required for the same reason as .app-header above: the
   global `body { ... !important }` token block sets --colorNeutralForeground*
   with !important, and !important is resolved before specificity, so a plain
   declaration here would lose to it despite .app-nav being more specific.

   Hover/pressed foregrounds are pinned to black too, otherwise the library
   flips item text to the brand green on hover. The *Selected* tokens are
   deliberately left alone so the active item keeps its brand indication.

   LIGHT MODE ONLY. These are re-pointed for dark mode further below: black on
   the dark nav panel is 1.27:1, which made every nav label unreadable. The
   values live here rather than being made theme-neutral because the point of
   the block is to defeat the brand-green foreground, and what should replace
   it differs per theme. */
.app-nav {
    --colorNeutralForeground1: #000000 !important;
    --colorNeutralForeground2: #000000 !important;
    --colorNeutralForeground3: rgba(0, 0, 0, 0.72) !important;
    --colorNeutralForeground2Hover: #000000 !important;
    --colorNeutralForeground2Pressed: #000000 !important;
    --colorNeutralForeground2BrandHover: #000000 !important;
    --colorNeutralForeground2BrandPressed: #000000 !important;

    min-height: 100%;
    padding: 6px 0 8px;
    /* Flat ground, like a portal rail: the tint made the nav read as a
       separate panel floating beside the page rather than part of the shell. */
    background: var(--colorNeutralBackground1);
}

/* Dark mode: the block above pins the nav foreground to black, which is right
   on the light panel and unreadable on the dark one -- black on
   --colorNeutralBackground1 measured 1.27:1, so the labels effectively
   disappeared. These re-point the same tokens at the theme's own light
   foreground, keeping the original intent (defeat the brand-green text) while
   restoring contrast.

   Both selectors are needed for the reason set out at the token block near the
   top of this file: `body[data-theme="dark"]` covers an explicit choice, and
   the media query covers an OS preference with no explicit choice, guarded by
   `:not([data-theme="light"])` so it never overrides someone who picked light. */
body[data-theme="dark"] .app-nav {
    --colorNeutralForeground1: #F3F2F1 !important;
    --colorNeutralForeground2: #F3F2F1 !important;
    --colorNeutralForeground3: rgba(243, 242, 241, 0.72) !important;
    --colorNeutralForeground2Hover: #FFFFFF !important;
    --colorNeutralForeground2Pressed: #FFFFFF !important;
    --colorNeutralForeground2BrandHover: #FFFFFF !important;
    --colorNeutralForeground2BrandPressed: #FFFFFF !important;
}

/* Removed: this lightened the nav foreground from an OS preference alone,
   while .app-nav's background stayed on the light ramp (--colorNeutralBackground1
   is only re-pointed under body[data-theme="dark"]). That put near-white labels
   on a white rail. The dark nav foregrounds now come only from the
   body[data-theme="dark"] block above, which is applied together with the dark
   backgrounds. */

/* The nav does not scroll. The library sets overflow-y: auto on this pane, so
   it is overridden here; the slim-scrollbar rules that used to style it are
   gone with it. Anything past the bottom of the window is not reachable by
   scrolling, so the menu has to stay short enough to fit. */
.fluent-layout-item[area="nav"] {
    overflow: hidden !important;
}

/* The page sits one shade back from the cards, so panels visibly float. */
.app-content {
    padding: 26px 30px 60px;
    min-height: 100%;
}

fluent-layout-item[area="content"],
.app-content {
    background: var(--qf-paper, #FBFBFB);
}

.app-footer {
    color: var(--colorNeutralForeground3);
    border-top: 1px solid var(--colorNeutralStroke2);
    background: var(--colorNeutralBackground2);
}

    .app-footer a {
        color: var(--colorBrandForegroundLink);
        text-decoration: none;
    }

        .app-footer a:hover {
            text-decoration: underline;
        }

/* Navigation polish */
.fluent-nav .fluent-navitem {
    border-radius: var(--borderRadiusMedium);
}

/* ============================================================
   06. SHARED COMPONENTS
   ============================================================ */
/* ---------- Popup option lists (autocomplete / select) ----------
   fluent-listbox renders every option with no height limit and no
   scrolling of its own, so a field backed by a long list (e.g. 50 ports)
   painted a listbox taller than the viewport -- 1708px measured against a
   50-item port list -- that visually buried every field below it on the
   page instead of behaving like a dropdown. It also has no width floor of
   its own, so inside a narrow grid column (a 4-up field row) it rendered
   barely wider than the input and drifted left into the neighbouring
   field. Applied globally: every FluentAutocomplete/FluentSelect in the
   app renders through this same element. */
fluent-listbox {
    max-height: 320px;
    overflow-y: auto;
    min-width: 200px;
}

/* ---------- Shared page building blocks ---------- */

.page-container {
    width: 100%;
    max-width: 1240px;
    margin: 0 auto;
}

.page-title {
    font-size: var(--fontSizeBase600);
    font-weight: var(--fontWeightSemibold);
    margin: 0;
}

.page-subtitle {
    color: var(--colorNeutralForeground3);
    margin: 2px 0 0 0;
}

/* Cards */
/* The library styles cards as `.fluent-card:not([appearance])` and
   `.fluent-card[appearance="..."]` with --borderRadiusMedium; a bare
   `.fluent-card` selector loses to that on specificity, so match it. */
.fluent-card.master-grid-card,
.fluent-card:not([appearance]),
.fluent-card[appearance="default"],
.fluent-card[appearance="filled"],
.fluent-card[appearance="outline"],
.fluent-card[appearance="subtle"] {
    border-radius: var(--borderRadiusLarge);
    border: 1px solid var(--colorNeutralStroke2);
    box-shadow: var(--shadow4);
    background: var(--colorNeutralBackground1);
}

    .fluent-card.master-grid-card:hover {
        box-shadow: var(--shadow8);
    }

/* ---------- App loader (branded cargo ship) ----------
   Drop-in for FluentSpinner: same inline footprint, but reads as the app's
   own cargo-ship mark (see HeaderBrand/Authlogo) instead of a generic ring.

   Two motions together, because neither carries on its own at the 16-20px
   most call sites use: the hull rocks on its centre, and two wake lines flow
   beneath it at different dash rhythms and speeds so the water reads as
   moving rather than as one sliding rule. The rock stays shallow so the mark
   remains readable instead of tumbling. prefers-reduced-motion freezes all of
   it via the global rule in section 12. */
.app-loader {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--app-loader-size, 20px);
    height: var(--app-loader-size, 20px);
    flex-shrink: 0;
}

/* The far wake sits just past the viewBox edge, so it must not be clipped. */
.app-loader svg {
    width: 100%;
    height: 100%;
    overflow: visible;
}

/* fill-box keeps the origin at the ship's own centre wherever it sits in the
   viewBox; the heave is a percentage so it scales with the loader. */
.app-loader-ship {
    transform-box: fill-box;
    transform-origin: center;
    animation: app-loader-rock 1.4s ease-in-out infinite;
}

/* Dash offset travels exactly one dash+gap per cycle, so each line loops with
   no visible seam. */
.app-loader-sea {
    stroke-dasharray: 4 5;
    animation: app-loader-sea 0.7s linear infinite;
}

.app-loader-sea-far {
    stroke-dasharray: 3 6;
    animation-name: app-loader-sea-far;
    animation-duration: 1s;
}

@keyframes app-loader-rock {
    0%, 100% {
        transform: translateY(6%) rotate(-2.5deg);
    }

    50% {
        transform: translateY(-6%) rotate(2.5deg);
    }
}

@keyframes app-loader-sea {
    to {
        stroke-dashoffset: -9;
    }
}

@keyframes app-loader-sea-far {
    to {
        stroke-dashoffset: -9;
    }
}

/* ============================================================
   07. MASTER LIST PAGES
   ============================================================ */
/* ---------- Master list pages ---------- */

/* Compact by intent: the list itself is the content, so the header is kept
   to a single tight band rather than a tall title panel above the data. */
.master-header {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--colorNeutralStroke2);
}

.master-breadcrumb {
    margin: 0;
}

.master-breadcrumb-current {
    font-weight: var(--fontWeightSemibold);
    color: var(--colorBrandForeground1);
}

/* Title on the left, search and the page tools on the right of one row.
   The tools wrap underneath on narrow viewports rather than squeezing the
   search field down to a few characters. */
.master-header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px 20px;
    flex-wrap: wrap;
    width: 100%;
}

.master-title-block {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

/* Tinted tile so each page carries a recognisable mark next to its name. */
.master-header-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    flex: 0 0 auto;
    border-radius: var(--borderRadiusLarge);
    border: 1px solid var(--colorNeutralStroke2);
    background: var(--colorNeutralBackground1);
    box-shadow: var(--shadow2);
}

.master-title-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.master-header-tools {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1 1 420px;
    justify-content: flex-end;
    flex-wrap: wrap;
}

.master-header-tools fluent-button {
    white-space: nowrap;
    flex: 0 0 auto;
}

/* Sits in the header tool row and takes the width the buttons leave. */
.master-search {
    flex: 1 1 320px;
    min-width: 220px;
    max-width: 480px;
}

.master-grid-card {
    margin: 0;
}

/* List grids sit flat on the page: the rows' own hairlines carry the
   structure, so the surrounding card border and shadow are dropped. Written
   against the same selector shape the library uses for .fluent-card so this
   wins on specificity; scoped to master-grid-card so form cards elsewhere
   keep their panel treatment. */
.fluent-card.master-grid-card,
.fluent-card.master-grid-card[appearance="default"],
.fluent-card.master-grid-card[appearance="filled"],
.fluent-card.master-grid-card[appearance="outline"],
.fluent-card.master-grid-card[appearance="subtle"] {
    border: none;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
    padding: 0;
}

    .fluent-card.master-grid-card:hover {
        box-shadow: none;
    }

/* List grids declare a min-width so their columns keep usable widths; this
   wrapper keeps the overflow inside the card instead of scrolling the page. */
.master-grid-scroll {
    width: 100%;
    overflow-x: auto;
    scrollbar-width: thin;
}

    .master-grid-card .fluent-data-grid td {
        padding: 7px 10px;
    }

    /* Header cells carry an inner [col-title] with its own padding, so the
       cell itself keeps only a hairline of its own; padding both stacked and
       made the header band noticeably deeper than the rows beneath it. */
    .master-grid-card .fluent-data-grid th {
        padding: 0 10px;
    }

    .master-grid-card .fluent-data-grid th [col-title] {
        padding-left: 0;
        padding-right: 0;
    }

/* RowSize is a fixed pixel height on the cell (Medium = 44px), so cell
   content plus padding has to fit inside it. 12px top/bottom leaves only
   20px, which a 34px dropdown or a 32px button overflows. Tighten just the
   cells that hold a control -- 34 + 2*5 = 44 exactly -- and leave text
   cells on the roomier default.

   These rules -- along with the header, hover/focus, and stretch rules
   below -- were previously commented out entirely, which is why the
   Quotation container/dimension grids looked cramped and unstyled: none
   of this ever reached the page. Re-enabled as originally intended. */
    .master-grid-card .fluent-data-grid td:has(fluent-dropdown),
    .master-grid-card .fluent-data-grid td:has(fluent-text-input),
    .master-grid-card .fluent-data-grid td:has(fluent-textarea),
    .master-grid-card .fluent-data-grid td:has(fluent-button),
    .master-grid-card .fluent-data-grid td:has(fluent-switch),
    .master-grid-card .fluent-data-grid td:has(fluent-checkbox) {
        padding-top: 5px;
        padding-bottom: 5px;
    }

    /* Sentence case on a plain ground, with only a hairline under the header
       row. Uppercase micro-type on a tinted band read as a heavy label strip
       and fought the row text for attention. */
    .master-grid-card .fluent-data-grid thead th {
        background: transparent;
        font-weight: 600;
        font-size: 12px;
        text-transform: none;
        letter-spacing: 0;
        color: var(--colorNeutralForeground1);
        border-bottom: 1px solid var(--colorNeutralStroke2);
    }

    /* Hairline between rows carries the row rhythm now that the grid has no
       outer border to sit inside. */
    .master-grid-card .fluent-data-grid tbody td {
        border-bottom: 1px solid var(--colorNeutralStroke3, var(--colorNeutralStroke2));
    }

    .master-grid-card .fluent-data-grid tbody tr:hover {
        background: var(--colorNeutralBackground2);
    }

    .master-grid-card .fluent-data-grid tbody tr:focus-within {
        background: var(--colorNeutralBackground2);
    }

/* ---------- Grid stretch / alignment fixes ---------- */

.master-grid-card .fluent-data-grid {
    width: 100%;
}

.master-grid-card .fluent-data-grid fluent-field,
.master-grid-card .fluent-data-grid fluent-text-input,
.master-grid-card .fluent-data-grid fluent-textarea,
.master-grid-card .fluent-data-grid fluent-dropdown {
    width: 100%;
    min-width: 0;
}

.master-grid-card .fluent-data-grid .fluent-stack-horizontal,
.master-grid-card .fluent-data-grid .fluent-stack-vertical {
    align-items: center;
}

.master-grid-card .fluent-data-grid .fluent-stack-horizontal fluent-button,
.master-grid-card .fluent-data-grid .fluent-stack-vertical fluent-button {
    flex-shrink: 0;
}

/* The Containers/Dimensions grids in QuotationForm give CBM/TON only
   64px in their GridTemplateColumns -- too tight for an end-aligned
   number input's own padding, which is what made those cells look
   clipped. Give them breathing room without hardcoding it in markup. */
.qf-panel-title {
    font-size: 13px;
}

.master-empty {
    width: 100%;
    padding: 40px 16px;
    text-align: center;
}

/* ---------- Master form pages ---------- */

.master-form-card {
    border-radius: var(--borderRadiusLarge);
    border: 1px solid var(--colorNeutralStroke2);
    box-shadow: var(--shadow4);
}

.master-form-card fluent-field,
.master-form-card fluent-text-input,
.master-form-card fluent-textarea,
.master-form-card fluent-dropdown {
    width: 100%;
    min-width: 0;
}

.master-form-card .fluent-stack-horizontal > fluent-field:has(fluent-checkbox),
.master-form-card .fluent-stack-vertical > fluent-field:has(fluent-checkbox) {
    align-self: center;
}

.master-form-card .fluent-stack-horizontal > fluent-button,
.master-form-card .fluent-stack-vertical > fluent-button {
    align-self: end;
    width: max-content;
}

/* ---------- Activity feed (aside) ---------- */

.activity-item {
    transition: background-color 0.15s ease, transform 0.15s ease;
}

    .activity-item:hover {
        transform: translateX(2px);
    }

/* ---------- Auth pages (login / register) ---------- */

.auth-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 24px;
    background:
        radial-gradient(1100px 520px at 8% 8%, rgba(14, 124, 134, 0.16), transparent 60%),
        radial-gradient(900px 480px at 92% 92%, rgba(61, 90, 180, 0.14), transparent 60%),
        linear-gradient(180deg, var(--colorNeutralBackground2) 0%, var(--colorNeutralBackground1) 100%);
}

.auth-card {
    width: 100%;
    max-width: 440px;
    padding: 36px 32px;
    /* Token, not a fixed light grey. Hardcoding #eff2f5 kept the card light in
       dark mode while its text followed the theme, so the title and logotype
       rendered near-white on near-white -- 1:1, completely invisible on the
       register and reset-password screens. */
    background: var(--colorNeutralBackground1);
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusXLarge);
    box-shadow: var(--shadow16);
}

    .auth-card .auth-brand {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 6px;
        margin-bottom: 24px;
    }

    .auth-card .auth-brand-logo svg {
        width: 100%;
        height: auto;
    }

        .auth-card .auth-brand-logo svg .logotype {
            fill: var(--colorNeutralForeground1) !important;
        }

    .auth-card .auth-brand-title {
        font-size: var(--fontSizeBase500);
        font-weight: var(--fontWeightSemibold);
        margin: 4px 0 0 0;
    }

    .auth-card .auth-brand-subtitle {
        color: var(--colorNeutralForeground3);
        margin: 0;
    }

.auth-field-row {
    display: flex;
    flex-direction: row;
    gap: 12px;
}

    .auth-field-row > * {
        flex: 1;
        min-width: 0;
    }

.auth-options {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.auth-submit {
    width: 100%;
    height: 42px;
    font-weight: var(--fontWeightSemibold);
}

.auth-footer {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    color: var(--colorNeutralForeground3);
}

.auth-demo {
    font-size: var(--fontSizeBase200);
}

/* ============================================================
   08. HOME / DASHBOARD
   ============================================================ */
/* ---------- Home page ---------- */

.home-page {
    width: 100%;
    max-width: 1240px;
    margin: 0 auto;
}

.home-hero {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
    padding: 36px 32px;
    border-radius: var(--borderRadiusXLarge);
    color: #ffffff;
    overflow: hidden;
    background: linear-gradient(120deg, #0E6B5C 0%, #1D6FB8 60%, #3F5CC0 100%);
    box-shadow: var(--shadow8);
}

    .home-hero::after {
        content: "";
        position: absolute;
        right: -80px;
        top: -80px;
        width: 320px;
        height: 320px;
        border-radius: 50%;
       /* background: rgba(255, 255, 255, 0.08);*/
    }

    .home-hero::before {
        content: "";
        position: absolute;
        right: 60px;
        bottom: -120px;
        width: 260px;
        height: 260px;
        border-radius: 50%;
        /*background: rgba(255, 255, 255, 0.06);*/
    }

.home-hero-text {
    position: relative;
    z-index: 1;
    min-width: 0;
}

    .home-hero-text .home-eyebrow {
        display: inline-block;
        padding: 3px 10px;
        margin-bottom: 10px;
        border-radius: var(--borderRadiusCircular);
        background: rgba(255, 255, 255, 0.16);
        font-size: var(--fontSizeBase200);
        font-weight: var(--fontWeightSemibold);
        letter-spacing: 0.6px;
        text-transform: uppercase;
    }

    .home-hero-text h1 {
        margin: 0;
        font-size: var(--fontSizeBase600);
        font-weight: var(--fontWeightSemibold);
    }

    .home-hero-text p {
        margin: 8px 0 0 0;
        color: rgba(255, 255, 255, 0.88);
        max-width: 640px;
    }

.home-hero-actions {
    position: relative;
    z-index: 1;
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

.hero-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    border-radius: var(--borderRadiusMedium);
    font-weight: var(--fontWeightSemibold);
    text-decoration: none;
    transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
}

.hero-btn-primary {
    background: #ffffff;
    color: #0B594D;
    box-shadow: var(--shadow4);
}

    .hero-btn-primary:hover {
        transform: translateY(-1px);
        box-shadow: var(--shadow8);
    }

.hero-btn-ghost {
    background: rgba(255, 255, 255, 0.14);
    color: #ffffff;
    border: 1px solid rgba(255, 255, 255, 0.45);
}

    .hero-btn-ghost:hover {
        background: rgba(255, 255, 255, 0.24);
    }

.home-section {
    margin-top: 28px;
}

    .home-section > h2 {
        display: flex;
        align-items: center;
        gap: 8px;
        margin: 0 0 12px 0;
        font-size: var(--fontSizeBase400);
        font-weight: var(--fontWeightSemibold);
    }

        .home-section > h2::after {
            content: "";
            flex: 1;
            height: 1px;
            background: var(--colorNeutralStroke2);
        }

.home-modules {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 14px;
}

.home-tile {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px;
    border-radius: var(--borderRadiusLarge);
    border: 1px solid var(--colorNeutralStroke2);
    background: var(--colorNeutralBackground1);
    box-shadow: var(--shadow4);
    text-decoration: none;
    transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}

    .home-tile:hover {
        transform: translateY(-2px);
        box-shadow: var(--shadow8);
        border-color: var(--colorBrandStroke1);
    }

    .home-tile .home-tile-icon {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 40px;
        height: 40px;
        border-radius: var(--borderRadiusMedium);
        background: var(--colorBrandBackground2);
        color: var(--colorBrandForeground1);
        flex-shrink: 0;
    }

    /* Every tile shared the same pale mint icon chip, so a page of a dozen
       identical squares read as flat regardless of the content behind
       them. Cycling a small, brand-adjacent palette across tiles gives
       the grid visual rhythm without touching the markup -- position is
       stable because Home.razor renders these as plain siblings. */
    .home-modules .home-tile:nth-of-type(6n+2) .home-tile-icon {
        background: #D6E8FB;
        color: #1D6FB8;
    }

    .home-modules .home-tile:nth-of-type(6n+3) .home-tile-icon {
        background: #E7E1FB;
        color: #6E4FCF;
    }

    .home-modules .home-tile:nth-of-type(6n+4) .home-tile-icon {
        background: var(--colorPaletteYellowBackground1);
        color: var(--colorPaletteYellowForeground1);
    }

    .home-modules .home-tile:nth-of-type(6n+5) .home-tile-icon {
        background: #FBE2E8;
        color: #C23A63;
    }

    .home-modules .home-tile:nth-of-type(6n+6) .home-tile-icon {
        background: var(--colorPaletteGreenBackground1);
        color: var(--colorPaletteGreenForeground1);
    }

    .home-tile .home-tile-body {
        min-width: 0;
    }

        .home-tile .home-tile-body .home-tile-title {
            font-weight: var(--fontWeightSemibold);
            color: var(--colorNeutralForeground1);
            margin: 0;
        }

        .home-tile .home-tile-body .home-tile-desc {
            font-size: var(--fontSizeBase200);
            color: var(--colorNeutralForeground3);
            margin: 0;
        }

.home-activity {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px;
}

    .home-activity .activity-item {
        display: flex;
        gap: 10px;
        padding: 10px 8px;
        border-radius: var(--borderRadiusMedium);
        transition: background-color 0.15s ease;
    }

        .home-activity .activity-item:hover {
            background-color: var(--colorNeutralBackground2);
        }

    .home-activity .activity-dot {
        flex-shrink: 0;
        width: 8px;
        height: 8px;
        border-radius: 50%;
        margin-top: 6px;
        background-color: var(--colorBrandBackground);
    }

        .home-activity .activity-dot.activity-dot-created {
            background-color: var(--colorPaletteGreenForeground1);
        }

        .home-activity .activity-dot.activity-dot-deleted {
            background-color: var(--colorPaletteRedForeground1);
        }

    .home-activity .activity-content {
        display: flex;
        flex-direction: column;
        gap: 2px;
        min-width: 0;
    }

    .home-activity .activity-desc {
        font-size: var(--fontSizeBase300);
        line-height: 1.3;
    }

    .home-activity .activity-meta {
        font-size: var(--fontSizeBase200);
        color: var(--colorNeutralForeground3);
    }

/* ---------- Scrollbars ---------- */

::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: var(--colorNeutralStroke2);
    border-radius: 8px;
}

    ::-webkit-scrollbar-thumb:hover {
        background: var(--colorNeutralStrokeAccessible);
    }

* {
    scrollbar-width: thin;
    /*scrollbar-color: var(--colorNeutralStroke2) transparent;*/
}

/* ---------- Utility (existing demo helpers) ---------- */

.demopanel {
    border: 1px dashed var(--colorBrandForeground1);
    padding: 5px;
}

:dir(rtl) .hidden-if-ltr {
    display: none;
}

:dir(ltr) .hidden-if-rtl {
    display: none;
}

/* ============================================================
   12. ACCESSIBILITY & RESPONSIVE
   ============================================================ */
/* ---------- Accessibility / interaction ---------- */

.home-tile:focus-visible,
.qf-backlink:focus-visible,
.qf-add-ship:focus-visible,
.qf-add-row-btn:focus-visible {
    outline: 2px solid var(--colorBrandStrokeFocus1);
    outline-offset: 2px;
}

.home-tile:focus-visible {
    box-shadow: var(--shadow8);
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        scroll-behavior: auto !important;
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }
}

/* ============================================================
   13. BLAZOR / FRAMEWORK
   ============================================================ */
/* ---------- Blazor styles ---------- */

#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/* ---------- boot loader ---------- */
/* Shown in the raw index.html markup before Blazor has booted, so it can't
   use AppLoader.razor -- it reuses that component's .app-loader/.app-loader-
   wave classes directly instead of a separate implementation, which keeps
   this first paint visually identical to every loading state Blazor renders
   afterward. */
.boot-loader {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    margin: 20vh auto 1rem auto;
}

.boot-loader-text {
    font-size: 13px;
    font-weight: 600;
    color: var(--colorNeutralForeground3, #647975);
}

.detail-header-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 16px;
    width: 100%;
}

.detail-header-grid h3,
.dashboard-kpi-card h3 {
    margin: 0 0 12px 0;
}


/* =========================================================
   Shipping instruction forms
   Field grid for the SI routing section and cargo dialog:
   several short fields per row on wide screens, one per row
   on phones. Fluent has no form-grid layout component.
   ========================================================= */

.si-form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 12px 16px;
    width: 100%;
    margin-bottom: 12px;
}


/* =========================================================
   Date and time field
   DateTimeField pairs a date picker with a time select on one
   row, bottom-aligned so both inputs line up whatever their
   label heights. The time column matches the Fluent dropdown's
   own minimum width, which it keeps even in a narrower track.
   Fluent has no combined date-time input.
   ========================================================= */

.date-time-field {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 160px;
    gap: 8px;
    align-items: end;
    width: 100%;
}

.date-time-field > *,
.date-time-field fluent-text-input,
.date-time-field fluent-dropdown {
    min-width: 0;
    width: 100%;
}

/* In a multi-column form grid the pair needs two columns' width. */
.si-form-grid > .date-time-field {
    grid-column: span 2;
}


/* =========================================================
   Quotation detail - shipment hierarchy
   A quotation is a tree: a shipment carries container lines,
   and a charge hangs off the shipment, one of its containers
   or its cargo. Nesting is shown with indentation and a left
   rule rather than separate tables, so a charge always reads
   as belonging to something. Fluent has no nested
   master-detail primitive, so this is plain markup styled
   with Fluent tokens.
   ========================================================= */

.qd-shipment {
    margin-top: 16px;
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium);
    overflow: hidden;
}

.qd-shipment:first-of-type {
    margin-top: 8px;
}

.qd-shipment-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    padding: 10px 14px;
    background: var(--colorNeutralBackground2);
    border-bottom: 1px solid var(--colorNeutralStroke2);
}

.qd-shipment-id {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.qd-shipment-name {
    font-weight: var(--fontWeightSemibold);
}

.qd-shipment-route {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}

/* Shipment facts as a description list: label above value, so the row stays
   readable when it wraps on a narrow screen. */
.qd-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
    margin: 0;
}

.qd-facts dt {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}

.qd-facts dd {
    margin: 2px 0 0 0;
    font-size: var(--fontSizeBase300);
}

/* A container line and its charges, inset from the shipment that owns them. */
.qd-container {
    padding: 10px 14px 12px 14px;
    margin: 10px 14px 12px 22px;
    border-left: 2px solid var(--colorNeutralStroke2);
}

.qd-container-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 6px;
}

.qd-container-name {
    font-weight: var(--fontWeightSemibold);
    font-size: var(--fontSizeBase300);
}

.qd-container-teu {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}

.qd-charges {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fontSizeBase300);
}

.qd-charges th {
    text-align: left;
    padding: 5px 8px;
    border-bottom: 1px solid var(--colorNeutralStroke2);
    color: var(--colorNeutralForeground3);
    font-weight: var(--fontWeightSemibold);
}

.qd-charges td {
    padding: 5px 8px;
    border-bottom: 1px solid var(--colorNeutralStroke3);
}

.qd-charges .qd-num,
.qd-money {
    text-align: right;
    white-space: nowrap;
}

.qd-charges tfoot td {
    border-top: 1px solid var(--colorNeutralStroke1);
    border-bottom: none;
    font-weight: var(--fontWeightSemibold);
}

/* An excluded charge stays on the page -- it is part of the quotation's record --
   but is visibly not counted toward the total. */
.qd-charges tr.qd-excluded td {
    color: var(--colorNeutralForegroundDisabled);
    text-decoration: line-through;
}

.dashboard-kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 16px;
    width: 100%;
}

.dashboard-kpi-card {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 20px;
    border-radius: var(--borderRadiusXLarge);
    background: var(--colorNeutralBackground2);
    cursor: pointer;
    transition: box-shadow 0.15s ease-in-out;
    box-shadow: var(--shadow2);
}

.dashboard-kpi-card:hover {
    box-shadow: var(--shadow8);
}

.dashboard-kpi-value {
    font-size: 2rem;
    font-weight: 600;
    line-height: 1;
}

.dashboard-kpi-label {
    font-weight: 600;
}

.dashboard-kpi-sub {
    color: var(--colorNeutralForeground3);
    font-size: 0.875rem;
}

.dashboard-kpi-static {
    cursor: default;
}

    .dashboard-kpi-static:hover {
        box-shadow: var(--shadow2);
    }

.kpi-delta {
    margin-right: 6px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.kpi-delta-up {
    color: var(--colorPaletteGreenForeground1);
}

.kpi-delta-down {
    color: var(--colorPaletteRedForeground1);
}

/* =========================================================
   Reports hub and module dashboards
   ========================================================= */
.report-hub-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
}

.report-hub-area {
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: 100%;
}

    .report-hub-area h2 {
        display: flex;
        align-items: center;
        gap: 8px;
        margin: 8px 0 0 0;
        font-size: var(--fontSizeBase500);
    }

.report-hub-count {
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase300);
    font-weight: 400;
}

.report-hub-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 12px;
    width: 100%;
}

.report-hub-card {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    background: var(--colorNeutralBackground1);
    color: var(--colorNeutralForeground1);
    text-decoration: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

    .report-hub-card:hover,
    .report-hub-card:focus-visible {
        border-color: var(--colorBrandStroke1);
        box-shadow: var(--shadow4);
    }

.report-hub-card-featured {
    background: var(--colorBrandBackground2);
    border-color: var(--colorBrandStroke2);
}

.report-hub-card-title {
    font-weight: 600;
}

.report-hub-card-sub {
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase200);
}

.module-dashboard-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
}

@media (max-width: 600px) {
    .report-hub-grid {
        grid-template-columns: 1fr;
    }
}

.report-sheet {
    width: 100%;
    max-width: 900px;
    margin: 0 auto;
    padding: 32px;
    background: var(--report-background);
    color: var(--report-foreground);
    border-radius: var(--borderRadiusXLarge);
    box-shadow: var(--shadow8);
}

.report-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 24px;
    flex-wrap: wrap;
    margin-bottom: 24px;
    padding-bottom: 16px;
    border-bottom: 2px solid var(--report-border);
}

.report-header h1 {
    margin: 0 0 8px 0;
    font-size: 1.5rem;
    letter-spacing: 1px;
}

.report-header-right {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 0.9rem;
    text-align: right;
}

.report-table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 16px;
}

.report-table th,
.report-table td {
    border: 1px solid var(--report-border);
    padding: 8px 10px;
    text-align: left;
    font-size: 0.875rem;
}

.report-table th {
    background: var(--report-header-background);
    font-weight: 600;
}

.report-totals {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 4px;
    margin-bottom: 24px;
    font-size: 1rem;
}

.report-footer {
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid #e0e0e0;
    font-size: 0.875rem;
    color: var(--report-muted-foreground);
}

/* ============================================================
   11. QUOTATION WORKSPACE
   ============================================================ */
/* ============================================================
   Quotation form (three-pane workspace)

   Specificity notes (see the FluentUI CSS model reference):
   - app.css is linked AFTER the FluentUI bundle, so equal-specificity
     rules here win the tie. That removes the need for most !important.
   - Library component rules are Blazor-scoped, e.g.
     fluent-text[size="200"][b-xxxxx] = (0,2,1). To beat those we qualify
     our classes with the element and an ancestor class, e.g.
     .qf-summary fluent-text.qf-grand-total = (0,2,1) plus later source.
   - Only rules that lose on strict specificity keep !important, and each
     one says why.
   ============================================================ */

/* ---------- Three-pane widths ----------
   FluentGrid breakpoints are viewport-based (md=960, lg=1280) while its
   column percentages apply to the container, which here is ~292px narrower
   than the viewport because of the app nav. That mismatch left the middle
   pane too narrow for the pricing grid, so use fixed rails and a fluid
   middle instead of 12-column proportions.
   flex-basis must be 0, not auto: with auto the wide pricing grid becomes
   the flex base size and wraps the summary rail onto its own row. */
/* 960px is FluentUI's own `md` breakpoint (xs <600, sm <960, md <1280,
   lg <1920, xl <2560, xxl >=2560). A custom 992px value left a 32px band
   where the grid had switched to md but these rails had not. The library
   also mirrors the current breakpoint on <body data-media="xs|sm|md|…">
   if a rule ever needs to match it exactly. */
/* Small-screen safety. Desktop rails are enabled only at 960px+. */
@media (max-width: 959px) {
    .app-content {
        padding: 20px 16px 40px;
    }

    .page-container,
    .home-page {
        max-width: none;
    }

    .qf-leftrail {
        position: static;
    }

    .qf-backlink {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
    }
}

@media (min-width: 960px) {
    /* Nav and the summary rail now stack in one left column (qf-leftrail
       below) instead of nav-left/summary-right, so qf-main picks up the
       width the summary column used to reserve on its own. Widened again
       to 296px so the nav list has room to breathe and still lines up
       edge-to-edge with the summary card stacked under it. */
    .fluent-grid > .qf-navcol {
        flex: 0 0 296px;
        max-width: 296px;
    }

    .fluent-grid > .qf-main {
        flex: 1 1 0;
        max-width: none;
        min-width: 0;
    }
}

/* ---------- Field grid density ----------
   FluentGrid drives both its gap and its column-width maths from
   --fluent-grid-gap: width is calc(N/12*100% - (12-N)/12 * gap). Setting
   gap alone leaves columns sized for the old value and misaligns them, so
   always set the variable.
   FluentField 12px margins come from DefaultStyles.FluentFieldClass, which
   Program.cs clears — no margin override needed here. */
.qf-main .fluent-grid {
    --fluent-grid-gap: 13px;
}

.qf-main fluent-field {
    row-gap: 0;
}

/* Let the breakpoint ladder actually do its job.
   Flex items default to min-width:auto, so their content sets a floor and
   a column refuses to shrink below it — the control then spills past the
   cell instead of resizing. Zeroing it down the chain (grid item -> field
   -> control) lets each column shrink to its share, and the Xs/Sm/Md
   breakpoints handle re-flowing to fewer per row. */
.qf-main .fluent-grid > div,
.qf-main fluent-field {
    min-width: 0;
}

/* Width is handled by the components' own Width="100%" parameter, which
   sets width on both the FluentField wrapper and the inner web component
   (StyleValue + ComponentStyleValue). Style="width:100%" only reaches the
   wrapper, which is display:inline-grid and shrink-wraps its content —
   that left text inputs at 197px and dropdowns at 160px in the same
   359px column. See the library's Debug/Width page.

   Two things still need correcting, measured on the real Pricing panel:
   - fluent-dropdown carries an intrinsic min-width (~160px) and overflowed
     a 152px column, so it stuck out past the cell edge.
   - control heights disagree: number inputs render 36px, text inputs and
     dropdowns 32px, so a mixed row sits unevenly. */
/* Autocomplete, DatePicker and NumberInput all render fluent-text-input;
   Select renders fluent-dropdown. Those two elements plus textarea cover
   every control on this page. */
.qf-main fluent-dropdown,
.qf-main fluent-text-input,
.qf-main fluent-textarea {
    min-width: 0;
    max-width: 100%;
    box-sizing: border-box;
}

.qf-main fluent-dropdown,
.qf-main fluent-text-input {
    min-height: 32px;
    height: 32px;
}

/* ---------- Inputs ----------
   v5 renders fluent-text-input / fluent-dropdown / fluent-textarea. There
   are no fluent-text-field, -select or -number-field elements. The visible
   border is on div.root inside shadow DOM: sides use --colorNeutralStroke1,
   the bottom edge uses --colorNeutralStrokeAccessible. Only inherited
   custom properties cross the shadow boundary. */
fluent-text-input:focus-within,
fluent-dropdown:focus-within,
fluent-textarea:focus-within {
    --colorCompoundBrandStroke: var(--colorBrandStroke1);
    --colorNeutralStrokeAccessible: var(--colorBrandStroke1);
    outline: none;
    box-shadow: none;
}

/* FluentField emits a bare <label slot="label"> in the light DOM with no
   class. The label.label seen in devtools lives in the input shadow root
   and is unreachable from here. */
.qf-main fluent-field > label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.25;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--colorNeutralForeground3);
    margin-bottom: 5px;
}

/* Checkbox labels sit beside the control and stay sentence case. A checkbox
   field's min-width was zeroed along with every other grid-item/field above
   so text columns could shrink to their share — but a checkbox + its label
   both need to fit on one line, so give this one variant back a floor wide
   enough for the longest label used ("Optional"/"Included") plus the box,
   rather than letting the grid squeeze them until they overlap. */
.qf-main fluent-field:has(fluent-checkbox) {
    min-width: 100px;
}

.qf-main fluent-field:has(fluent-checkbox) > label {
    font-size: 13px;
    font-weight: 400;
    line-height: normal;
    text-transform: none;
    letter-spacing: normal;
    color: var(--colorNeutralForeground1);
    margin-bottom: 0;
}

/* Native spin buttons clutter dense numeric cells. */
.qf-main input[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
}

    .qf-main input[type="number"]::-webkit-outer-spin-button,
    .qf-main input[type="number"]::-webkit-inner-spin-button {
        -webkit-appearance: none;
        margin: 0;
    }

/* ---------- Nav rail ---------- */

.fluent-card.qf-nav {
    padding: 8px;
}

.qf-nav fluent-button {
    border-radius: var(--borderRadiusSmall);
    /* Containing block for the status dot. Must live here rather than in
       the inline style attribute, which fluent-button does not forward. */
    position: relative;
}

.qf-nav-dot {
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    /* !important: fluent-badge scoped rules set width/height/padding at
       (0,2,1) on this same element, which outranks a bare class. */
    width: 6px !important;
    height: 6px !important;
    min-width: 0 !important;
    padding: 0 !important;
    border: none !important;
}

    /* Fluent success green is #107C10, and subtle renders white — which is
       invisible on a white card. */
    .qf-nav-dot[color="success"] {
        background-color: var(--colorPaletteGreenForeground1);
    }

    .qf-nav-dot[color="subtle"] {
        background-color: var(--colorNeutralStroke1);
    }


/* =========================================================
   Badge legibility in dark mode
   Two different failures, one per badge colour family.

   1. A *status* badge (success/danger/warning) keeps the same
      saturated dark background in both themes, but the library
      flips its text to the near-black on-brand colour for dark.
      That put rgb(4,13,11) on the success green (#107C10) at
      3.66:1. Light mode uses white on that same green at
      5.37:1, so white is pinned for dark too.

   2. A *brand* badge is the opposite: in dark mode
      --colorBrandBackground is the light mint #3FBF9E, whose
      correct partner is the near-black --colorNeutralForegroundOnBrand
      (#040D0B). Left to inherit it rendered white instead, at
      2.29:1, so the token is applied explicitly.

   Colour is targeted rather than appearance because these
   badges are written as <FluentBadge Color="..."> with no
   Appearance, so an [appearance="filled"] selector misses them.
   ========================================================= */

body[data-theme="dark"] fluent-badge:not(.qf-nav-dot):not([color="brand"]):not([color="subtle"]):not([color="neutral"]) {
    color: #FFFFFF;
}

/* Only a badge that actually paints the brand fill. An outline or ghost badge
   sits on the page background, where the on-brand near-black would be 1.19:1. */
body[data-theme="dark"] fluent-badge[color="brand"]:not(.qf-nav-dot):not([appearance="outline"]):not([appearance="ghost"]) {
    color: var(--colorNeutralForegroundOnBrand);
}

@media (prefers-color-scheme: dark) {
    /* See the note at the token block above: `:not([data-theme="light"])`
       is what keeps this from also hitting someone who explicitly chose
       light while their OS is dark. */
    body:not([data-theme="light"]) fluent-badge:not(.qf-nav-dot):not([color="brand"]):not([color="subtle"]):not([color="neutral"]) {
        color: #FFFFFF;
    }

    body:not([data-theme="light"]) fluent-badge[color="brand"]:not(.qf-nav-dot):not([appearance="outline"]):not([appearance="ghost"]) {
        color: var(--colorNeutralForegroundOnBrand);
    }
}

/* Active row: a flat pale tint, no button chrome. Hover/pressed need the
   same retint -- left alone, they fall back to the library's solid brand
   green while the text stays pinned to colorBrandForeground1 (dark green),
   which put dark-green text on a dark-green hover fill and made the label
   disappear on hover. */
.qf-nav fluent-button[appearance="primary"] {
    --colorBrandBackground: var(--colorBrandBackgroundTint);
    --colorBrandBackgroundHover: var(--colorBrandBackground2Hover);
    --colorBrandBackgroundPressed: var(--colorBrandBackground2Pressed);
    --colorNeutralForegroundOnBrand: var(--colorBrandForeground1);
}

.qf-nav fluent-button[appearance="outline"] {
    background: var(--colorBrandBackgroundTint);
    border-color: transparent;
}

.qf-nav fluent-button[appearance="transparent"] {
    border-color: transparent;
}

/* ---------- Main pane header ---------- */

.qf-topbar {
    width: 100%;
    margin-bottom: 14px;
}

/* Back-to-list pill, from .backlink in the three-pane mockup. Inline in the
   topbar rather than the mockup's position:fixed, since the app already has
   its own chrome at the top of the viewport. */
.qf-backlink {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex-shrink: 0;
    padding: 6px 10px;
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: 999px;
    background: var(--qf-surface);
    box-shadow: var(--shadow2);
    font-family: var(--fontFamilyBase);
    font-size: 12px;
    line-height: 1;
    white-space: nowrap;
    color: var(--colorNeutralForeground3);
    text-decoration: none;
}

    .qf-backlink:hover {
        color: var(--colorNeutralForeground1);
        border-color: var(--colorNeutralStroke1);
    }

.qf-main-head {
    margin-bottom: 18px;
}

    .qf-main-head h2 {
        font-family: "Archivo", "Segoe UI", system-ui, sans-serif;
        font-size: 18px;
        font-weight: 700;
        letter-spacing: -0.01em;
        margin: 0 0 3px;
        display: flex;
        align-items: center;
        gap: 9px;
        color: var(--colorNeutralForeground1);
    }

    .qf-main-head p {
        margin: 0;
        color: var(--colorNeutralForeground3);
        font-size: 12.5px;
    }

/* Per-section Save / Cancel / Delete bar. Each stage of the quotation saves
   on its own, so the actions sit with the heading they belong to. */
.qf-section-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 10px;
}

/* Amber "Unsaved" tag: without autosave, the user needs a standing cue that
   this section still holds edits. */
.qf-unsaved {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .03em;
    text-transform: uppercase;
    color: var(--colorStatusWarningForeground1);
    background: var(--colorStatusWarningBackground1);
    border: 1px solid var(--colorStatusWarningBorder1);
    border-radius: 999px;
    padding: 3px 9px;
}

.qf-main-head .qf-delete-btn {
    --colorNeutralForeground2: var(--colorStatusDangerForeground1);
    margin-inline-start: auto;
}

/* Panel headings. Qualified with the element so this beats the global
   heading rule fluent-text[weight="semibold"] at (0,1,1). */
.qf-main fluent-text.qf-panel-title {
    font-family: "Archivo", "Segoe UI", system-ui, sans-serif;
    font-size: 12.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--colorNeutralForeground1);
}

.qf-main fluent-text.qf-rate-hint {
    display: block;
    margin-top: 4px;
    font-size: 11.5px;
    color: var(--colorNeutralForeground3);
}

/* =========================================================
   Form toolbar icon buttons
   Square brand-tinted icon buttons used in the page-header
   toolbars of the parent/child forms (quotation, rate, sales
   order, meeting). Defined here rather than in a scoped
   stylesheet because several pages share the class.
   ========================================================= */

.quotation-icon-btn {
    width: 36px;
    height: 36px;
    min-width: 36px;
    padding: 0;
    border-radius: 9px;
    flex-shrink: 0;
    color: var(--colorBrandForeground1);
    background: color-mix(in srgb, var(--colorBrandBackground2, var(--colorBrandBackground)) 24%, transparent);
    border: 1px solid color-mix(in srgb, var(--colorBrandBackground) 34%, transparent);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
    transition: background-color 0.14s ease, border-color 0.14s ease, color 0.14s ease, transform 0.14s ease;
}

    .quotation-icon-btn:hover {
        color: var(--colorBrandForeground1);
        background: color-mix(in srgb, var(--colorBrandBackground2, var(--colorBrandBackground)) 36%, transparent);
        border-color: color-mix(in srgb, var(--colorBrandBackground) 48%, transparent);
        transform: translateY(-1px);
    }

    .quotation-icon-btn:focus-visible {
        outline: 2px solid var(--colorBrandStroke1, var(--colorBrandBackground));
        outline-offset: 2px;
    }


/* ---------- Dashed add buttons ---------- */

.qf-add-ship-row {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

.qf-add-ship,
.qf-add-row-btn {
    border: 1px dashed var(--colorNeutralStroke1);
    background: none;
    border-radius: 8px;
    padding: 7px 12px;
    font-family: var(--fontFamilyBase);
    font-size: 12px;
    font-weight: 600;
    color: var(--colorNeutralForeground3);
    cursor: pointer;
    /* Keep the label on one line when the panel header gets tight. */
    white-space: nowrap;
    flex-shrink: 0;
}

    .qf-add-ship:hover:not(:disabled),
    .qf-add-row-btn:hover:not(:disabled) {
        border-color: var(--colorBrandBackground);
        color: var(--colorBrandForeground1);
    }

    /* Shipments cannot exist before the quotation is saved, so the add buttons
       are disabled until the details section has been created. */
    .qf-add-ship:disabled,
    .qf-add-row-btn:disabled {
        color: var(--colorNeutralForegroundDisabled);
        border-color: var(--colorNeutralStrokeDisabled);
        cursor: not-allowed;
    }

/* ---------- Data grid rows ---------- */
.qf-main .fluent-grid > div,
.qf-main fluent-field {
    --colorNeutralStroke1: transparent;
    background: transparent;
    min-width: 0;
}

.qf-main fluent-dropdown,
.qf-main fluent-text-input,
.qf-main fluent-textarea {
    min-width: 0;
    max-width: 100%;
    box-sizing: border-box;
}

.master-grid-card .fluent-data-grid fluent-field,
.master-grid-card .fluent-data-grid fluent-text-input,
.master-grid-card .fluent-data-grid fluent-textarea,
.master-grid-card .fluent-data-grid fluent-dropdown {
    width: 100%;
    min-width: 0;
}
/* ---------- Container & dimension line tables ----------

   In-grid text and number inputs use the library appearance
   TextInputAppearance.Underline (set in the markup), so a dense table reads
   as a row of values rather than a wall of boxes. FluentSelect exposes no
   Appearance parameter, so the dropdowns are matched here instead -- this is
   the smallest CSS needed to keep the two control types consistent.

   Cell padding is deliberately left to the library (0 18px): inside a 44px
   RowSize="Medium" row that already centres a 32px control. */

.qf-main .fluent-data-grid fluent-dropdown {
    background: var(--colorNeutralBackground1);
    min-width: 0;
}

    .qf-main .fluent-data-grid fluent-dropdown:focus-within {
        --colorNeutralStroke1: var(--colorCompoundBrandStroke);
        --colorNeutralStrokeAccessible: var(--colorCompoundBrandStroke);
    }

/* Custom properties inherit, so the transparent surface above also reaches
   the dropdown's own options and popup, leaving them unreadable over page
   content. Restore a solid surface on the list parts. */
.qf-main .fluent-data-grid fluent-listbox,
.qf-main .fluent-data-grid fluent-option,
.qf-main .fluent-data-grid fluent-popover-b {
    --colorNeutralBackground1: var(--qf-surface);
    background: var(--qf-surface);
}

/* Row-level icon buttons (dangerous-goods toggle, row menu). Square at the
   32px the library gives them naturally -- setting a smaller height here has
   no effect, the inner control owns it, so a narrower width just made them
   oblong. */
.qf-main .fluent-data-grid fluent-button {
    width: 32px;
    min-width: 32px;
    border-radius: var(--borderRadiusMedium);
    padding: 0;
    flex-shrink: 0;
}

/* The library pads every cell 0 18px. The trailing row-menu column is only
   ~48px wide, so 36px of that is padding and the 32px button overflows its
   own cell. Trim the padding on the last column, header included so the
   grid line stays straight. */
.qf-main .fluent-data-grid tbody td:last-child,
.qf-main .fluent-data-grid thead th:last-child {
    padding-inline: 4px;
    text-align: center;
}

/* Hairline row separators: the eye should follow values, not rules. */
.qf-main .fluent-data-grid tbody td {
    border-bottom: 1px solid var(--colorNeutralStroke3);
}

/* Container/Dimension rows pack five or six controls edge to edge with no
   fill on any of them (Appearance="TextInputAppearance.Underline" per the
   library note above), so without a divider the row reads as one
   continuous strip instead of distinct columns. A hairline vertical rule
   between cells gives the eye a column to track. */
.qf-main .fluent-data-grid tbody td {
    border-right: 1px solid var(--colorNeutralStroke3);
}

    .qf-main .fluent-data-grid tbody td:last-child {
        border-right: none;
    }

.qf-main .fluent-data-grid thead th {
    color: var(--colorNeutralForeground1);
    font-weight: var(--fontWeightSemibold);
    background: var(--colorNeutralBackground2);
    border-bottom: 1px solid var(--colorNeutralStroke1);
}

.qf-main .master-grid-card {
    border-color: var(--colorNeutralStroke1);
    box-shadow: var(--shadow8);
}

.qf-main .master-grid-card fluent-dropdown,
.qf-main .master-grid-card fluent-text-input,
.qf-main .master-grid-card fluent-number-input {
    --colorNeutralForeground2: var(--colorNeutralForeground1);
}

.qf-main .fluent-data-grid tbody tr:hover td {
    background: var(--colorNeutralBackground2);
}

/* ---------- Left rail (nav + summary, stacked) ---------- */
/* The summary card used to be its own sticky column; it now stacks below
   the nav card in qf-navcol. The rail itself just sticks -- it does not
   clip or scroll as a whole, so the summary always renders at its full
   height, uncropped. Only the nav card scrolls internally, since its own
   shipment list is what grows long on a big quotation. */
.qf-leftrail {
    position: sticky;
    top: 12px;
}

.qf-nav {
    max-height: min(50vh, 480px);
    overflow-y: auto;
}

.qf-summary fluent-text.qf-summary-label {
    font-family: "Archivo", "Segoe UI", system-ui, sans-serif;
    font-size: 12.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--colorNeutralForeground2);
}

/* The headline figure is the accent colour, not body ink. */
.qf-summary fluent-text.qf-grand-total {
    font-family: var(--fontFamilyNumeric);
    font-variant-numeric: tabular-nums;
    font-size: 25px;
    font-weight: 700;
    line-height: 1.15;
    color: var(--colorBrandForeground1);
    margin: 4px 0 2px;
}

.qf-summary fluent-text.qf-grand-sub {
    font-size: 11px;
    color: var(--colorNeutralForeground3);
    margin-bottom: 18px;
}

/* Per-shipment rows: hairline separated, figure right-aligned in mono. */
.qf-ship-line {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    padding: 8px 0;
    border-bottom: 1px solid var(--colorNeutralStroke2);
    font-size: 12px;
}

    .qf-ship-line:last-of-type {
        border-bottom: none;
    }

.qf-ship-fig {
    font-weight: 600;
    white-space: nowrap;
}

/* Amber notice. FluentMessageBar Warning intent renders red here, so this
   is a plain element styled directly, as the mockup does. */
.qf-rail-notice {
    display: flex;
    gap: 8px;
    align-items: flex-start;
    width: 100%;
    margin-top: 18px;
    padding: 10px 12px;
    border: 1px solid var(--colorPaletteYellowBorder1, #EDC998);
    border-radius: 8px;
    background: var(--colorPaletteYellowBackground1, #FBEEDD);
    color: var(--colorPaletteYellowForeground1, #A8641A);
    font-size: 11.5px;
    line-height: 1.4;
}

    .qf-rail-notice svg {
        width: 15px;
        height: 15px;
        flex-shrink: 0;
        margin-top: 1px;
    }

.qf-save-btn {
    margin-top: 18px;
}

/* Money figures anywhere on the page. */
.qf-money {
    font-family: var(--fontFamilyNumeric);
    font-variant-numeric: tabular-nums;
}


@media print {
    .report-sheet {
        box-shadow: none;
        border-radius: 0;
        padding: 0;
    }
}


/* =========================================================
   Master list tool strip (search + bulk actions)
   Fluent UI has no bulk-action bar component. Search and the
   bulk tools are combined into one strip because as separate
   stacked bands the narrow search box left a wide empty
   gutter, and the action bar read as a heavy coloured slab.
   ========================================================= */

.master-toolstrip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 12px;
    width: 100%;
}

/* The record toolbar is a flat command strip on the page ground, separated
   from the grid by a single hairline rather than a card of its own. Boxing
   it made the page read as stacked panels competing with the grid; a portal
   command bar is chrome, not content. */
.master-bulkbar {
    flex: 1 1 100%;
    min-width: 0;
    padding: 0 0 6px;
    border: none;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
}

/* Selection is signalled on the count alone -- there is no longer a border
   to tint, and colouring the strip overwhelmed the toolbar's own icons. */
.master-bulkbar-active {
    background: transparent;
}

/* The toolbar wraps to a second line when the page is narrow. It used to
   scroll horizontally instead, which hid the leading actions behind a
   scrollbar with no sign that anything was there. */
.master-bulkbar-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0 2px;
    min-height: 32px;
}

/* Toolbar buttons carry their label beside the icon and sit evenly spaced,
   which is what makes the strip read as a toolbar rather than a slab.
   Flat and compact: a command bar's buttons are text with a small leading
   icon, taking no background until hovered. */
.master-bulkbar-row fluent-button::part(control) {
    gap: 6px;
    padding: 0 8px;
    height: 30px;
    min-width: 0;
    font-weight: 400;
    background: transparent;
    border-color: transparent;
}

.master-bulkbar-row fluent-button {
    margin-left: 0;
}

.master-bulkbar-row fluent-button:not([disabled]):hover::part(control) {
    background: var(--colorNeutralBackground3);
}

/* Selection count, paired with its checkbox icon. */
.master-bulkbar-count {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0 6px 0 2px;
    white-space: nowrap;
    color: var(--colorNeutralForeground2);
}

.master-bulkbar-active .master-bulkbar-count {
    color: var(--colorBrandForeground1);
}

.master-bulkbar-sep {
    height: 18px;
    margin: 0 8px;
    flex: 0 0 auto;
}

/* Toolbar buttons sit tight together, so stop their labels wrapping.
   The buttons render as <fluent-button> custom elements, so the class
   selector alone matched nothing and multi-word labels such as
   "Generate sales order" broke across three lines. */
.master-bulkbar-row .fluent-button,
.master-bulkbar-row fluent-button {
    white-space: nowrap;
    flex: 0 0 auto;
}

/* Safety net for any <fluent-menu> reintroduced before the Fluent web
   components script is loaded: an un-upgraded menu paints its items
   straight into the page instead of into a popup. */
fluent-menu:not(:defined) {
    display: none;
}

/* Delete is the only irreversible action in the bar, so it is tinted
   apart from the neutral tools rather than relying on its icon alone. */
.master-bulkbar-danger .fluent-button:not([disabled]) {
    color: var(--colorPaletteRedForeground1);
}

/* The confirmation bar replaces the buttons in-place, so it should
   not inherit the toolbar's own padding twice. */
.master-bulkbar .fluent-message-bar {
    margin: 0;
}

/* Keep the checkbox column narrow rather than letting it share the
   even width distribution the other columns use. */
.master-grid-card .fluent-data-grid th.master-select-col,
.master-grid-card .fluent-data-grid td.master-select-col {
    width: 44px;
    padding-right: 0;
}

/* Centre the select-all checkbox over the column of ticks below it. */
.master-grid-card .fluent-data-grid th.master-select-col [col-title] {
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* =========================================================
   List row cells
   Two-line cells: the primary value on top, a quieter
   supporting detail underneath. Fluent UI has no such cell
   template, and packing every attribute into its own column
   left the grid wide, truncated and hard to scan.
   ========================================================= */

.list-cell {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.list-cell-primary {
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The record's identifier doubles as the link to its detail page. */
.list-cell-link {
    color: var(--colorBrandForegroundLink);
    text-decoration: none;
    cursor: pointer;
}

    .list-cell-link:hover {
        text-decoration: underline;
    }

.list-cell-secondary {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Placeholder for a value the record does not carry yet, so an empty cell
   reads as deliberate rather than as a rendering fault. */
.list-cell-empty {
    color: var(--colorNeutralForeground4, var(--colorNeutralForeground3));
}

/* Numeric columns line up on their last digit. */
.list-cell-amount {
    font-variant-numeric: tabular-nums;
    font-weight: var(--fontWeightSemibold);
}

/* Status badge with a leading dot, matching the pill used on the
   dashboard rather than Fluent's default solid badge. */
.list-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: var(--borderRadiusCircular);
    font-size: var(--fontSizeBase200);
    font-weight: var(--fontWeightSemibold);
    white-space: nowrap;
    background: var(--colorNeutralBackground3);
    color: var(--colorNeutralForeground2);
}

    .list-status::before {
        content: "";
        width: 6px;
        height: 6px;
        border-radius: 50%;
        background: currentColor;
    }

.list-status-active {
    background: var(--colorPaletteGreenBackground1, #E7F5EC);
    color: var(--colorPaletteGreenForeground1, #0E6027);
}

.list-status-archived {
    background: var(--colorNeutralBackground3);
    color: var(--colorNeutralForeground3);
}

/* =========================================================
   List footer
   Item summary on the left, pager on the right. FluentPaginator
   renders only the page controls, so the "Showing x to y" line
   is supplied by the page.
   ========================================================= */

.master-grid-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px 16px;
    padding: 12px 14px 4px;
    border-top: 1px solid var(--colorNeutralStroke2);
    color: var(--colorNeutralForeground3);
}

/* =========================================================
   Header filters
   Filters are inline selects rather than a dropdown menu:
   the Fluent web components script is not loaded, so
   <fluent-menu> never upgrades and would paint its items
   into the page instead of into a popup.
   ========================================================= */

.master-filters {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 1 auto;
}

.master-filters fluent-select,
.master-filters fluent-dropdown {
    flex: 0 1 auto;
}

/* Delete is the only irreversible row action, so it is tinted apart from
   the neutral view/edit buttons rather than relying on its icon alone. */
.list-action-danger fluent-button:not([disabled]) {
    color: var(--colorPaletteRedForeground1);
}

/* FluentPaginator prints its own total-items line, which duplicates the
   "Showing x to y of z" summary beside it. */
.master-grid-footer .fluent-paginator > .pagination-text:first-child {
    display: none;
}


/* =========================================================
   Opportunity Pipeline
   A horizontally scrolling board of stage columns. Fluent UI
   has no board/kanban component, so the column layout is
   custom; the cards themselves are FluentCard.
   ========================================================= */

.pipeline-summary {
    margin-bottom: 16px;
    padding: 14px 18px;
}

.pipeline-summary-label {
    display: block;
    font-size: 11px;
    letter-spacing: 0.06em;
    color: var(--colorNeutralForeground3);
    white-space: nowrap;
}

.pipeline-summary-figure {
    display: block;
    font-size: 20px;
    font-weight: 600;
    white-space: nowrap;
}

/* The board scrolls sideways so added stages never squeeze the
   columns narrower than a readable card. The right-edge fade
   marks the overflow, since a scrollbar alone is easy to miss
   when the last visible column is cut off mid-card. */
.pipeline-board {
    display: flex;
    gap: 14px;
    align-items: stretch;
    overflow-x: auto;
    overflow-y: hidden;
    padding: 2px 2px 14px;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
}

.pipeline-board::-webkit-scrollbar {
    height: 10px;
}

.pipeline-board::-webkit-scrollbar-track {
    background: var(--colorNeutralBackground3);
    border-radius: 5px;
}

.pipeline-board::-webkit-scrollbar-thumb {
    background: var(--colorNeutralStroke1);
    border-radius: 5px;
}

.pipeline-board::-webkit-scrollbar-thumb:hover {
    background: var(--colorNeutralForeground3);
}

/* Columns share one height so their backgrounds line up as a row
   of equal panels rather than ragged blocks sized to their cards. */
.pipeline-column {
    flex: 0 0 300px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    background: var(--colorNeutralBackground3);
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: 8px;
    padding: 12px;
    box-sizing: border-box;
    scroll-snap-align: start;
}

/* Closed stages are visually quieter than the open ones, so the
   working part of the pipeline reads first. */
.pipeline-column.is-final {
    background: var(--colorNeutralBackground2);
}

/* The head is a two-row grid: title and count on the first row,
   the column total on the second. Side by side, a long total
   pushed the stage name into wrapping. */
.pipeline-column-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--colorNeutralStroke2);
}

.pipeline-column-title {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.pipeline-column-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pipeline-column-total {
    font-size: 12px;
    color: var(--colorNeutralForeground3);
    white-space: nowrap;
}

/* Cards stack in a scrollable list so one busy stage cannot make
   the whole board taller than the viewport. */
.pipeline-column-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
    flex: 1 1 auto;
    min-height: 120px;
    max-height: calc(100vh - 340px);
    overflow-y: auto;
    padding-right: 2px;
}

.pipeline-card {
    padding: 12px;
    display: block;
}

.pipeline-card-title {
    font-weight: 600;
    line-height: 1.3;
}

.pipeline-card-customer {
    font-size: 12px;
    color: var(--colorNeutralForeground3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pipeline-card-figures {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.pipeline-card-value {
    font-weight: 600;
    white-space: nowrap;
}

.pipeline-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    min-height: 90px;
    border: 1px dashed var(--colorNeutralStroke2);
    border-radius: 6px;
    color: var(--colorNeutralForeground3);
    font-size: 12px;
}


/* =========================================================
   Responsive Pipeline
   Narrower columns on small viewports so the next one stays
   partly visible and the sideways scroll is discoverable.
   ========================================================= */

@media (max-width: 768px) {
    .pipeline-column {
        flex-basis: 260px;
    }

    .pipeline-column-body {
        max-height: none;
    }
}


/* =========================================================
   Lead Enquiry Detail
   Shipment cards nested inside the detail page's own card,
   so they need a visible edge of their own to separate them.
   ========================================================= */

.enquiry-shipment-card {
    border: 1px solid var(--colorNeutralStroke2);
    background: var(--colorNeutralBackground2);
    padding: 14px;
}


/* =========================================================
   Lead enquiry dimension rows
   Each dimension is twelve fields on a 12-column grid, so it
   wraps onto two label rows. A hairline divider alone left
   consecutive dimensions reading as one continuous block, so
   each row gets its own inset card and an ordinal badge.
   ========================================================= */

.enquiry-dim-row {
    position: relative;
    width: 100%;
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium, 4px);
    background: var(--colorNeutralBackground1);
    padding: 14px 14px 14px 38px;
    margin-bottom: 10px;
}

    .enquiry-dim-row:last-of-type {
        margin-bottom: 0;
    }

/* Ordinal badge, pinned to the row's left gutter so the two
   wrapped field rows are visibly bracketed as one dimension. */
.enquiry-dim-ord {
    position: absolute;
    top: 12px;
    left: 10px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border-radius: 10px;
    background: var(--colorNeutralBackground4);
    color: var(--colorNeutralForeground3);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
}


/* =========================================================
   Main navigation: search, collapse toggle, icon rail
   The rail follows the portal pattern -- a search box over a
   filtered menu, and a toggle that reduces it to icons. The
   menu itself is data (NavMenu.cs), so search can filter it.
   ========================================================= */

.app-nav-head {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px 8px;
}

/* The toggle is the only child when collapsed, so it centres itself. */
.app-nav-collapsed .app-nav-head {
    justify-content: center;
}

/* ---------- Collapsed icon rail ----------
   Labels are hidden rather than removed so the items keep their
   tooltips, hrefs and active states; only the chrome narrows. */

.app-nav-collapsed {
    padding-left: 0;
    padding-right: 0;
}

    .app-nav-collapsed .fluent-navitem,
    .app-nav-collapsed .fluent-navcategoryitem {
        margin: 0;
        padding-left: 0;
        padding-right: 0;
        justify-content: center;
    }

    /* Hide the text of each item, keeping its icon. The label is the text node
       inside the anchor, so the anchor's own gap and padding go too. */
    .app-nav-collapsed .fluent-navitem span,
    .app-nav-collapsed .fluent-navcategoryitem span {
        display: none;
    }

    /* Sub-item groups have no room in a 56px rail, and their parent category
       cannot be expanded while collapsed anyway. */
    .app-nav-collapsed .fluent-navsubitemgroup {
        display: none;
    }

    /* The chevron is meaningless with the group hidden. */
    .app-nav-collapsed .fluent-navcategoryitem svg:last-child {
        display: none;
    }


/* =========================================================
   All modules catalogue
   Holds the full product -- built and planned -- so the nav
   rail itself only ever lists screens that open.
   ========================================================= */

.modules-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: 12px;
}

.modules-toolbar {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

.modules-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 12px;
    align-items: start;
}

.fluent-card.modules-card {
    padding: 12px 14px;
}

.modules-card-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 8px;
    margin-bottom: 8px;
    border-bottom: 1px solid var(--colorNeutralStroke2);
}

/* Built-vs-total, so the roadmap's progress is legible at a glance. */
.modules-count {
    margin-left: auto;
    font-size: 11px;
    color: var(--colorNeutralForeground3);
    white-space: nowrap;
}

.modules-links {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.modules-link {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 6px;
    border-radius: var(--borderRadiusMedium, 4px);
    font-size: 13px;
    text-decoration: none;
    color: var(--colorBrandForeground1);
}

    .modules-link:hover {
        background: var(--colorNeutralBackground2);
    }

/* Planned screens read as present but unavailable: quieter, not a link, and
   with no hover affordance suggesting they can be opened. */
.modules-link-soon {
    color: var(--colorNeutralForeground3);
    cursor: default;
}

    .modules-link-soon:hover {
        background: transparent;
    }

.modules-soon {
    margin-left: auto;
    padding: 1px 6px;
    border-radius: 9px;
    background: var(--colorNeutralBackground4);
    color: var(--colorNeutralForeground3);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .02em;
}

.modules-empty {
    padding: 40px 16px;
    text-align: center;
}


/* =========================================================
   Navigation rail -- portal treatment
   The rail renders its own rows rather than FluentNav, because
   each row carries a star and an overflow button alongside the
   link, and the two-level model swaps the whole list. Rows are
   square and full-bleed so hover and selection span the rail.
   ========================================================= */

.app-nav-list {
    display: flex;
    flex-direction: column;
    width: 100%;
}

/* One row: chevron slot, icon, label, then the trailing actions. */
.app-nav-row {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-height: 32px;
    padding: 0 8px;
    border: none;
    background: transparent;
    font: inherit;
    font-size: 13px;
    text-align: start;
    text-decoration: none;
    color: var(--colorNeutralForeground1);
    cursor: pointer;
    transition: background-color 100ms linear;
}

    .app-nav-row:hover {
        background: var(--colorNeutralBackground3);
    }

/* Keeps icons in one vertical line whether or not a row has a chevron. */
.app-nav-chevron-space {
    width: 16px;
    flex: 0 0 16px;
}

/* Trailing chevron marks a module row that opens a sub-menu, so it reads
   differently from a plain link (Home, Dashboard). Muted until hover/active. */
.app-nav-has-sub {
    flex: 0 0 16px;
    margin-left: auto;
    opacity: .45;
    transition: opacity .12s ease, transform .12s ease;
}

.app-nav-row-group:hover .app-nav-has-sub,
.app-nav-row-active .app-nav-has-sub {
    opacity: .9;
    transform: translateX(2px);
}

.app-nav-icon {
    flex: 0 0 auto;
}

.app-nav-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Selected page: one thin accent bar and a quiet fill. */
.app-nav-item.app-nav-row-active,
a.app-nav-row.app-nav-row-active {
    position: relative;
    background: var(--colorNeutralBackground3);
    font-weight: 600;
}

    .app-nav-item.app-nav-row-active::before,
    a.app-nav-row.app-nav-row-active::before {
        content: '';
        position: absolute;
        left: 0;
        top: 0;
        bottom: 0;
        width: 2px;
        background: var(--colorBrandBackground);
    }

/* ---------- Item rows with trailing actions ---------- */

.app-nav-item {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
}

    .app-nav-item .app-nav-row {
        flex: 1 1 auto;
        min-width: 0;
    }

    .app-nav-item:hover {
        background: var(--colorNeutralBackground3);
    }

/* Star and overflow stay in the DOM for keyboard and screen-reader access,
   and fade in on hover so the rail stays quiet at rest. A starred item keeps
   its star visible, since that is the state being communicated. */
.app-nav-star,
.app-nav-more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    margin-right: 2px;
    border: none;
    border-radius: var(--borderRadiusSmall, 2px);
    background: transparent;
    color: var(--colorNeutralForeground3);
    cursor: pointer;
    opacity: 0;
    transition: opacity 100ms linear, background-color 100ms linear;
}

.app-nav-item:hover .app-nav-star,
.app-nav-item:hover .app-nav-more,
.app-nav-star:focus-visible,
.app-nav-more:focus-visible,
.app-nav-star-on {
    opacity: 1;
}

    .app-nav-star:hover,
    .app-nav-more:hover {
        background: var(--colorNeutralBackground4);
    }

.app-nav-star-on {
    color: var(--colorBrandForeground1);
}

/* Overflow panel, anchored under its row. */
.app-nav-overflow {
    position: absolute;
    top: 100%;
    right: 4px;
    z-index: 20;
    display: flex;
    flex-direction: column;
    min-width: 180px;
    padding: 4px;
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium, 4px);
    background: var(--colorNeutralBackground1);
    box-shadow: var(--shadow16);
}

    .app-nav-overflow button {
        padding: 6px 10px;
        border: none;
        border-radius: var(--borderRadiusSmall, 2px);
        background: transparent;
        font: inherit;
        font-size: 13px;
        text-align: start;
        color: var(--colorNeutralForeground1);
        cursor: pointer;
    }

        .app-nav-overflow button:hover {
            background: var(--colorNeutralBackground3);
        }

/* ---------- Section headings and groups ---------- */

.app-nav-section {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 12px 8px 4px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .03em;
    text-transform: uppercase;
    color: var(--colorNeutralForeground3);
}

.app-nav-group .app-nav-chevron {
    flex: 0 0 16px;
}

/* Sub-items indent under their group; no border rail, which would sit beside
   the selection bar and read as a second indicator. */
.app-nav-sub .app-nav-row {
    padding-left: 32px;
}

/* ---------- Module level ---------- */

.app-nav-module {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 0 0 6px;
    border-bottom: 1px solid var(--colorNeutralStroke2);
    margin-bottom: 6px;
}

.app-nav-back {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    font-size: 12px;
    text-decoration: none;
    color: var(--colorNeutralForeground3);
}

    .app-nav-back:hover {
        color: var(--colorNeutralForeground1);
        background: var(--colorNeutralBackground3);
    }

.app-nav-module-title {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 2px 8px 4px;
    font-size: 14px;
    font-weight: 600;
    color: var(--colorNeutralForeground1);
}


/* =========================================================
   Two-pane navigation
   The global menu and the current module's menu sit side by
   side, not one replacing the other: the user can always see
   where they are in the app and jump elsewhere without first
   backing out of the module they are in.
   ========================================================= */

.app-nav-panes {
    display: flex;
    align-items: stretch;
    width: 100%;
    height: 100%;
    min-height: 100%;
}

    /* The global rail keeps its own width; the resource pane takes the rest.
       Neither pane scrolls on its own: the nav layout item is already a scroll
       container, and a nested one gave the rail a second scrollbar of its own. */
    .app-nav-panes > .app-nav {
        flex: 0 0 auto;
        width: 240px;
        overflow: visible;
    }

    .app-nav-panes > .app-nav.app-nav-collapsed {
        width: 56px;
    }

    /* The resource pane takes the remaining width when open, but holds a fixed
       strip when collapsed -- flex: 1 would stretch the 40px stub back out. */
    .app-nav-panes > .app-resnav.app-resnav-collapsed {
        flex: 0 0 40px;
    }

/* ---------- Resource (module) menu ---------- */

.app-resnav {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    overflow: visible;
    padding: 6px 0 8px;
    /* Separated from the global rail by a hairline, so the two read as
       distinct contexts rather than one long list. */
    border-left: 1px solid var(--colorNeutralStroke2);
    background: var(--colorNeutralBackground1);
}

.app-resnav-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 10px 8px;
}

.app-resnav-title {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.app-resnav-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--colorNeutralForeground1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Mirrors the portal's resource-type caption under the resource name. */
.app-resnav-kind {
    font-size: 11px;
    color: var(--colorNeutralForeground3);
}

.app-resnav-list {
    display: flex;
    flex-direction: column;
    width: 100%;
}

.app-resnav-row {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    min-height: 30px;
    padding: 0 10px;
    border: none;
    background: transparent;
    font: inherit;
    font-size: 13px;
    text-align: start;
    text-decoration: none;
    color: var(--colorNeutralForeground1);
    cursor: pointer;
    transition: background-color 100ms linear;
}

    .app-resnav-row:hover {
        background: var(--colorNeutralBackground3);
    }

/* Keeps labels aligned whether or not a row carries a chevron. */
.app-resnav-chevron-space {
    width: 16px;
    flex: 0 0 16px;
}

.app-resnav-chevron {
    flex: 0 0 16px;
}

.app-resnav-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Children of an expanded section indent under their heading. */
.app-resnav-child {
    padding-left: 32px;
}

.app-resnav-row-active {
    position: relative;
    background: var(--colorNeutralBackground3);
    font-weight: 600;
}

    .app-resnav-row-active::before {
        content: '';
        position: absolute;
        left: 0;
        top: 0;
        bottom: 0;
        width: 2px;
        background: var(--colorBrandBackground);
    }

/* Below the Fluent nav breakpoint the two panes cannot both fit; the resource
   menu drops away and its screens stay reachable from the global rail. The
   layout item's width is set inline in C# and reserves room for both panes, so
   the pane container is capped here to hand that space back to the content. */
@media (max-width: 900px) {
    .app-resnav {
        display: none;
    }

    .app-nav-panes {
        max-width: 240px;
    }

    .fluent-layout-item[area="nav"] {
        max-width: 240px;
    }
}


/* =========================================================
   Resource menu collapse toggle
   Sits on the divider between the global and resource menus,
   so it reads as the control for the pane it opens and closes.
   Collapsing the resource menu is independent of the global
   rail: a user who knows one module often wants its menu out
   of the way while keeping the global one in view.
   ========================================================= */

.app-resnav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    /* Pushed to the trailing edge so it lands on the divider, except when the
       pane is collapsed and it is the only thing in the header. */
    margin-left: auto;
    width: 24px;
    height: 24px;
    border: none;
    border-radius: var(--borderRadiusSmall, 2px);
    background: transparent;
    color: var(--colorNeutralForeground3);
    cursor: pointer;
    transition: background-color 100ms linear;
}

    .app-resnav-toggle:hover {
        background: var(--colorNeutralBackground3);
        color: var(--colorNeutralForeground1);
    }

/* ---------- Collapsed resource pane ---------- */

.app-resnav-collapsed {
    width: 40px;
    flex: 0 0 40px;
    align-items: center;
}

    .app-resnav-collapsed .app-resnav-head {
        padding: 4px 0 6px;
        justify-content: center;
    }

    .app-resnav-collapsed .app-resnav-toggle {
        margin-left: 0;
    }

/* The module icon alone, so the collapsed strip still says which module is
   open rather than reading as an empty gutter. */
.app-resnav-stub {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 6px 0;
}


/* =========================================================
   Resource menu standing alone
   Inside a module the expanded resource menu replaces the
   global rail rather than sitting beside it -- two menus in
   view at once competed for the same glance. It therefore
   carries its own route back to the main menu, and takes the
   full rail width instead of the remainder beside it.
   ========================================================= */

.app-nav-panes > .app-resnav {
    border-left: none;
}

.app-resnav-home {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    margin-left: auto;
    width: 24px;
    height: 24px;
    border-radius: var(--borderRadiusSmall, 2px);
    color: var(--colorNeutralForeground3);
    text-decoration: none;
    transition: background-color 100ms linear;
}

    .app-resnav-home:hover {
        background: var(--colorNeutralBackground3);
        color: var(--colorNeutralForeground1);
    }

/* With the home link taking the trailing edge, the collapse toggle follows it
   rather than pushing itself right. */
.app-resnav-home + .app-resnav-toggle {
    margin-left: 2px;
}


/* =========================================================
   Navigation transitions
   The rail's width is set inline from C# and the panes swap
   as the route enters or leaves a module, so without this the
   whole shell jumps between states. Width is the one property
   animated on the layout item -- the content beside it is laid
   out against that width, so easing it moves the page with the
   rail instead of snapping it.

   Reduced-motion is already honoured by the global rule in the
   accessibility section, which zeroes every transition and
   animation, so it is not repeated here.
   ========================================================= */

.fluent-layout-item[area="nav"] {
    transition: width 180ms cubic-bezier(0.33, 0, 0.67, 1);
}

.app-nav-panes > .app-nav,
.app-nav-panes > .app-resnav {
    transition: width 180ms cubic-bezier(0.33, 0, 0.67, 1),
                flex-basis 180ms cubic-bezier(0.33, 0, 0.67, 1);
}

/* Labels are added and removed from the DOM by the components rather than
   hidden, so they cannot be faded from here; the width easing is what carries
   the movement.

   The menu that appears when a module opens eases in, so the swap reads as one
   movement rather than a new panel appearing over a resizing rail. */
.app-resnav {
    animation: app-nav-fade-in 180ms ease-out;
}

@keyframes app-nav-fade-in {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}


/* =========================================================
   Quotation print screen
   The document markup itself is generated by
   QuotationDocument (Shared/Documents) and carries its own
   styles, so the rules here only cover the screen-side host:
   the command bar and the paper-like sheet it sits on.
   Printing must show the document alone, so the app shell is
   dropped from the printed page rather than merely hidden.
   ========================================================= */

/* The document viewer: a toolbar over a tinted ground, so the white sheet
   below reads as a page rather than as the page background. */
.doc-viewer {
    min-height: 100vh;
    background: var(--colorNeutralBackground3, #f0f0f0);
    padding-bottom: 24px;
}

.doc-toolbar {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    flex-wrap: wrap;
    gap: 8px 16px;
    align-items: center;
    padding: 12px 16px;
    margin-bottom: 20px;
    background: var(--colorNeutralBackground1);
    border-bottom: 1px solid var(--colorNeutralStroke2);
}

.doc-toolbar-main {
    display: flex;
    gap: 8px;
    align-items: center;
}

/* Names the two print-dialog settings the page itself cannot set. */
.doc-toolbar-hint {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}

/* A4 landscape, shown at its true width so the preview is the page: 297mm of
   paper less the 10mm side margins the @page rule applies at print time. */
.doc-sheet {
    background: #ffffff;
    color: #1a1a1a;
    margin: 0 auto 24px;
    padding: 12mm 10mm 16mm;
    width: 297mm;
    max-width: 100%;
    min-height: 210mm;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.16);
}

@media print {
    /* The print page is hosted inside the app shell; at print time the
       document must be the only thing on the paper. */
    .doc-toolbar,
    .app-header,
    .fluent-layout-item[area="nav"] {
        display: none !important;
    }

    /* The viewer's tinted ground is screen furniture; the paper supplies its
       own white, and @page supplies the margins. */
    .doc-viewer {
        min-height: 0;
        padding: 0;
        background: none;
    }

    .doc-sheet {
        margin: 0;
        padding: 0;
        width: auto;
        max-width: none;
        min-height: 0;
        box-shadow: none;
    }
}


/* =========================================================
   Generate Quote dialog
   The print options. The dialog draws its own title and
   actions because the Fluent version in use does not render
   the header and footer supplied through DialogOptions.

   Laid out as a column: a fixed header and footer with the
   options scrolling between them, so a long option set never
   pushes the actions off the screen and the dialog keeps a
   steady height instead of stretching to the viewport edges.
   ========================================================= */

/* Sized in viewport units on this element rather than inherited from the dialog
   frame. The frame is the Fluent dialog's shadow `::part(dialog)`, and the rule
   below that sizes it does not reliably apply -- when it does not, an auto-height
   chain leaves this column unbounded, so the body grows past the footer instead
   of scrolling and the frame keeps its own fixed height with dead space beneath.
   Giving this element a definite height makes the whole layout self-contained:
   the header and footer take what they need and the body gets the remainder. */
.quote-dialog {
    display: flex;
    flex-direction: column;
    min-height: 0;
    height: min(680px, 86vh);
    max-height: 86vh;
    box-sizing: border-box;
}

.quote-dialog-header {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
    padding: 0 0 10px;
    border-bottom: 1px solid var(--colorNeutralStroke2);
    color: var(--colorBrandForeground1);
}

.quote-dialog-header h2 {
    margin: 0;
    font-size: var(--fontSizeBase400);
    font-weight: 600;
    line-height: 1.2;
    color: var(--colorNeutralForeground1);
}

/* The options scroll rather than the dialog growing. The side padding keeps
   the group borders clear of both the scrollbar and the dialog frame.

   `flex: 1 1 auto` with `min-height: 0` takes whatever the header and footer
   leave, which works because .quote-dialog above has a definite height. The
   min-height override is what actually allows shrinking below the content size
   and so makes this a scrollport rather than letting it grow. */
.quote-dialog-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 12px 2px 12px 0;
}

/* Full width and on its own row (see the comment above .quote-row-3), so the
   exclusion sits beside the rate sentences rather than below them -- that
   keeps the group to the height of its three radio lines instead of adding a
   fourth for the checkbox. */
.quote-group-rate {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);
    gap: 0 24px;
    align-items: start;
}

.quote-group-rate h3 {
    grid-column: 1 / -1;
}

/* Two groups side by side, collapsing to one on a narrow dialog. */
.quote-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    align-items: stretch;
}

/* The three column-switch cards on the last row. */
.quote-row-3 {
    grid-template-columns: repeat(3, 1fr);
}

@media (max-width: 900px) {
    /* Two across, and the odd card out takes the full width rather than
       leaving a half-empty cell beside it. */
    .quote-row-3 {
        grid-template-columns: 1fr 1fr;
    }

    .quote-row-3 > :last-child {
        grid-column: 1 / -1;
    }
}

@media (max-width: 560px) {
    .quote-row {
        grid-template-columns: 1fr;
    }
}

.quote-field label {
    display: block;
    margin-bottom: 4px;
    font-size: var(--fontSizeBase200);
    font-weight: 600;
    color: var(--colorNeutralForeground2);
}

/* Each group is a bordered block so its options read as belonging to the
   heading above them rather than to the column alongside. */
.quote-group {
    padding: 10px 12px;
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: 8px;
    background: var(--colorNeutralBackground2);
}

.quote-group h3 {
    margin: 0 0 6px;
    font-size: var(--fontSizeBase100);
    font-weight: 600;
    color: var(--colorNeutralForeground3);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* A second heading inside a group that holds two related sets. */
.quote-group h3.quote-subhead {
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--colorNeutralStroke3);
}

/* Each checkbox takes its own line. Left to flow inline they wrap between
   the box and its label, which reads as the wrong option being ticked. */
.quote-group fluent-checkbox {
    display: flex;
    margin-bottom: 2px;
}

/* Two boxes to a line, so a four-option card is two lines tall rather than
   four. The grid supplies the columns; each box still fills its own cell, so
   a long label wraps under itself instead of beside the next option. */
.quote-checks-2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2px 16px;
    align-items: start;
}

.quote-checks-2 fluent-checkbox {
    margin-bottom: 0;
    min-width: 0;
}

/* One per line instead. The Layout card's labels are whole phrases rather
   than the two or three words the location and carrier boxes carry, so
   pairing them wraps every one of them across two lines -- which is taller
   than simply stacking them, as well as harder to read. */
.quote-checks-1 {
    grid-template-columns: minmax(0, 1fr);
}

.quote-group fluent-checkbox:last-child {
    margin-bottom: 0;
}

/* Set apart from the rate options beside it, because excluding per-shipment
   charges is a separate decision from how the rate is composed. */
/* Set apart from the rate options beside it, because excluding per-shipment
   charges is a separate decision from how the rate is composed. */
.quote-group-extra {
    padding-left: 24px;
    border-left: 1px solid var(--colorNeutralStroke3);
}

.quote-dialog-footer {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
    padding: 10px 0 0;
    border-top: 1px solid var(--colorNeutralStroke2);
}

/* The dialog frame itself, not just its contents. The Fluent dialog stretches
   its shadow `dialog` part to the full viewport height for an auto-height
   dialog, which pins the contents to the top edge and leaves dead space under
   the footer. Sizing the part to its content re-centres the whole frame.

   Both the host element and its shadow part are targeted. The ::part() selector
   alone proved unreliable here -- when it does not apply, the frame keeps its
   stretched height and the dead space comes back -- so the host carries the same
   sizing, which does not depend on the shadow boundary. .quote-dialog inside
   carries a definite height of its own, so the layout is correct either way. */
fluent-dialog:has(.quote-dialog),
fluent-dialog:has(.quote-dialog)::part(dialog) {
    height: auto;
    max-height: min(680px, 86vh);
    margin: auto;
    border-radius: 8px;
    overflow: hidden;
}

fluent-dialog:has(.quote-dialog)::part(dialog) {
    padding: 16px 20px;
}

.quote-hint {
    margin: 6px 0 0;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}


/* =========================================================
   Rate Management — charge cards
   All charge types (Freight, Origin, Destination, Surcharge,
   Customs, Documentation, Other) live in one flat list of
   numbered/bordered cards, reusing the lead-enquiry dimension
   row pattern (.enquiry-dim-row); only the badge is widened
   here since it now shows the charge type name, not an
   ordinal, and needs room for longer labels.
   ========================================================= */

/* The charge type name can run much longer than a 2-digit ordinal, so this
   card variant drops the absolute-positioned badge (which overlapped the
   first field's own label) in favor of a normal-flow badge on its own line
   above the grid, with ordinary (non-lopsided) card padding. */
.rate-charge-row {
    padding: 14px;
    border-color: var(--colorNeutralStroke1);
    background: var(--colorNeutralBackground2);
}

.rate-charge-row .rate-charge-type-badge {
    position: static;
    display: inline-flex;
    max-width: 100%;
    height: auto;
    margin-bottom: 8px;
    padding: 3px 8px;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    /* .enquiry-dim-ord's muted tertiary gray (meant for a 2-digit ordinal) is
       too low-contrast for the full charge/container label this badge shows
       here - override with a stronger, on-brand color. */
    color: var(--colorBrandForeground1, var(--colorNeutralForeground1));
    background: var(--colorBrandBackground2, var(--colorNeutralBackground4));
    font-weight: 600;
}

/* Labels like "Calculation (Per shipment)" wrap to two lines while sibling
   labels in the same row ("Container type", "Buying", ...) stay on one, which
   pushes that field's input out of vertical alignment with the rest of the
   row. Reserve two lines of label height for every field in a charge card so
   inputs line up regardless of which labels wrap. */
.rate-charge-row fluent-field > label {
    min-height: 2lh;
}

.rate-charge-cell-input {
    width: 100%;
    min-width: 64px;
    box-sizing: border-box;
    font: inherit;
    padding: 3px 6px;
    border: 1px solid var(--colorNeutralStroke1);
    border-radius: 4px;
    background: var(--colorNeutralBackground1);
    color: var(--colorNeutralForeground1);
}

    .rate-charge-cell-input::placeholder {
        color: var(--colorNeutralForeground3);
    }

    .rate-charge-cell-input:disabled {
        color: var(--colorNeutralForeground3);
        background: var(--colorNeutralBackground2);
    }

.rate-break-table-wrap {
    max-width: 420px;
}

.rate-break-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
    margin-top: 4px;
}

    .rate-break-table th {
        text-align: left;
        color: var(--colorNeutralForeground3);
        font-weight: 600;
        padding: 2px 6px;
    }

    .rate-break-table td {
        padding: 2px 6px;
    }


/* =========================================================
   Quotation Charges
   A dense charge table grouped by charge scope. Fluent UI has
   no table primitive that renders a compact, right-aligned
   money grid inside a card, so a plain table is styled with
   Fluent design tokens to stay consistent with the theme.
   ========================================================= */

.qc-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fontSizeBase300);
}

.qc-table th {
    text-align: left;
    padding: 6px 8px;
    border-bottom: 1px solid var(--colorNeutralStroke2);
    color: var(--colorNeutralForeground3);
    font-weight: var(--fontWeightSemibold);
}

.qc-table td {
    padding: 6px 8px;
    border-bottom: 1px solid var(--colorNeutralStroke3);
    vertical-align: middle;
}

.qc-table .qc-num {
    text-align: right;
    white-space: nowrap;
}

.qc-table .qc-actions {
    text-align: right;
    white-space: nowrap;
}

/* An excluded optional charge stays visible but is visibly not payable. */
.qc-table tr.qc-excluded td {
    color: var(--colorNeutralForegroundDisabled);
    text-decoration: line-through;
}

/* The charge grid reads as a child of the rate reference above it, so it is
   indented and given a left rule rather than sitting flush in the card. */
.qc-table-child {
    border-left: 2px solid var(--colorNeutralStroke2);
    padding-left: 10px;
}

.qc-table-child th,
.qc-table-child td {
    padding: 6px 10px;
}

/* A charge grid has many narrow columns -- movement type through amount -- which
   together exceed the dialog width, so it scrolls sideways instead of crushing
   every column.

   The scroll sits on a wrapper rather than on the table. Making the table itself
   `display: block` leaves it subject to the `width: 100%` above, which caps it at
   the container and squeezes the columns rather than overflowing, so the scroll
   container never has anything to scroll. As a table inside a block wrapper it
   sizes to its content and the wrapper does the scrolling. */
/* Horizontal scroll for the charge grid, which is wider than the dialog.

   CSS does not allow one axis to scroll while the other stays `visible`: setting
   overflow-x alone forces the computed overflow-y to `auto` as well. That made
   this wrapper its own vertical scrollport, clamped to whatever height the flex
   layout handed it -- 78px, about one row of a 61-row table -- with the rest
   unreachable, because the dialog body above it then had nothing left to scroll.

   `flex: 0 0 auto` is what stops that: as a flex item in the .qc-children column
   it would otherwise be stretched or shrunk to the container, whereas sizing to
   its own content means its scrollHeight never exceeds its height and the
   vertical scroll falls through to the dialog body where it belongs. */
.qc-table-scroll {
    overflow-x: auto;
    max-width: 100%;
    flex: 0 0 auto;
    /* Its parents are flex columns, where a flex item's min-width defaults to
       `auto` -- the item refuses to shrink below its content, so the wrapper
       stretches to the table's full width and never overflows. Explicitly
       zeroing it lets the wrapper narrow and the scrollbar appear. */
    min-width: 0;
}

.qc-table-scroll > .qc-table-child {
    width: auto;
    min-width: 100%;
    white-space: nowrap;
}


/* =========================================================
   Get charges dialog - scrolling charge rows
   In the dialog the charge rows scroll within their own
   region rather than scrolling the whole dialog body, so the
   rate reference, the match criteria and the card picker stay
   in view while a long charge list is reviewed.

   Scoped to .quote-dialog: on the Charges tab these same
   classes must keep sizing to their content, because there it
   is the page that scrolls.
   ========================================================= */

/* The body no longer scrolls: it hands its height to the master block below,
   which is what contains the scrolling region. */
.quote-dialog .quote-dialog-body {
    overflow-y: hidden;
}

/* The master block becomes the column that owns the leftover height. Overriding
   the `flex: 0 0 auto` it carries for the Charges tab is what lets it shrink to
   the dialog instead of growing to its rows. */
.quote-dialog .qc-master {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* The rate reference row stays put at the top of that block. */
.quote-dialog .qc-master-row {
    flex: 0 0 auto;
}

.quote-dialog .qc-children {
    flex: 1 1 auto;
    min-height: 0;
}

/* Fluent renders a wrapping stack between .qc-children and the table, so it has
   to pass the height through as well or the scrollport never forms. */
.quote-dialog .qc-children > .fluent-stack-vertical {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* The scrollport itself: vertical for the rows, horizontal for the columns. */
.quote-dialog .qc-table-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Keeps the column headers visible while the rows scroll beneath them. The
   border is drawn as a box-shadow because a sticky cell's own border does not
   travel with it, which would let the first row appear to run into the header. */
.quote-dialog .qc-table-child thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--colorNeutralBackground1);
    border-bottom: none;
    box-shadow: inset 0 -1px 0 var(--colorNeutralStroke2);
}

/* The totals row stays pinned to the bottom edge of the same scrollport. */
.quote-dialog .qc-table-child tfoot td {
    position: sticky;
    bottom: 0;
    z-index: 2;
    background: var(--colorNeutralBackground1);
    box-shadow: inset 0 1px 0 var(--colorNeutralStroke1);
}

/* The totals row of the pricing breakdown. */
.qc-pricing-table tfoot td {
    border-top: 1px solid var(--colorNeutralStroke1);
    border-bottom: none;
    font-weight: var(--fontWeightSemibold);
}


/* =========================================================
   Quotation Rate Reference
   The route-and-carrier master row a charge grid hangs off,
   shown on the Charges tab and in the "Get charges" dialog.
   Fluent has no master-detail grid or description-list
   primitive, so a button + <dl> are styled with Fluent tokens.
   ========================================================= */

.qc-master {
    width: 100%;
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: 4px;
    /* A flex item of the dialog body / charges column; see .qc-table-scroll. */
    min-width: 0;
    /* As a flex item this would be shrunk to the container's height while its
       charge rows stay full size -- 437px around 2427px of content. Paired with
       the `overflow: hidden` this used to carry (for the rounded corners), that
       silently clipped every row past the first few and left the dialog body
       with nothing to scroll. Sizing to content instead pushes the overflow up
       to the body, which is the element that actually scrolls. */
    flex: 0 0 auto;
}

/* The master row is a button so it is keyboard-reachable and announces its
   expanded state, but it has to read as a table row, not a control. */
.qc-master-row {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 10px 12px;
    border: none;
    /* Rounded here rather than by clipping on .qc-master: that element must not
       establish an overflow context, or it hides the charge rows instead of
       letting the dialog body scroll them. */
    border-radius: 4px 4px 0 0;
    background: var(--colorNeutralBackground2);
    color: var(--colorNeutralForeground1);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.qc-master-row:hover {
    background: var(--colorNeutralBackground3);
}

.qc-master-row:focus-visible {
    outline: 2px solid var(--colorBrandStroke1);
    outline-offset: -2px;
}

.qc-master-ref {
    font-weight: var(--fontWeightSemibold);
    white-space: nowrap;
}

/* Child rows are inset from the master row that owns them. */
.qc-children {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 10px 12px;
    border-top: 1px solid var(--colorNeutralStroke2);
    background: var(--colorNeutralBackground1);
    /* See .qc-table-scroll: the default `auto` would stop the scroll wrapper
       inside from ever narrowing below the table's width. */
    min-width: 0;
}


.qc-ref-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 24px;
    margin: 0;
}

/* The rate-match criteria toggles, shown on the Charges tab and in the lookup
   dialog. They wrap freely so a narrow viewport stacks them rather than
   overflowing the card. */
.rate-criteria {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    align-items: center;
}

.rate-criteria-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 12px;
    width: 100%;
}

.qc-ref-grid dt {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}

.qc-ref-grid dd {
    margin: 0;
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground1);
}

/* ==========================================================
   Export operations detail pages (Booking, Job, ...) —
   read-only field grid and timeline, shared across modules.
   ========================================================== */

.ops-fields {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px 16px;
}

.ops-fields > div {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.ops-fields label {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}

.ops-fields span {
    color: var(--colorNeutralForeground1);
    overflow-wrap: anywhere;
}

.ops-fields .ops-wide {
    grid-column: 1 / -1;
}

@media (max-width: 1100px) {
    .ops-fields {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 600px) {
    .ops-fields {
        grid-template-columns: minmax(0, 1fr);
    }
}

.ops-timeline {
    list-style: none;
    margin: 0;
    padding: 0 0 0 14px;
    border-left: 2px solid var(--colorNeutralStroke2);
}

.ops-timeline li {
    position: relative;
    padding: 0 0 14px 12px;
}

.ops-timeline li::before {
    content: "";
    position: absolute;
    left: -21px;
    top: 4px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--colorBrandBackground);
    box-shadow: 0 0 0 3px var(--colorNeutralBackground1);
}

.ops-tl-head {
    display: flex;
    justify-content: space-between;
    gap: 12px;
}

.ops-tl-head span,
.ops-tl-sub {
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase200);
}

.ops-tl-note {
    margin-top: 4px;
    white-space: pre-wrap;
}

.list-cell-danger {
    color: var(--colorPaletteRedForeground1);
    font-weight: 600;
}

/* Stage strip on the Job workspace: one chip per operational stage. */
.ops-stages {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.ops-stage {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: var(--borderRadiusCircular);
    border: 1px solid var(--colorNeutralStroke2);
    background: var(--colorNeutralBackground2);
    color: var(--colorNeutralForeground2);
    font-size: var(--fontSizeBase200);
}

.ops-stage.done {
    border-color: var(--colorPaletteGreenBorder2);
    background: var(--colorPaletteGreenBackground1);
    color: var(--colorPaletteGreenForeground1);
}

.ops-stage.current {
    border-color: var(--colorBrandStroke1);
    background: var(--colorBrandBackground2);
    color: var(--colorBrandForeground1);
    font-weight: 600;
}

.ops-kpis {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
}

.ops-kpi {
    padding: 12px;
    border-radius: var(--borderRadiusMedium);
    background: var(--colorNeutralBackground2);
}

.ops-kpi label {
    display: block;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}

.ops-kpi strong {
    font-size: var(--fontSizeBase500);
}

@media (max-width: 1100px) {
    .ops-kpis {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* Section card headings inside the rail layout's main pane (Booking, Job,
   ...): sized as card titles, not page headings. */
.qf-main .fluent-card h3,
.qf-main fluent-card h3 {
    font-size: var(--fontSizeBase400);
    line-height: var(--lineHeightBase400);
    font-weight: var(--fontWeightSemibold);
    margin: 0 0 12px;
}

/* Section tabs inside a rail section (same look as the Quotation tabs). */
.quotation-tabs fluent-tab[aria-selected="true"] fluent-text {
    color: var(--colorBrandForeground1);
    font-weight: 600;
}

.quotation-tabs fluent-tab:not([aria-selected="true"]) fluent-text {
    color: var(--colorNeutralForeground3);
    font-weight: 500;
}

/* Section status badge sits at the right of its rail button. */
.qf-nav-item .qf-nav-dot {
    margin-left: auto;
}

/* Tracking line: the job's milestones in order, reached ones filled. */
.ops-tracking {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px 16px;
}

.ops-tracking li {
    display: grid;
    grid-template-columns: 14px 1fr;
    column-gap: 8px;
    align-items: baseline;
    color: var(--colorNeutralForeground3);
}

.ops-tracking .ops-track-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 2px solid var(--colorNeutralStroke1);
    align-self: center;
}

.ops-tracking li.reached {
    color: var(--colorNeutralForeground1);
}

.ops-tracking li.reached .ops-track-dot {
    background: var(--colorPaletteGreenBackground3);
    border-color: var(--colorPaletteGreenBackground3);
}

.ops-tracking li.na {
    opacity: 0.55;
}

.ops-tracking .ops-track-date {
    grid-column: 2;
    font-size: var(--fontSizeBase200);
}

@media (max-width: 1100px) {
    .ops-tracking {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 600px) {
    .ops-tracking {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* Action buttons stacked in the summary card of a rail page. */
.ops-action-stack {
    width: 100%;
    margin-top: 10px;
}

.ops-action-stack fluent-button,
.ops-action-stack .list-action-danger {
    width: 100%;
    display: block;
}

/* ---------- Container allocation card ---------- */

/* Thin vertical rule between the primary actions and the utility icons. */
.app-row-actions-sep {
    width: 1px;
    align-self: stretch;
    margin: 2px 4px;
    background: var(--colorNeutralStroke2);
}

/* Stuffing / VGM facets sit below the action row, set off by a top rule so
   they read as sub-records of the container rather than loose text. */
.app-alloc-subrow {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--colorNeutralStroke2);
}

.app-alloc-facet {
    min-width: 220px;
}

.app-alloc-facet-label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--colorNeutralForeground3);
    margin-bottom: 2px;
}

/* Shown by MainLayout instead of an add/edit screen the user cannot write to. */
.app-readonly-notice {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    max-width: 720px;
    padding: 24px 0;
}

/* Heading inside a dashboard / report card. */
.card-title {
    margin: 0 0 12px 0;
    font-size: var(--fontSizeBase400);
    font-weight: 600;
    line-height: var(--lineHeightBase400);
}
