/* ══ Material Design 3 system tokens ═══════════════════════════════
   The app's design language is Material 3 (m3.material.io). Everything
   below the md-sys block maps onto these roles — feature files keep using
   the older role names (--teal, --border, --gray-mid…) and pick the M3
   values up through the aliases further down.

   Colour: an M3 tonal-spot scheme generated (material-color-utilities)
   from the brand teal #009999. Primary is pinned to tone 45 of that
   palette (#007777): the closest tone to the brand that still gives
   on-primary text the 4.5:1 contrast M3's colour pairs guarantee. */
:root {
  --md-sys-color-primary: #007777;
  --md-sys-color-on-primary: #ffffff;
  --md-sys-color-primary-container: #9cf1f0;
  --md-sys-color-on-primary-container: #004f4f;
  --md-sys-color-secondary: #4a6363;
  --md-sys-color-on-secondary: #ffffff;
  --md-sys-color-secondary-container: #cce8e7;
  --md-sys-color-on-secondary-container: #324b4b;
  --md-sys-color-tertiary: #4b607c;
  --md-sys-color-on-tertiary: #ffffff;
  --md-sys-color-tertiary-container: #d3e4ff;
  --md-sys-color-on-tertiary-container: #334863;
  /* The error roles keep the app's pre-M3 reds (--danger, -bg, -dark), so
     the app has one red, not M3's #ba1a1a beside the old one. */
  --md-sys-color-error: #c0392b;
  --md-sys-color-on-error: #ffffff;
  --md-sys-color-error-container: #fdecea;
  --md-sys-color-on-error-container: #a93226;
  --md-sys-color-surface: #f4fbfa;
  --md-sys-color-surface-dim: #d5dbda;
  --md-sys-color-surface-bright: #f4fbfa;
  --md-sys-color-surface-container-lowest: #ffffff;
  --md-sys-color-surface-container-low: #eff5f4;
  --md-sys-color-surface-container: #e9efee;
  --md-sys-color-surface-container-high: #e3e9e9;
  --md-sys-color-surface-container-highest: #dde4e3;
  --md-sys-color-on-surface: #161d1d;
  --md-sys-color-on-surface-variant: #3f4948;
  --md-sys-color-outline: #6f7979;
  --md-sys-color-outline-variant: #bec9c8;
  --md-sys-color-inverse-surface: #2b3231;
  --md-sys-color-inverse-on-surface: #ecf2f1;
  --md-sys-color-inverse-primary: #80d5d4;
  --md-sys-color-scrim: #000000;
  --md-sys-color-shadow: #000000;

  /* Shape — the M3 corner-radius scale. */
  --md-sys-shape-corner-none: 0;
  --md-sys-shape-corner-extra-small: 4px;
  --md-sys-shape-corner-small: 8px;
  --md-sys-shape-corner-medium: 12px;
  --md-sys-shape-corner-large: 16px;
  --md-sys-shape-corner-large-increased: 20px;
  --md-sys-shape-corner-extra-large: 28px;
  --md-sys-shape-corner-full: 9999px;

  /* Elevation — the six M3 levels as key + ambient shadow pairs. Resting
     surfaces sit on 0–3; 4–5 are for hover/drag lifts. */
  --md-sys-elevation-level0: none;
  --md-sys-elevation-level1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
  --md-sys-elevation-level2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
  --md-sys-elevation-level3: 0 1px 3px 0 rgb(0 0 0 / 0.3), 0 4px 8px 3px rgb(0 0 0 / 0.15);
  --md-sys-elevation-level4: 0 2px 3px 0 rgb(0 0 0 / 0.3), 0 6px 10px 4px rgb(0 0 0 / 0.15);
  --md-sys-elevation-level5: 0 4px 4px 0 rgb(0 0 0 / 0.3), 0 8px 12px 6px rgb(0 0 0 / 0.15);

  /* State layers — opacity of the content colour laid over a container. */
  --md-sys-state-hover-opacity: 8%;
  --md-sys-state-focus-opacity: 10%;
  --md-sys-state-pressed-opacity: 10%;
  --md-sys-state-dragged-opacity: 16%;

  /* Motion — easing and duration tokens. */
  --md-sys-motion-easing-standard: cubic-bezier(0.2, 0, 0, 1);
  --md-sys-motion-easing-standard-decelerate: cubic-bezier(0, 0, 0, 1);
  --md-sys-motion-easing-standard-accelerate: cubic-bezier(0.3, 0, 1, 1);
  --md-sys-motion-easing-emphasized-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1);
  --md-sys-motion-easing-emphasized-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15);
  --md-sys-motion-duration-short2: 100ms;
  --md-sys-motion-duration-short4: 200ms;
  --md-sys-motion-duration-medium2: 300ms;
  --md-sys-motion-duration-medium4: 400ms;

  /* Type — the M3 baseline type scale as `font` shorthands (weight
     size/line-height family) plus each style's tracking. `font` doesn't
     reset letter-spacing, so a rule setting a style sets its tracking too,
     or it inherits body-medium's. */
  --md-ref-typeface-brand: "Roboto Flex", Roboto, system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --md-ref-typeface-plain: "Roboto Flex", Roboto, system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --md-sys-typescale-display-small: 400 2.25rem/2.75rem var(--md-ref-typeface-brand);
  --md-sys-typescale-display-small-tracking: 0;
  --md-sys-typescale-headline-large: 400 2rem/2.5rem var(--md-ref-typeface-brand);
  --md-sys-typescale-headline-large-tracking: 0;
  --md-sys-typescale-headline-medium: 400 1.75rem/2.25rem var(--md-ref-typeface-brand);
  --md-sys-typescale-headline-medium-tracking: 0;
  --md-sys-typescale-headline-small: 400 1.5rem/2rem var(--md-ref-typeface-brand);
  --md-sys-typescale-headline-small-tracking: 0;
  --md-sys-typescale-title-large: 400 1.375rem/1.75rem var(--md-ref-typeface-brand);
  --md-sys-typescale-title-large-tracking: 0;
  --md-sys-typescale-title-medium: 500 1rem/1.5rem var(--md-ref-typeface-plain);
  --md-sys-typescale-title-medium-tracking: 0.009375rem;
  --md-sys-typescale-title-small: 500 0.875rem/1.25rem var(--md-ref-typeface-plain);
  --md-sys-typescale-title-small-tracking: 0.00625rem;
  --md-sys-typescale-body-large: 400 1rem/1.5rem var(--md-ref-typeface-plain);
  --md-sys-typescale-body-large-tracking: 0.03125rem;
  --md-sys-typescale-body-medium: 400 0.875rem/1.25rem var(--md-ref-typeface-plain);
  --md-sys-typescale-body-medium-tracking: 0.015625rem;
  --md-sys-typescale-body-small: 400 0.75rem/1rem var(--md-ref-typeface-plain);
  --md-sys-typescale-body-small-tracking: 0.025rem;
  --md-sys-typescale-label-large: 500 0.875rem/1.25rem var(--md-ref-typeface-plain);
  --md-sys-typescale-label-large-tracking: 0.00625rem;
  --md-sys-typescale-label-medium: 500 0.75rem/1rem var(--md-ref-typeface-plain);
  --md-sys-typescale-label-medium-tracking: 0.03125rem;
  --md-sys-typescale-label-small: 500 0.6875rem/1rem var(--md-ref-typeface-plain);
  --md-sys-typescale-label-small-tracking: 0.03125rem;

  /* Keyboard focus indicator (M3 web): a 3px secondary ring, 2px out. */
  --md-focus-ring: 3px solid var(--md-sys-color-secondary);
  --md-focus-ring-offset: 2px;
}

/* ══ App role tokens, mapped onto M3 ═══════════════════════════════
   The names the feature stylesheets use. Each points at an M3 role (or, for
   status colours, at a custom-colour tonal palette: the hue at tone 40 for
   text, tone 90 for its container, tone 80 for a container outline). */
:root {
  --teal: var(--md-sys-color-primary);
  --teal-dark: var(--md-sys-color-on-primary-container);
  --teal-light: var(--md-sys-color-secondary-container);
  --teal-tint: rgb(0 119 119 / 0.35);  /* translucent primary: "committed but not spent" bar segments */
  --gray-dark: var(--md-sys-color-on-surface);
  --gray-mid: var(--md-sys-color-on-surface-variant);
  --gray-light: var(--md-sys-color-surface-container-high);
  --white: var(--md-sys-color-surface-container-lowest);

  /* Neutral borders & surfaces, named by role. */
  --border: var(--md-sys-color-outline-variant);        /* hairlines: cards, panels, popovers */
  --border-input: var(--md-sys-color-outline);          /* text-field outline */
  --divider: var(--md-sys-color-surface-container-highest); /* rules between list items / table rows */
  --surface-muted: var(--md-sys-color-surface-container-low); /* muted fill: table headers, hover rows */

  --text-strong: var(--md-sys-color-on-surface);
  --text-faint: var(--md-sys-color-outline);

  /* Height of the sticky top app bar — sticky table headers offset by this
     so they pin just below it instead of sliding under it. */
  --nav-height: 64px;

  /* Semantic status colours. The reds (via the error roles above), the
     warning amber-orange and the review orange are the app's pre-M3 values:
     the M3 tones put orange and red at the same lightness and they read as
     one colour. Text on a warning fill is --warning-dark; --warning itself
     (3.2:1 on white) is for icons and bars only. */
  --danger: var(--md-sys-color-error);
  --danger-dark: var(--md-sys-color-on-error-container);
  --danger-bg: var(--md-sys-color-error-container);
  --danger-border: #f5c0c0;
  --success: #006d30;
  --success-dark: #005323;
  --warning: #d97706;
  --warning-dark: #b45309;
  --success-bg: #cef1ce;
  --warning-bg: #fef9e7;

  /* Status-badge palettes (badges.css .status-*): text = tone 40, fill =
     tone 90 container, -border = tone 80. The greens' fills cap chroma at 24
     (tone 92): at full chroma they glow when a whole table row is tinted. Hover fills are a state layer
     over the container rather than a separate colour. */
  --info: #005cba;
  --info-bg: #d7e3ff;
  --info-bg-hover: color-mix(in srgb, var(--info-bg), var(--info) 8%);
  --review: #b85c00;
  --review-bg: #fff3e0;
  --review-border: #f7ddb8;
  --review-bg-hover: #ffe8c7;
  --approved: #006c48;
  --approved-bg: #c8f2d8;
  --approved-bg-hover: color-mix(in srgb, var(--approved-bg), var(--approved) 8%);
  --pending: #7d5800;
  --pending-bg: #ffdea9;
  --requested: #2151da;
  --requested-bg: #dce1ff;
  --requested-border: #b7c4ff;
  --requested-bg-hover: color-mix(in srgb, var(--requested-bg), var(--requested) 8%);
  --invoiced: #713dcc;
  --invoiced-bg: #ebddff;
  --invoiced-bg-hover: color-mix(in srgb, var(--invoiced-bg), var(--invoiced) 8%);

  --success-border: #94d69c;
  --warning-tint: #fff7ed;

  --indigo: #5148d7;
  --indigo-dark: #372abf;
  --indigo-bg: #e3dfff;
  --indigo-bg-hover: color-mix(in srgb, var(--indigo-bg), var(--indigo) 8%);

  --amber-dark: #6b3b00;
  --amber-bg: #ffdcc0;

  --gold: #d7a31b;
  --gold-bright: #f6be39;
  --gold-soft: #ffdfa0;
  --gold-dark: #5c4300;

  --overflow: #a73a00;
  --overflow-dark: #7f2b00;
  --overflow-bg: #ffdbce;

  --teal-border: var(--md-sys-color-outline-variant);

  /* EU emblem blue — the EU donor badge only; not a UI colour. */
  --eu-blue: #003399;
  --eu-blue-bg: #dce1ff;

  --chart-gray: var(--md-sys-color-outline-variant);
  --chart-blue: #70b4d3;
  --chart-gold: #e0c46c;

  --dropdown-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 -960 960 960'%3E%3Cpath d='M480-360 280-560h400L480-360Z' fill='%233f4948'/%3E%3C/svg%3E");
  /* Material Symbols as masks, for icons CSS draws itself (a pseudo-element,
     or text a script writes): tint with background-color. Same path data as
     IconsHelper::ICON_PATHS. */
  --md-icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M378-246 154-470l43-43 181 181 384-384 43 43-427 427Z'/%3E%3C/svg%3E");
  --md-icon-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M453-280h60v-240h-60v240Zm50.5-323.2q9.5-9.2 9.5-22.8 0-14.45-9.48-24.22-9.48-9.78-23.5-9.78t-23.52 9.78Q447-640.45 447-626q0 13.6 9.48 22.8 9.48 9.2 23.5 9.2t23.52-9.2ZM480.27-80q-82.74 0-155.5-31.5Q252-143 197.5-197.5t-86-127.34Q80-397.68 80-480.5t31.5-155.66Q143-709 197.5-763t127.34-85.5Q397.68-880 480.5-880t155.66 31.5Q709-817 763-763t85.5 127Q880-563 880-480.27q0 82.74-31.5 155.5Q817-252 763-197.68q-54 54.31-127 86Q563-80 480.27-80Zm.23-60Q622-140 721-239.5t99-241Q820-622 721.19-721T480-820q-141 0-240.5 98.81T140-480q0 141 99.5 240.5t241 99.5Zm-.5-340Z'/%3E%3C/svg%3E");
  --md-icon-warning: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='m40-120 440-760 440 760H40Zm104-60h672L480-760 144-180Zm361.5-65.68q8.5-8.67 8.5-21.5 0-12.82-8.68-21.32-8.67-8.5-21.5-8.5-12.82 0-21.32 8.68-8.5 8.67-8.5 21.5 0 12.82 8.68 21.32 8.67 8.5 21.5 8.5 12.82 0 21.32-8.68ZM454-348h60v-224h-60v224Zm26-122Z'/%3E%3C/svg%3E");
  --md-icon-chevron-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M530-481 332-679l43-43 241 241-241 241-43-43 198-198Z'/%3E%3C/svg%3E");
  --md-icon-expand-more: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M480-344 240-584l43-43 197 197 197-197 43 43-240 240Z'/%3E%3C/svg%3E");
  --md-icon-open-in-new: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h279v60H180v600h600v-279h60v279q0 24-18 42t-42 18H180Zm202-219-42-43 398-398H519v-60h321v321h-60v-218L382-339Z'/%3E%3C/svg%3E");
  --md-icon-arrow-upward: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M510-686v526h-60v-526L202-438l-42-42 320-320 320 320-42 42-248-248Z'/%3E%3C/svg%3E");
  --md-icon-arrow-downward: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M450-274v-526h60v526l248-248 42 42-320 320-320-320 42-42 248 248Z'/%3E%3C/svg%3E");
  --md-icon-unfold-more: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M480-120 300-300l43-43 137 137 137-137 43 43-180 180ZM343-617l-43-43 180-180 180 180-43 43-137-137-137 137Z'/%3E%3C/svg%3E");
  /* The same check pre-tinted on-secondary-container, for a selected filter
     chip built on a <select> (no pseudo-elements, so no mask). */
  --chip-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M378-246 154-470l43-43 181 181 384-384 43 43-427 427Z' fill='%23324b4b'/%3E%3C/svg%3E");

  /* Native controls the app can't restyle (checkbox/radio ticks, the date
     input's calendar popup) take the primary colour on a light scheme. */
  accent-color: var(--md-sys-color-primary);
  color-scheme: light;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* Fieldsets default to min-width: min-content, so one wide child pushes the
   whole page sideways on a phone; let them shrink like any block. */
fieldset { min-width: 0; }

/* A raw URL in running text (e.g. a project's cashbook submission link) must
   wrap rather than widen the page. break-word leaves min-content sizing of
   tables and grids untouched. */
p { overflow-wrap: break-word; }

body {
  font: var(--md-sys-typescale-body-medium);
  letter-spacing: var(--md-sys-typescale-body-medium-tracking);
  font-optical-sizing: auto;
  color: var(--md-sys-color-on-surface);
  background: var(--md-sys-color-surface);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings follow the M3 type scale: page titles are headline-small,
   section titles title-large, sub-sections title-medium, then title-small. */
h1 { font: var(--md-sys-typescale-headline-small); letter-spacing: var(--md-sys-typescale-headline-small-tracking); color: var(--md-sys-color-on-surface); }
h2 { font: var(--md-sys-typescale-title-large); letter-spacing: var(--md-sys-typescale-title-large-tracking); color: var(--md-sys-color-on-surface); }
h3 { font: var(--md-sys-typescale-title-medium); letter-spacing: var(--md-sys-typescale-title-medium-tracking); color: var(--md-sys-color-on-surface); }
h4 { font: var(--md-sys-typescale-title-small); letter-spacing: var(--md-sys-typescale-title-small-tracking); color: var(--md-sys-color-on-surface); }

a { color: var(--md-sys-color-primary); }

/* The M3 type scale tops out at weight 500; the browser's bold (700) for
   inline emphasis would be the only heavier text on the page. */
b, strong { font-weight: 500; }

/* The browser's `smaller` lands between type-scale steps (11.67px under
   body-medium); fine print is body-small. */
small {
  font: var(--md-sys-typescale-body-small);
  letter-spacing: var(--md-sys-typescale-body-small-tracking);
}

/* Form controls don't inherit type by default (browsers give them their own
   system font); make every bare control use the M3 type. Component rules
   set their own type-scale role on top. */
button, input, select, textarea {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}

/* Default look for any text field or select no component styles: a dense
   (40px) M3 outlined field. :where() keeps it at zero specificity, so every
   class-based rule still wins. */
:where(input:is([type="text"], [type="number"], [type="email"], [type="date"], [type="month"],
                [type="search"], [type="password"], [type="tel"], [type="url"], :not([type])),
       select, textarea) {
  min-height: 40px;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--md-sys-color-outline);
  border-radius: var(--md-sys-shape-corner-extra-small);
  background-color: var(--md-sys-color-surface-container-lowest);
  color: var(--md-sys-color-on-surface);
}

:where(select:not([multiple]):not([size])) {
  appearance: none;
  background-image: var(--dropdown-arrow);
  background-repeat: no-repeat;
  background-position: right 0.375rem center;
  background-size: 24px;
  padding-right: 2.25rem;
}

:where(input:is([type="text"], [type="number"], [type="email"], [type="date"], [type="month"],
                [type="search"], [type="password"], [type="tel"], [type="url"], :not([type])),
       select, textarea):where(:focus) {
  outline: none;
  border-color: var(--md-sys-color-primary);
  box-shadow: inset 0 0 0 1px var(--md-sys-color-primary);
}

::selection { background: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); }

/* M3 keyboard focus indicator on every interactive element. */
:focus-visible {
  outline: var(--md-focus-ring);
  outline-offset: var(--md-focus-ring-offset);
}

/* ── State layers ──────────────────────────────────────────────
   M3 interaction states: a translucent layer of the content colour over
   the container — 8% on hover, 10% on focus and press. Drawn as a
   background-image gradient so it composes with any container colour and
   needs no pseudo-element. Opt in with the .state-layer class, or by being
   one of the shared interactive components listed here. */
.state-layer,
.btn,
.btn-icon,
.btn-remove-line-item,
.pagination-bar a,
.nav-links a,
.project-tab,
.toggle-pill,
.filter-more-trigger,
.attachments-trigger,
.vendor-search-result,
input[type="file"]::file-selector-button {
  --md-state-layer: transparent;
  background-image: linear-gradient(var(--md-state-layer), var(--md-state-layer));
  transition:
    background-color var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
    color var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
    border-color var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
    box-shadow var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}

:is(.state-layer, .btn, .btn-icon, .btn-remove-line-item, .pagination-bar a, .nav-links a, .project-tab,
    .toggle-pill, .filter-more-trigger, .attachments-trigger,
    .vendor-search-result):hover,
input[type="file"]::file-selector-button:hover {
  --md-state-layer: color-mix(in srgb, currentColor var(--md-sys-state-hover-opacity), transparent);
}

:is(.state-layer, .btn, .btn-icon, .btn-remove-line-item, .pagination-bar a, .nav-links a, .project-tab,
    .toggle-pill, .filter-more-trigger, .attachments-trigger,
    .vendor-search-result):is(:focus-visible, :active) {
  --md-state-layer: color-mix(in srgb, currentColor var(--md-sys-state-pressed-opacity), transparent);
}

/* ── Top app bar ───────────────────────────────────────────────
   M3 small top app bar: surface container at rest; once the page scrolls
   it gains a tonal lift to surface-container (scroll-driven, no JS). */
.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0 1rem 0 0.75rem;
  /* A contract, not a look: six stylesheets use --nav-height as their sticky
     `top`, so the bar must never grow. The link row scrolls instead. */
  height: var(--nav-height);
  background: var(--md-sys-color-surface);
  position: sticky;
  top: 0;
  z-index: 100;
  animation: app-bar-scroll linear both;
  animation-timeline: scroll(root);
  animation-range: 0 64px;
}

@keyframes app-bar-scroll {
  from { background-color: var(--md-sys-color-surface); }
  to   { background-color: var(--md-sys-color-surface-container); }
}

.nav-brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  text-decoration: none;
  flex-shrink: 0;
}

.nav-brand img {
  height: 32px;
  width: 32px;
}

.nav-brand-name {
  font: var(--md-sys-typescale-title-large);
  letter-spacing: var(--md-sys-typescale-title-large-tracking);
  color: var(--md-sys-color-on-surface);
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  /* Role-dependent width, wider than a tablet. min-width lets the row shrink
     and scroll instead of forcing a floor the whole page stretches to. */
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}

.nav-links::-webkit-scrollbar {
  display: none;
}

/* Nothing in the bar may wrap to a second line — the bar is a fixed height, so
   a two-line item would spill out of it. */
.nav-links > * {
  white-space: nowrap;
  flex-shrink: 0;
}

/* Below this an admin's links + email + button stop fitting, and Sign out must
   stay reachable without scrolling. The email is the only non-control here. */
@media (max-width: 1000px) {
  .nav-user {
    display: none;
  }
}

/* App-bar destinations read as M3 text buttons: on-surface-variant label in a
   full-round 40px target with a state layer. */
.nav-links a:not(.btn) {
  display: inline-flex;
  align-items: center;
  height: 40px;
  padding: 0 0.875rem;
  border-radius: var(--md-sys-shape-corner-full);
  text-decoration: none;
  color: var(--md-sys-color-on-surface-variant);
  font: var(--md-sys-typescale-label-large);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
}

.nav-links a:not(.btn):hover {
  color: var(--md-sys-color-on-surface);
}

/* The signed-in user, shown as supporting text before the account actions. */
.nav-user {
  font: var(--md-sys-typescale-body-medium);
  color: var(--md-sys-color-on-surface-variant);
  padding: 0 0.5rem 0 0.75rem;
}

/* ── Navigation menu ───────────────────────────────────────────
   The app's global destinations (Dashboard, Experts, Vendors, Management,
   Admin) hang off the app bar's leading navigation icon as an M3 menu: a
   native popover, so it dismisses on outside click or Escape with no JS.
   Anchored under its button where CSS anchor positioning exists; otherwise
   it drops from the top-left corner of the app bar. */
.nav-brand { margin-right: auto; }

.app-menu {
  position: fixed;
  inset: auto;
  top: calc(var(--nav-height) - 0.5rem);
  left: 0.75rem;
  margin: 0;
  min-width: 14rem;
  padding: 0.5rem 0;
  border: none;
  border-radius: var(--md-sys-shape-corner-extra-small);
  /* Menus are white, like the dialogs and chip menus: the default
     surface-container grey read as inactive. */
  background: var(--md-sys-color-surface-container-lowest);
  color: var(--md-sys-color-on-surface);
  box-shadow: var(--md-sys-elevation-level2);
}

@supports (anchor-name: --app-menu) {
  .app-menu-trigger { anchor-name: --app-menu; }
  .app-menu {
    position-anchor: --app-menu;
    top: calc(anchor(bottom) + 0.25rem);
    left: anchor(left);
  }
}

.app-menu:popover-open {
  animation: app-menu-enter var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized-decelerate);
}

@keyframes app-menu-enter {
  from { opacity: 0; transform: translateY(-0.5rem); }
  to   { opacity: 1; transform: none; }
}

/* M3 menu items: 48px, label-large, a 24dp leading icon. */
.app-menu-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 48px;
  padding: 0 0.75rem;
  font: var(--md-sys-typescale-label-large);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
  color: var(--md-sys-color-on-surface);
  text-decoration: none;
}

.app-menu-item .icon {
  width: 24px;
  height: 24px;
  vertical-align: 0;
  color: var(--md-sys-color-on-surface-variant);
}

.app-menu-item:hover,
.app-menu-item:focus-visible {
  background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
  outline: none;
}

/* ── Overflow menu ─────────────────────────────────────────────
   An M3 overflow menu off a ⋮ icon button, for a page's rarely used actions:
   a native popover with the navigation menu's items, dropping from the
   button that opened it (the popover's implicit anchor) and flipping when it
   would leave the viewport; without anchor positioning it centres. */
.overflow-menu {
  position: fixed;
  margin: auto;
  min-width: 14rem;
  padding: 0.5rem 0;
  border: none;
  border-radius: var(--md-sys-shape-corner-extra-small);
  background: var(--md-sys-color-surface-container-lowest);
  color: var(--md-sys-color-on-surface);
  box-shadow: var(--md-sys-elevation-level2);
}

@supports (position-area: bottom) {
  .overflow-menu {
    inset: auto;
    margin: 0.25rem 0;
    position-area: bottom span-left;
    position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
  }
}

.overflow-menu:popover-open {
  animation: app-menu-enter var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized-decelerate);
}

/* ── Buttons ───────────────────────────────────────────────────
   M3 common buttons: 40px tall, fully rounded, label-large text, 24px side
   padding. Variants: filled (.btn-primary), tonal (.btn-tonal), outlined
   (.btn-outline), text (.btn-text), and error-filled (.btn-danger). */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 40px;
  padding: 0.5rem 1.5rem;
  border-radius: var(--md-sys-shape-corner-full);
  font: var(--md-sys-typescale-label-large);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
  text-decoration: none;
  text-align: center;
  /* M3 labels never wrap: a squeezed flex row would otherwise fold a label
     onto two lines and turn the pill into a lozenge. Rows that hold buttons
     wrap instead. */
  white-space: nowrap;
  cursor: pointer;
  vertical-align: middle;
  /* Same width as .btn-outline's border, so filled and outline buttons match in height. */
  border: 1px solid transparent;
  background-color: transparent;
  color: var(--md-sys-color-primary);
  -webkit-appearance: none;
  appearance: none;
}

.btn .icon { width: 18px; height: 18px; vertical-align: 0; }
.btn:has(> .icon:first-child) { padding-left: 1rem; }

.btn-primary {
  background-color: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
}

.btn-primary:hover {
  color: var(--md-sys-color-on-primary);
  box-shadow: var(--md-sys-elevation-level1);
}

.btn-tonal {
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
}

.btn-tonal:hover { box-shadow: var(--md-sys-elevation-level1); }

.btn-outline {
  background-color: transparent;
  color: var(--md-sys-color-primary);
  border: 1px solid var(--md-sys-color-outline);
}

/* Outlined in the error colour: a destructive action that isn't the page's
   main action (e.g. "Delete partner" at the foot of an edit form). */
.btn-outline-danger {
  color: var(--md-sys-color-error);
  border-color: var(--md-sys-color-error);
}

.btn-text {
  background-color: transparent;
  color: var(--md-sys-color-primary);
  padding-left: 0.75rem;
  padding-right: 0.75rem;
}
/* Text button with a leading icon: 12dp before the icon, 16dp after the label. */
.btn-text:has(> .icon:first-child) { padding-left: 0.75rem; padding-right: 1rem; }

/* ── Danger button (destructive actions) ──────────────────────
   Error-filled. Reserve for destructive actions so red consistently
   signals danger. */
.btn-danger {
  background-color: var(--md-sys-color-error);
  color: var(--md-sys-color-on-error);
}

.btn-danger:hover {
  color: var(--md-sys-color-on-error);
  box-shadow: var(--md-sys-elevation-level1);
}

/* ── Inline SVG icons ──────────────────────────────────────────
   Rendered by IconsHelper#icon. Size to the surrounding text and inherit
   colour so they recolour with their button/link. */
.icon {
  width: 1.25em;
  height: 1.25em;
  display: inline-block;
  vertical-align: -0.25em;
  flex: none;
}

/* Toggle icons carry a filled twin (IconsHelper::FILLED_PATHS); the outline
   shows until the toggle's "on" class swaps them. */
.icon .icon-filled { display: none; }

/* ── Icon-only action buttons (edit / view / delete) ───────────
   The standard square icon button used for row + header actions. Promoted
   here from bank.css so every feature gets the same edit/view/delete UI.
   button_to wraps the <button> in a <form>; keep that form inline so the
   buttons sit in a row. */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background-color: transparent;
  border: none;
  color: var(--md-sys-color-on-surface-variant);
  width: 40px;
  height: 40px;
  border-radius: var(--md-sys-shape-corner-full);
  line-height: 1;
  cursor: pointer;
  padding: 0;
  text-decoration: none;
  /* <button> doesn't inherit font-size by default; without this the em-sized
     .icon renders smaller in a button than in an <a>, making grouped edit/
     delete icons look mismatched. Inherit so every icon is the same size. */
  font: inherit;
}

/* 20px icons in M3's 40px state-layer circle. M3's 24px read about a fifth
   too big beside this app's 14px body text (Spencer's call). Every icon-only
   button uses this size. */
.btn-icon .icon { width: 20px; height: 20px; vertical-align: 0; }

.btn-icon:hover { color: var(--md-sys-color-on-surface); }

/* Destructive variant — the glyph and its state layer turn error red on
   hover so delete/remove reads as dangerous. */
.btn-icon-danger:hover { color: var(--md-sys-color-error); }

/* button_to renders <form><button></form>; collapse the form so grouped
   icon buttons line up. */
form.button_to { display: inline-block; margin: 0; }

/* Wrapper to group row/header action buttons with consistent spacing.
   Vertically centres mixed icon + text buttons so they share a baseline. */
.icon-actions {
  display: inline-flex;
  align-items: center;
  gap: 0;
}

/* ── Pagination ────────────────────────────────────────────── */
.pagination-bar {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 1.5rem;
  margin-top: 1rem;
  padding-top: 0.75rem;
}

.pagination-count {
  font: var(--md-sys-typescale-body-small);
  letter-spacing: var(--md-sys-typescale-body-small-tracking);
  color: var(--md-sys-color-on-surface-variant);
  white-space: nowrap;
}

.pagination-bar nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0.25rem;
}

.pagination-bar a,
.pagination-bar span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding: 0 0.75rem;
  border-radius: var(--md-sys-shape-corner-full);
  font: var(--md-sys-typescale-label-large);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
  text-decoration: none;
}

.pagination-bar a {
  color: var(--md-sys-color-on-surface-variant);
}

.pagination-bar span.current {
  background: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
}

.pagination-bar span.disabled {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  cursor: default;
}

/* ── Form actions + errors ─────────────────────────────────── */
/* Every form ends the same way: a text-button Cancel, then the filled
   primary action, at the trailing edge (M3 puts the confirming action last). */
.form-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
  padding-top: 0.5rem;
}

/* A card following a form's action row (e.g. a Delete section) needs space. */
.form-actions + .form-section,
form + .form-section { margin-top: 1.5rem; }

.form-actions input[type="submit"] {
  min-height: 40px;
  padding: 0 1.5rem;
  border-radius: var(--md-sys-shape-corner-full);
  font: var(--md-sys-typescale-label-large);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
  cursor: pointer;
  border: none;
  background: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  transition: box-shadow var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}

.form-actions input[type="submit"]:hover {
  background-image: linear-gradient(rgb(255 255 255 / 0.08), rgb(255 255 255 / 0.08));
  box-shadow: var(--md-sys-elevation-level1);
}

.form-errors {
  background: var(--md-sys-color-error-container);
  border-radius: var(--md-sys-shape-corner-medium);
  padding: 1rem 1.25rem;
  margin-bottom: 1.5rem;
  font: var(--md-sys-typescale-body-medium);
  color: var(--md-sys-color-on-error-container);
  /* Keep scrollIntoView from parking the banner under the sticky nav. */
  scroll-margin-top: calc(var(--nav-height) + 1rem);
}

.form-errors ul {
  margin-top: 0.5rem;
  padding-left: 1.25rem;
}

/* Client-side "what's missing" summary (submit_guard_controller). Compact: a
   short heading over the missing field names as pill chips, not a wall of
   bullets. */
.submit-guard-errors {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  background: var(--md-sys-color-error-container);
  border-radius: var(--md-sys-shape-corner-medium);
  padding: 0.875rem 1rem;
  margin-bottom: 1.5rem;
}

.submit-guard-errors-title {
  margin: 0;
  font: var(--md-sys-typescale-title-small);
  letter-spacing: var(--md-sys-typescale-title-small-tracking);
  color: var(--md-sys-color-on-error-container);
}

.submit-guard-errors ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Missing fields as M3 input chips: 32px, small (8dp) corners. */
.submit-guard-errors li {
  display: inline-flex;
  align-items: center;
  height: 32px;
  font: var(--md-sys-typescale-label-large);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
  color: var(--md-sys-color-on-error-container);
  background: var(--md-sys-color-surface-container-lowest);
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
  padding: 0 0.75rem;
}

/* ── Form fields ───────────────────────────────────────────────
   Shared form primitives used across every feature's forms (invoices,
   cashbook, budget lines, ToRs, accounts…). Live in base so any feature
   file can override at equal specificity, and so feature files don't have
   to load in a particular order to pick them up. */
.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

/* The HTML `hidden` attribute is overridden by any selector that sets
   `display` (e.g. .form-field uses display: flex). We rely on `hidden` to
   collapse manual-rate fields on cashbook new/edit until the toggle-field
   Stimulus controller flips them on, so force the attribute to win. */
[hidden] { display: none !important; }

/* Read by screen readers, not shown — e.g. the label of an icon-only column. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

.form-field label {
  font: var(--md-sys-typescale-body-small);
  letter-spacing: var(--md-sys-typescale-body-small-tracking);
  color: var(--md-sys-color-on-surface-variant);
}

.required-legend {
  margin: 0 0 1rem;
}

/* M3 outlined text field, at density -2 (48px) for this data-dense app:
   outline-coloured 1px border, extra-small corners, on-surface input text.
   Hover darkens the outline to on-surface; focus thickens it to a 2px
   primary outline (drawn inset so the field doesn't shift). A file input is
   not a text field — it gets the file-picker look further down. */
.form-field input:not([type="file"]),
.form-field textarea,
.form-field select {
  min-height: 48px;
  padding: 0.75rem 1rem;
  border: 1px solid var(--md-sys-color-outline);
  border-radius: var(--md-sys-shape-corner-extra-small);
  font: var(--md-sys-typescale-body-large);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);
  color: var(--md-sys-color-on-surface);
  /* Native <select> would otherwise render with a grey system background,
     which reads as "disabled". */
  background-color: transparent;
  caret-color: var(--md-sys-color-primary);
  transition:
    border-color var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
    box-shadow var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
  outline: none;
  width: 100%;
}

.form-field input:not([type="file"]):hover:not(:disabled):not(:focus),
.form-field textarea:hover:not(:disabled):not(:focus),
.form-field select:hover:not(:disabled):not(:focus) {
  border-color: var(--md-sys-color-on-surface);
}

/* Checkboxes and radios are not text fields. */
.form-field input:is([type="checkbox"], [type="radio"]) {
  min-height: 0;
  width: auto;
  padding: 0;
}

input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--md-sys-color-primary);
  width: 18px;
  height: 18px;
}

.form-field input::placeholder,
.form-field textarea::placeholder {
  color: var(--md-sys-color-on-surface-variant);
  opacity: 1;
}

/* Error state (aria-invalid / Rails' field_with_errors): error outline. */
.form-field :is(input, textarea, select)[aria-invalid="true"],
.field_with_errors :is(input, textarea, select) {
  border-color: var(--md-sys-color-error);
}

/* <select> drops the native chrome (incl. the system arrow) so it matches the
   white inputs, then draws the shared custom arrow. */
.form-field select {
  appearance: none;
  background-image: var(--dropdown-arrow);
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 24px;
  padding-right: 2.75rem;
}

.form-field input:not([type="file"]):focus,
.form-field textarea:focus,
.form-field select:focus {
  border-color: var(--md-sys-color-primary);
  box-shadow: inset 0 0 0 1px var(--md-sys-color-primary);
}

.form-field:focus-within > label {
  color: var(--md-sys-color-primary);
}

/* Disabled / locked fields (e.g. the budget line once a ToR line item is
   chosen): grey fill, muted text, no caret — the single, consistent signal
   that a field can't be edited. */
/* M3 disabled text field: a 12% on-surface outline and 38% text. A faint
   4% fill keeps "not editable" readable at a glance. */
.form-field input:disabled,
.form-field textarea:disabled,
.form-field select:disabled {
  border-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 4%, transparent);
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  cursor: not-allowed;
}

/* A read-only field VALUE shown where an input would otherwise sit (e.g. a
   carried-over quantity that must not be edited). Same box + grey fill as a
   disabled field — the app's one signal for "not editable" — with the value in
   normal text and an optional trailing note muted. It wraps rather than
   spills out of a narrow grid column: the note drops below the value, and a
   long unbroken value (an e-mail address) breaks. */
.form-readonly {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.5rem;
  overflow-wrap: anywhere;
  min-height: 48px;
  padding: 0.75rem 1rem;
  border: 1px solid color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
  border-radius: var(--md-sys-shape-corner-extra-small);
  font: var(--md-sys-typescale-body-large);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);
  color: var(--md-sys-color-on-surface);
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 4%, transparent);
}
.form-readonly-note {
  font: var(--md-sys-typescale-body-small);
  letter-spacing: var(--md-sys-typescale-body-small-tracking);
  color: var(--md-sys-color-on-surface-variant);
}

/* File pickers. M3 has no file field, so a file input isn't drawn as one
   (the field rules above skip it): it's an M3 tonal button ("Choose file" /
   "Browse…", the browser's own label) with the chosen file's name beside it
   as body-medium supporting text, and no box around them. Inside a 40-48px
   outlined field the 40dp button met the field's edges and clipped. */
input[type="file"] {
  max-width: 100%;
  min-height: 40px;
  padding: 0;
  border: none;
  background: none;
  font: var(--md-sys-typescale-body-medium);
  letter-spacing: var(--md-sys-typescale-body-medium-tracking);
  color: var(--md-sys-color-on-surface-variant);
  cursor: pointer;
}

input[type="file"]::file-selector-button {
  margin-right: 0.75rem;
  height: 40px;
  padding: 0 1.5rem;
  border: none;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
  font: var(--md-sys-typescale-label-large);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
  cursor: pointer;
}

input[type="file"]:focus-visible {
  outline: none;
}
input[type="file"]:focus-visible::file-selector-button {
  outline: var(--md-focus-ring);
  outline-offset: 2px;
}

.form-field textarea { resize: vertical; }

.form-field--full  { grid-column: 1 / -1; }
.form-field--small { min-width: 0; }
.form-field--grow  { min-width: 0; }

/* ── Inline line-item rows ─────────────────────────────────────
   A flex row (fields grid + Remove button) that wraps instead of
   overflowing. Used by the invoices and ToR builders via the line-items
   Stimulus controller. Each feature sets its own grid-template-columns on
   .line-item-fields (invoices in invoices.css, ToRs via .line-item-fields--tor). */
.line-item-row {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1rem 0;
  border-bottom: 1px solid var(--divider);
}

.line-item-row:last-child { border-bottom: none; }

.line-item-fields {
  display: grid;
  gap: 0.75rem;
  flex: 1;
  min-width: 0;
}

/* Dense rows (40+ items): text fields at M3 density -3 — 40px, body-medium,
   12px side padding — so narrow numeric columns don't clip their values. */
.line-item-fields .form-field :is(input, select, textarea) {
  min-height: 40px;
  padding: 0.5rem 0.75rem;
  font: var(--md-sys-typescale-body-medium);
  letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
.line-item-fields .form-field select { padding-right: 2.25rem; background-position: right 0.375rem center; }

/* Text button in the error colour. */
.btn-remove-line-item {
  background-color: transparent;
  border: none;
  color: var(--md-sys-color-error);
  font: var(--md-sys-typescale-label-large);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
  cursor: pointer;
  height: 40px;
  padding: 0 0.75rem;
  border-radius: var(--md-sys-shape-corner-full);
  white-space: nowrap;
  margin-top: 1.5rem;
}

.section-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  margin-bottom: 0.5rem;
}

/* ── Mobile (≤ 600px) ──────────────────────────────────────── */
@media (max-width: 600px) {

  /* Compact windows: a table that isn't already inside a scroll container
     scrolls sideways within itself instead of widening the whole page.
     (display: block keeps the table layout inside an anonymous table box.) */
  table:not(.data-table-wrap > table) {
    display: block;
    max-width: 100%;
    overflow-x: auto;
  }
  /* That makes the table its own scroll container, so a header made sticky
     below the app bar (top: --nav-height) would pin that far down INSIDE the
     table, over its first rows. It has nothing to pin against here. */
  table:not(.data-table-wrap > table) > thead th { position: static; }

  .pagination-bar { flex-wrap: wrap; gap: 0.5rem 1.5rem; }

  /* Nav — set the token, not the height: everything that sticks below the nav
     offsets itself by --nav-height, so a shorter bar has to move those too. */
  :root {
    --nav-height: 56px;
  }

  .nav {
    padding: 0 1rem;
  }

  .nav-brand-name { display: none; }

  /* .nav-user is already hidden from 1000px down. */

  .nav-links { gap: 0.75rem; }

  /* Page containers */
  .invoice-show,
  .project-show {
    padding: 1.5rem 1rem;
  }

  .dashboard { padding: 1.5rem 1rem; }

  /* Invoice header — stack vertically */
  .invoice-header {
    flex-direction: column;
    gap: 0.75rem;
    margin-bottom: 1.25rem;
  }

  .invoice-header-right {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.5rem;
  }

  /* Project header — stack vertically */
  .project-header {
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 1rem;
  }

  /* Expert/general header actions */
  .header-actions {
    flex-wrap: wrap;
    align-items: center;
  }

  /* Detail cells — 2-column grid on mobile */
  .invoice-details-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }

  .detail-cell {
    border-right: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
  }

  .detail-cell:nth-child(even) { border-right: none; }

  /* Review banner */
  .review-banner-actions {
    flex-wrap: wrap;
    gap: 0.5rem;
  }

  /* Assign form — stack on very small screens */
  .assign-form {
    flex-wrap: wrap;
  }

  .assign-form select {
    width: 100%;
  }

  /* Form sections — tighter padding */
  .form-section { padding: 1rem; }
  .dashboard-section { padding: 1rem; }
}

/* ── Disabled buttons ──────────────────────────────────────── */
/* M3 disabled button: 12% on-surface container, 38% on-surface label, no
   elevation and no state layer. */
.btn:disabled,
.btn[disabled],
.form-actions input[type="submit"]:disabled {
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
  border-color: transparent;
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  box-shadow: none;
  cursor: not-allowed;
  pointer-events: none;
}
.btn-outline:disabled,
.btn-outline[disabled] {
  background-color: transparent;
  border-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
}
.btn-text:disabled,
.btn-text[disabled] { background-color: transparent; }

/* Looks disabled, but stays clickable — clicking explains what's missing
   (submit_guard_controller). Keep pointer-events so the click is received. */
.btn--blocked {
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
  border-color: transparent;
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  box-shadow: none;
  cursor: not-allowed;
}

/* ── Attachments popover ───────────────────────────────────── */
.col-attachments {
  width: 4rem;
  text-align: center;
  white-space: nowrap;
}

.attachments-trigger {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  width: 40px;
  height: 40px;
  border-radius: var(--md-sys-shape-corner-full);
  padding: 0;
  color: var(--md-sys-color-on-surface-variant);
  cursor: pointer;
  font-family: inherit;
}

/* M3 menu surface, white like every menu here; extra-small corners,
   elevation 2. */
.attachments-popover {
  position: fixed;
  margin: auto;
  border: none;
  border-radius: var(--md-sys-shape-corner-extra-small);
  padding: 0.5rem 0;
  background: var(--md-sys-color-surface-container-lowest);
  color: var(--md-sys-color-on-surface);
  box-shadow: var(--md-sys-elevation-level2);
  min-width: 220px;
  max-width: 380px;
  /* Rendered inside a table cell; don't inherit its alignment. */
  text-align: left;
}

/* A menu drops from the button that opened it (the popover's implicit
   anchor), flipping when it would leave the viewport; without anchor
   positioning it centres as before. */
@supports (position-area: bottom) {
  .attachments-popover {
    inset: auto;
    margin: 0.25rem 0;
    position-area: bottom span-left;
    position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
  }
}

.attachments-popover:popover-open {
  animation: app-menu-enter var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized-decelerate);
}

.attachments-popover::backdrop {
  background: transparent;
}

.attachments-popover-title {
  font: var(--md-sys-typescale-title-small);
  letter-spacing: var(--md-sys-typescale-title-small-tracking);
  color: var(--md-sys-color-on-surface-variant);
  padding: 0.5rem 1rem;
}

.attachments-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* One menu item per file: the file name fills the row, its download icon
   button trails on the same line. */
.attachments-list li {
  display: flex;
  align-items: center;
  padding-right: 0.25rem;
}

/* Menu items: 48px, label-large, state layer on hover. */
.attachment-link {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  align-items: center;
  min-height: 48px;
  padding: 0 0.75rem 0 1rem;
  font: var(--md-sys-typescale-label-large);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
  color: var(--md-sys-color-on-surface);
  text-decoration: none;
  word-break: break-word;
}

.attachment-link:hover { background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent); }

.no-attachments {
  color: var(--md-sys-color-on-surface-variant);
  font: var(--md-sys-typescale-body-medium);
  padding: 0.5rem 1rem;
}

/* ── Flexible Remuneration overflow chip (cashbook index, invoice review) ── */
.overflow-chip {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.125rem 0.5rem;
  border-radius: var(--md-sys-shape-corner-small);
  font: var(--md-sys-typescale-label-small);
  letter-spacing: var(--md-sys-typescale-label-small-tracking);
  white-space: nowrap;
  color: var(--danger);
  background: var(--danger-bg);
}
/* A leading icon, as on an M3 assist chip, scaled to this 20px tag. */
.overflow-chip .icon { width: 14px; height: 14px; margin-right: 0.25rem; vertical-align: -3px; }
.overflow-chip--flex {
  color: var(--teal);
  background: var(--teal-light);
}

/* Inline help marker: a small Material "info" icon that reveals explanatory
   copy in the shared tooltip (.md-tooltip below) on hover, focus or tap.
   Shared by field labels and table column headers across the app — see the
   `info_tip` helper, which puts the copy in `data-tip`. */
.info-tip {
  position: relative;
  display: inline-flex;
  vertical-align: -0.2em;
  cursor: help;
  /* Reset the native <button> chrome so the marker reads as a plain inline icon. */
  margin: 0 0 0 0.25rem;
  padding: 0;
  border: 0;
  background: none;
  appearance: none;
  -webkit-appearance: none;
  color: var(--md-sys-color-on-surface-variant);
  user-select: none;
}
.info-tip .icon { width: 16px; height: 16px; vertical-align: 0; }
.info-tip:focus-visible {
  outline: var(--md-focus-ring);
  outline-offset: 1px;
  border-radius: var(--md-sys-shape-corner-full);
}

/* M3 plain tooltip: inverse surface, body-small, extra-small corners. One
   bubble for every [data-tip] marker, shown by lib/tooltip.js as a manual
   popover — the top layer, so no scrolling table or dialog clips it and no
   sticky header paints over it. The script sets top/left; `inset: auto`
   drops the popover's default centring. */
.md-tooltip {
  position: fixed;
  inset: auto;
  margin: 0;
  width: max-content;
  max-width: 260px;
  padding: 0.25rem 0.5rem;
  border: 0;
  border-radius: var(--md-sys-shape-corner-extra-small);
  background: var(--md-sys-color-inverse-surface);
  color: var(--md-sys-color-inverse-on-surface);
  font: var(--md-sys-typescale-body-small);
  letter-spacing: var(--md-sys-typescale-body-small-tracking);
  text-align: left;
  overflow: visible;
  pointer-events: none;
}

/* ── Touch targets for compact inline controls ─────────────────
   M3 keeps every target ≥48dp even where the visible control is small (an
   inline info icon, a row's favourite star). An invisible ::before centred on the
   control widens what the pointer hits without moving any layout — to 32px,
   not 48: these sit inline beside other targets (a sortable header's text,
   the next locale), and a wider halo would steal their clicks. */
:is(.info-tip, .sidebar-project-fav, .donor-grid-toggle-btn,
    .management-refresh-button, .locale-switcher-option) {
  position: relative;
}
:is(.info-tip, .sidebar-project-fav, .donor-grid-toggle-btn,
    .management-refresh-button, .locale-switcher-option)::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(100%, 32px);
  height: max(100%, 32px);
  transform: translate(-50%, -50%);
}

