/*
  MD3 shape/spacing/interaction refinements for MudBlazor components. MudBlazor's own
  defaults lean closer to Material 2 (4px corners, all-caps button labels, no
  selected-state styling for nav links) than to Material Design 3. This file only touches
  shape/spacing/typography-transform/interaction state — colors always come from
  HorusMudTheme.cs / md3-tokens.css, never hardcoded here (state-layer washes use
  `color-mix(in srgb, <a real token> X%, transparent)` — an overlay ON a themed color,
  not a color of its own — never a bare `white`/`black` literal as the wash color itself).

  Most concrete values here (spacing, radii, font sizes, padding) are ported 1:1 from a
  real, mature MD3 portal the user pointed to as a reference to copy exactly — an internal
  Horus-built tool ("Vitalant Raygun"), plain CSS with a `.vtl-*` component layer, at
  /Users/pablogonzalezmorelli/Documents/Projects/AvantiPoint/vitalant-hosting-raygun/. Only
  the palette differs (their violet #722282 primary → our red) — EXCEPT the nav drawer's
  container/active-indicator colors, which deliberately do NOT follow that reference (it
  uses a solid brand-color rail, a legitimate stylistic choice but not what the actual MD3
  spec — m3.material.io/components/navigation-drawer/specs — calls for); those follow the
  spec's own roles (surface container, secondary-container active indicator) instead. Where
  a value below cites a `.vtl-*` selector, that's the exact source rule it was ported from
  — go there before changing a number here, don't re-derive it from scratch.
*/

/* The reference's own layout sets `height: 100vh` on both <body> and its flex root
   (`.vtl-layout`) — ours didn't set any height at all, so `.mud-layout`'s own `height:100%`
   (MudBlazor's rule) had nothing to resolve against, and `.mud-main-content`'s `flex:1 1
   auto` had no viewport-height parent to grow into. Net effect: on a page shorter than the
   viewport, the themed background stopped exactly where the content ended instead of
   filling the page. Fix: give the chain a real height to distribute, same as the reference.

   overflow:hidden — explicit user request. By default MudBlazor leaves <html>/<body> as
   the scroll context (AppBar/Drawer are position:fixed, floating over the document, but
   the whole document is what actually scrolls), and the browser's native scrollbar for
   that context always starts at y=0 — above the AppBar, visually competing with it
   regardless of the AppBar being fixed. The only way to get the visible scrollbar to
   start below the AppBar is for the real scroll context to NOT be <html>/<body> but an
   element that already starts below it — see `.mud-main-content` further down, which is
   what the scroll gets transferred to. */
html, body {
    height: 100%;
    overflow: hidden;
}

/* MD3 buttons: fully rounded ("full" shape, stadium — the reference's own formula is
   literally height/2 for a 40px button = 20px radius, `.vtl-button`; 100px is just a value
   comfortably above that for any of our button sizes), normal case — MD2's all-caps label
   convention doesn't exist in MD3. */
.mud-button-filled,
.mud-button-outlined,
.mud-button-text {
    border-radius: 100px;
    text-transform: none;
    letter-spacing: normal;
}

/* `.vtl-button-primary` (MD3 "Filled button") carries a real elevation shadow, slightly
   deeper on hover — MudBlazor's filled button has none by default. Shadow is neutral
   black, not brand-tinted (the reference's own `--vtl-shadow-color` is a fixed dark navy
   unrelated to its violet primary — shadows aren't a brand color in MD3, elevation is). */
.mud-button-filled {
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 3px 1px rgba(0, 0, 0, 0.15);
}

.mud-button-filled:hover {
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 6px 2px rgba(0, 0, 0, 0.15);
}

/* MD3 "state layer" on icon buttons — a faint tonal wash on hover/focus, using the
   button's own current color (`.vtl-icon-btn:hover` uses the same "tint of its own color"
   technique — 8% hover / 12% active there — so this works whether an icon is on-surface,
   Primary, Tertiary, or Error, without hardcoding a single color for every case). */
.mud-icon-button:hover,
.mud-icon-button:focus-visible {
    background-color: color-mix(in srgb, currentColor 8%, transparent);
}

/* MD3 icon buttons (m3.material.io/components/icon-buttons/specs) define explicit
   container/icon/padding presets per size — they aren't just "any circle around an
   icon". Our row-action icons render at 20px (matching the spec's own "Extra Small"
   preset: 32dp container, 20dp icon, 6dp padding each side) — but MudBlazor's own
   `Size.Small` gives only 3px padding (`.mud-icon-button-size-small{padding:3px}`,
   confirmed via the compiled CSS), half of what the spec calls for at this icon size.
   Matching MudBlazor's own selector exactly (1 class, same as theirs) so this actually
   overrides it — with !important as the established safety net given how often a
   same-or-lower-specificity override has silently lost this session. */
.mud-icon-button-size-small {
    padding: 6px !important;
}

/* MD3 Switch (m3.material.io/components/switch/specs) is structurally quite different
   from MudBlazor's default — MudBlazor's own switch is Material 2's "thin track + big
   overlapping thumb" model, colored via opacity tricks (`.mud-switch-track{opacity:.48}`
   tinting a neutral color rather than using two genuinely distinct on/off colors), and
   the thumb never changes size. MD3's spec: a fuller pill track (roughly 52x32dp,
   filled with Primary when on / outlined with a 2px Outline border when off, no fill),
   and a thumb that GROWS when switched on (16dp unselected → 24dp selected) — that
   size change IS the primary on/off affordance in the spec, more than color.
   Rebuilding this: MudBlazor computes the thumb's horizontal travel distance as a fixed
   inline `transform: translateX()` baked into its own render logic (verified in the
   live DOM — not a CSS variable), tuned to the TRACK'S EXISTING WIDTH. Widening the
   track via CSS would leave the thumb short of the new right edge when on. Track HEIGHT
   is safe to change (doesn't affect horizontal travel) — used to get closer to MD3's
   fuller pill proportions without touching width. The thumb's grow effect is done via
   `transform: scale()` (changes visual size only, not layout box or position) instead of
   width/height, for the same reason — this keeps MudBlazor's own travel math intact
   while still delivering the spec's real "thumb grows on select" cue.
   MudBlazor's own height/radius rule is actually `.mud-switch-span .mud-switch-track`
   (2 classes) — confirmed live via document.styleSheets (a plain grep on the compiled CSS
   missed the ancestor prefix, same trap as every other specificity gotcha this session):
   a bare `.mud-switch-track` (1 class) silently lost height/border-radius to it despite
   loading later in the `<head>`. Matched here, plus !important as the established safety
   net for this exact recurring trap.
   Track height picked to fit MD3's real thumb:track ratio, not eyeballed: spec is
   track=32dp / selected-thumb=24dp (0.75×) / unselected-thumb=16dp (0.5×). The base
   (unscaled) thumb element here is a fixed 20px — a first pass left the track at 20px
   too and scaled the thumb up to a full, un-shrunk 1.0 (=20px) when selected. With a
   2px border eating into the track's own visible fill (border-box: 20px total − 2×2px
   border = 16px of actual inner height), a 20px-diameter thumb didn't just reach the
   inner edge — it OVERFLOWED past both borders, "it goes out past the borders" as
   reported, and neither state matched the spec's own size (checked was 20px against a
   spec target of `24dp-ratio-equivalent`, not scaled from a value chosen for THIS
   track). Fixed properly: track raised to 24px (comfortable clearance for a properly
   MD3-proportioned thumb; width is untouched so the existing translateX travel math
   still lines up), thumb scaled to hit the spec's exact 0.75/0.5 ratios of that new
   track height — see the two thumb rules below for the resulting scale values. */
.mud-switch-span .mud-switch-track {
    height: 24px !important;
    /* Left-anchored, not centered, same as the vertical axis (see the transform comment
       below). First pass here only widened the track 4px to the right, fixing the
       CHECKED thumb's 2px overflow — but left the UNCHECKED thumb with just 1px of its
       original clearance from the track's (untouched) left edge, which read as "stuck
       to the edge" once actually seen live (0px-overflow isn't the same as "looks properly
       inset" — a 1px gap is visually indistinguishable from none). Widening the track
       AND shifting it left by 3px extends the track's footprint on BOTH ends at once:
       measured precisely against both thumb positions (unchecked-left and checked-right)
       to land ~4px of clearance on each side — not just "wide enough to not overflow".
       `width` alone didn't work — verified live: MudBlazor's `.mud-switch-span` is a
       fixed-size flex container, and the track is a flex item with `flex-shrink: 1` (its
       own default), so the browser silently shrank any wider `width` right back down to
       fit the container's existing content box, no matter the specified value (even
       forcing it via `element.style` directly, bypassing the stylesheet cascade
       entirely, still measured 34px — confirming this was flex sizing, not a CSS
       specificity loss like every other gotcha this session). `flex-shrink: 0` is what
       actually lets the track render at its full specified width. */
    flex-shrink: 0 !important;
    width: calc(100% + 9px) !important;
    border-radius: 12px !important;
    opacity: 1 !important;
    background-color: var(--md-sys-color-surface-variant) !important;
    border: 2px solid var(--md-sys-color-outline);
    box-sizing: border-box;
    /* translateY(-5px): MudBlazor positions the track top-anchored, not vertically
       centered, in .mud-switch-span — verified live via getBoundingClientRect: growing
       the track's height from its original 14px only pushed its BOTTOM edge down, so the
       track's own center drifted away from the thumb's (fixed, unaffected by this height
       change) center by exactly half the height delta. Measured directly rather than
       derived on paper: thumb center sat 5px above the new track's center, so shifting
       the track up by that same 5px is what actually re-centers it on the thumb — a
       value taken from the live measurement, not computed from the 14px→24px delta on
       paper (the two don't match exactly, which is itself evidence the track isn't
       simply top-anchored at a static offset either — empirical > theoretical here).
       translateX(-3px): extends the track's LEFT edge further left (same "shift the
       whole box, don't just grow it on one end" logic as the width fix above) — combined
       with the 9px width increase, this lands ~4px of clearance on BOTH the unchecked
       (left) and checked (right) thumb positions instead of the checked side alone. */
    transform: translateY(-5px) translateX(-3px);
}

/* Not `.mud-checked .mud-switch-track` (descendant) — verified live via the actual DOM
   that `.mud-switch-track` is a SIBLING of the element carrying `.mud-checked`
   (`.mud-switch-base`), not a descendant of it, so a descendant-combinator selector here
   silently never matches at all (confirmed: the thumb's own `.mud-checked .mud-switch-
   thumb-medium` rule below DOES work, because the thumb genuinely IS nested inside
   `.mud-switch-base` — only the track needed the sibling form). Mirrors MudBlazor's own
   existing sibling rule for this exact relationship
   (`.mud-switch-base.mud-checked + .mud-switch-track{opacity:.5}`), matching its
   specificity too. */
.mud-switch-base.mud-checked + .mud-switch-track {
    background-color: var(--md-sys-color-primary) !important;
    border-color: transparent !important;
}

/* Base (unscaled) thumb element is a fixed 20px. Track is now 24px — MD3's own ratio is
   unselected-thumb = 0.5× track, selected-thumb = 0.75× track. 24×0.5=12px → scale
   12/20=0.6; 24×0.75=18px → scale 18/20=0.9. Both now sit comfortably inside the
   track's 20px inner fill (24px − 2×2px border), instead of the previous 0.7/1.0
   scale pair that was sized against the OLD 20px track and overflowed once the
   border was accounted for. */
.mud-switch-thumb-medium,
.mud-switch-thumb-small,
.mud-switch-thumb-large {
    background-color: var(--md-sys-color-outline) !important;
    transform: scale(0.6);
    transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.mud-checked .mud-switch-thumb-medium,
.mud-checked .mud-switch-thumb-small,
.mud-checked .mud-switch-thumb-large {
    background-color: var(--md-sys-color-on-primary) !important;
    transform: scale(0.9);
}

/* Disabled switches (system roles/features, can't be toggled): MudBlazor already fades
   the TRACK to 12% opacity for `.mud-switch-disabled` — coincidentally almost exactly
   MD3's own disabled-container convention. But that rule only targets the track; the
   thumb has its own explicit background-color set above (not `currentColor`, so it
   never picked up MudBlazor's disabled dimming) and stayed at full opacity — verified
   live: a disabled checked switch rendered as a solid white dot with no visible track
   at all around it, since the track had faded to near-invisible but the thumb hadn't.
   38% is MD3's own disabled-content opacity (the counterpart to its 12% disabled-
   container value already matched here by coincidence). */
.mud-switch-base.mud-switch-disabled .mud-switch-thumb-medium,
.mud-switch-base.mud-switch-disabled .mud-switch-thumb-small,
.mud-switch-base.mud-switch-disabled .mud-switch-thumb-large {
    opacity: 0.38;
}

/* A page "section" — the card-like container for a toolbar + data grid (or any other
   grouped content) — directly ports `.vtl-section`: Surface-colored plane, 1px
   outline-variant border, 8px radius. No padding here (unlike an earlier version of this
   file) — the reference's `.vtl-section` has none either; its toolbar rows and the table
   own their own padding, see below. Elevation shadow is the reference's `.vtl-stat-card`
   value (its own `.vtl-section` has none, just the border) — neutral black, not
   brand-tinted, same reasoning as the filled-button shadow above. */
.horus-section {
    background-color: var(--md-sys-color-surface);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
}

/* A MudDataGrid nested inside .horus-section shares the section's own background/shape
   instead of drawing its own (default) surface-colored box + shadow on top of it. */
.horus-section .mud-table.mud-data-grid {
    background-color: transparent;
    box-shadow: none;
    border-radius: 0;
}

/* `.vtl-toolbar-row`-equivalent: the search row and the filters row both sit inside the
   section as their own bordered strip (`.vtl-search-bar` / `.vtl-filters`, both
   `padding: 12px 20px` / `16px 20px` + `border-bottom: 1px solid var(--color-border-row-
   default)`) — NOT a floating pair of pill fields sitting outside/above the card, which is
   what this project had before and doesn't exist anywhere in the reference. */
.horus-toolbar-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 20px;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    flex-wrap: wrap;
}

/* Pushes a toolbar item — and everything after it — to the trailing edge of the row. Put it on
   the DIVIDER that precedes a filter, not on the filter itself, so the pair travels together
   instead of leaving the separator stranded mid-row.

   `margin-inline-start`, not `margin-left`: the rest of this file is direction-agnostic and
   there is no reason for this one to be the exception. */
.horus-toolbar-push {
    margin-inline-start: auto;
}

/* The per-kind leave budget on /pto: one box per kind, all the same width, sharing the row —
   and STAYING on one row as it narrows.

   The count is a custom property set from the data rather than `auto-fit`, and the track is
   `minmax(0, 1fr)` rather than a bare `1fr`. Both halves matter:

     - `auto-fit` with a px floor drops a box to the next line the moment N × floor stops
       fitting, which is what narrowing the window (or just expanding the nav drawer) did.
       A fixed count can't wrap; it only gets narrower.
     - a bare `1fr` has an implicit `auto` minimum, i.e. min-content — so a track refuses to
       shrink past its longest word and the grid overflows instead. `minmax(0, 1fr)` is what
       actually lets it give. Same reason for `min-width: 0` on the box below: a grid ITEM also
       defaults to `min-width: auto`, so both the track and its item have to be told. */
.horus-pto-allowance-grid {
    display: grid;
    grid-template-columns: repeat(var(--horus-pto-allowance-cols, 1), minmax(0, 1fr));
    gap: 12px;
}

/* The strip's own heading. Bigger and at full strength than the caption it replaced: it names
   the YEAR every figure below belongs to, which is the one thing that makes them readable. */
.horus-pto-allowance-heading {
    font-size: 16px;
    font-weight: 700;
    color: var(--md-sys-color-on-surface);
    margin-bottom: 12px;
}

/* TONAL, not outlined (explicit request: the boxes read flat). The fill is `background`, the
   canvas tone — the same one the timesheet grid wrap uses, and the documented answer for "grey"
   in this palette (`surface-variant` is #F5DDDA here, a pink). Against the card's white surface
   that reads as a recessed panel; in dark it steps the other way, darker than the card, which is
   the same direction the timesheet grid already goes.

   Fill AND border (explicit request). The first pass dropped the border on the argument that an
   outline over a fill draws the box twice — which is true of a heavy outline, but this is the
   same hairline every other bordered surface in the app uses, and against a fill this close in
   tone it does the work the fill alone can't: it says where the box ENDS. It matters most in
   dark, where the box is darker than the card and the edge would otherwise be the only cue. */
.horus-pto-allowance-box {
    min-width: 0;
    /* Long single words break rather than push the box wider. Measured: at a 1000px viewport
       the boxes land near 100px and every kind fits except "Licencia reglamentaria", whose
       longest word overflowed by 3px — enough to make the grid scroll sideways. `anywhere`
       rather than `break-word` because it also lowers the min-content width the track is sized
       against, which is what keeps the columns honestly proportional instead of quietly
       bottoming out on the longest name. */
    overflow-wrap: anywhere;
    background: var(--md-sys-color-background);
    border: 1px solid var(--mud-palette-table-lines);
    border-radius: 8px;
    padding: 14px 16px;
}

/* Which kind of leave. Quiet, because it labels the number rather than being it. */
.horus-pto-allowance-type {
    font-size: 13px;
    font-weight: 600;
    color: var(--horus-text-secondary);
    margin-bottom: 6px;
}

/* The figure, and the biggest thing in the box. */
.horus-pto-allowance-figure {
    font-size: 26px;
    font-weight: 700;
    line-height: 1.15;
    color: var(--md-sys-color-on-surface);
}

/* The "/ 25" half, deliberately smaller and quieter than the days SPENT beside it: they are two
   different facts sharing one line, and at the same size the eye can't tell which is which. */
.horus-pto-allowance-of {
    font-size: 18px;
    font-weight: 600;
    color: var(--horus-text-secondary);
    margin-left: 4px;
}

/* What's left — second in the hierarchy, so at full strength but small. */
.horus-pto-allowance-remaining {
    font-size: 13px;
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    margin-top: 6px;
}

/* Red only once it has actually gone negative — over budget is allowed, so this reports a fact
   rather than scolding. Same rule as the calendar's remaining figure. */
.horus-pto-allowance-remaining.is-over {
    color: var(--mud-palette-error);
}

/* Footnotes: the carried balance and the pending days. Smallest and quietest — they qualify the
   figure above without competing with it. */
.horus-pto-allowance-note {
    font-size: 12px;
    color: var(--horus-text-secondary);
    margin-top: 2px;
}

.horus-toolbar-icon {
    color: var(--horus-text-secondary);
    font-size: 20px;
    flex-shrink: 0;
}

/* A CLEARABLE NUMERIC FIELD overlaps its own clear button with its spin buttons, out of the
   box. Measured in the browser rather than guessed at, because the arithmetic is exact and
   explains the whole bug:

     - `.mud-input` is a flex row, `position: relative`.
     - `.mud-input-numeric-spin` is `position: absolute; right: 8px; width: 36px` — so it
       reserves 44px at the trailing edge, and being out of flow, nothing in the flex row
       knows it is there.
     - MudBlazor puts `me-6` on `.mud-input-clear-button`, i.e. `margin-inline-end: 24px`.

   24px against a 44px footprint leaves the clear button sitting 20px INSIDE the spin column —
   exactly the overlap seen on screen. The `me-6` is sized for an ordinary end adornment, which
   is in flow; the numeric spin is not.

   Global on purpose — every clearable numeric in the app has this (the country form's holiday
   limit and its per-type leave rows, the leave type's own default, the hour policy). Fixing it
   per screen would be the same override copied five times.

   `!important` is required, not defensive: `me-6` is one of MudBlazor's SPACING UTILITIES, and
   those ship as `.me-6 { margin-inline-end: 24px !important }` (read straight out of the loaded
   stylesheet). No amount of specificity beats an `!important` from a normal author sheet — only
   another `!important` does. A first pass without it changed nothing and measured the same -20px
   overlap. */
.mud-input:has(.mud-input-numeric-spin) .mud-input-clear-button {
    margin-inline-end: 36px !important;
}

/* `.vtl-search-input`: border:0, background:transparent — a plain inline input next to
   the icon, not a boxed/pill field. MudTextField's Text variant already drops the outlined
   box; this only needs to also drop its underline (MudBlazor's Text/underline variant
   still draws a `.mud-input-underline` bottom border on both the resting AND hover state
   by default). MudBlazor's own hover rule is
   `.mud-input-underline:hover:not(.mud-disabled):before` — :hover and :not() each count
   toward specificity same as a class, so it beats a plain `:before`/`:after` override
   (same gotcha as the pill-input and nav-link-active rules earlier this session — confirmed
   the hard way each time: a lower-specificity override loses silently, no error). Matching
   that exact selector (not just the resting :before/:after) plus !important as a safety
   net is what actually removes the line in every state. */
.horus-inline-search {
    flex: 1 1 240px;
    min-width: 160px;
}

.horus-inline-search .mud-input {
    background: transparent;
}

.horus-inline-search .mud-input-underline:before,
.horus-inline-search .mud-input-underline:after,
.horus-inline-search .mud-input-underline:hover:not(.mud-disabled):before {
    border-bottom: none !important;
}

/* A plain vertical rule between the search box and the status filter group — makes it
   read as two distinct controls sharing a row, not one continuous field. */
.horus-toolbar-divider {
    align-self: stretch;
    width: 1px;
    background-color: var(--md-sys-color-outline-variant);
    flex-shrink: 0;
}

/* A labelled filter group in a listing toolbar: a small uppercase caption to the LEFT of
   the control (a bare control with no caption reads as decoration, not a filter). A first
   attempt stacked the label ABOVE it — that made the group taller than the single-line
   search field next to it, so the toolbar row grew to fit and looked inflated compared to
   every other row. Inline instead: same height as the search field, vertically centered
   against it via the row's own `align-items: center`.

   Named for the job, not for one filter: it started as the Active/Inactive status filter's
   own class and now also captions the review inbox's month selector, which is a different
   control (arrows + menu) doing the same job in the same row. */
.horus-filter-group {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

/* A filter-bar field that does not need to grow: a date or a threshold take up what they
   take up, and left loose they stretch until they push the rest out of the row. It is the same
   cap Settlements.razor had been setting inline; it lives here as a class because there are three fields. */
.horus-filter-narrow {
    max-width: 180px;
    flex-shrink: 0;
}

/* A footer INSIDE a `.horus-section`, which has no padding of its own because the grid fills it
   edge to edge. The 16px horizontal are the ones from `.mud-table-cell`, so the text starts
   aligned with the first column instead of stuck to the edge; the top border separates it from the
   last row so it does not read as one more row. */
.horus-grid-footnote {
    padding: 12px 16px;
    border-top: 1px solid var(--md-sys-color-outline-variant);
}

/* A column that takes what it needs and does not stretch: `width: 1%` on a table cell is the
   idiom for "the least that fits you" (see the note on `.horus-invoice-form-lines`). Without this
   the grid splits by eye and a column of two short dates ends up with the width of an amount. */
.horus-cell-fit {
    width: 1%;
    white-space: nowrap;
}

/* A row that LABELS a block inside a subtable —the credit notes of a comprobante, below its
   lines—. It goes in as a table row and not as loose text underneath: what the subtable has to
   say goes inside its frame, just like its empty message. The top rule is what separates it
   from the previous block without opening a gap. */
.horus-subtable-grouprow {
    padding-top: 12px;
    border-top: 1px solid var(--mud-palette-table-lines);
    font-weight: 600;
    text-align: left;
    color: var(--mud-palette-text-secondary);
}

/* The filter bar of a subtable, aligned WITH the subtable. `.horus-subtable` brings
   `margin: -12px 56px`, so a filter row with the panel's padding ended up 40px to the
   left of the table it filters. The 56px put it on the same vertical.

   And the 28px at the bottom are 16 of separation plus the 12 the table moves up with its negative
   margin: without that the filters ended up touching the first row. */
.horus-subtable-toolbar {
    margin: 0 56px 28px;
    flex-wrap: wrap;
    row-gap: 8px;
}

/* What opens when a row is expanded. ALMOST NOTHING ON TOP: the expanded cell already comes detached
   from the row that opened it, so the 16px of a `pa-4` added to that and the panel started out floating
   far from its own row. Bottom and sides yes, which is where the block closes. */
.horus-expanded-panel {
    padding: 4px 16px 16px;
}

/* A MudDatePicker measures 52px even though its visible box measures 40: `.mud-input-control` brings
   `margin: 8px 0 4px` (measured). With `align-items: flex-end` the button rested on the edge of the
   WRAPPER and ended up 4px below the box you see.

   The fix is not to take 4px off the button but to give it THE SAME BOX SHAPE as the fields next
   to it: with the same vertical margins, `flex-end` rests the three inner boxes on the same
   line, and if MudBlazor changes those margins the three move together. */
.horus-subtable-toolbar .horus-toolbar-action {
    margin: 8px 0 4px;
}

/* Two dates in one cell, one above the other. They are read together —"billed in August, collected
   in June" is the question— and two date columns would each take the width of an amount. */
.horus-stacked-dates {
    display: flex;
    flex-direction: column;
    gap: 2px;
    line-height: 1.35;
}

.horus-stacked-dates > div {
    display: flex;
    gap: 6px;
    justify-content: space-between;
}

.horus-filter-label {
    color: var(--horus-text-secondary);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    line-height: 1;
    margin: 0 4px 0 8px;
    flex-shrink: 0;
}

/* Status filter as a 2-chip multi-select (Active/Inactive) — not a dropdown/select. The
   reference's own `.vtl-filter-chip` is a compact outlined pill (32px tall, 20px radius,
   auto width) — MudBlazor's own chip default is close (32px tall) but 16px radius;
   bumped to match. Unselected chips read as plain outline (no fill) — same "quiet by
   default" principle as the row-action icon buttons above. */
.horus-status-chips {
    flex: 0 0 auto;
    gap: 8px;
}

/* MudBlazor's own MudChip carries a default `margin: 4px` on ALL sides. Left unchecked,
   that put an extra, invisible 4px between the last chip and the row's own 20px right
   padding (24px total) while the label on the opposite side had no such hidden margin —
   the exact "asymmetric spacing" reported: the row declares the same 20px padding on
   both sides, but the chip's own margin was quietly adding to only one of them.
   Neutralized here (horizontal only — vertical margin still centers the chip nicely in
   the row); `gap: 8px` on the chip-set above now owns 100% of the inter-chip spacing
   instead of splitting it invisibly between two adjacent chips' margins. */
.horus-status-chips .mud-chip {
    border-radius: 20px;
    margin-left: 0;
    margin-right: 0;
}

/* Row hover — a faint Primary wash. The reference uses a fixed blue at 6%
   (`.vtl-issue-table tr:hover td`) regardless of its own brand color; deliberately not
   copied literally here — a hardcoded unrelated blue would look like a mistake in an
   otherwise red-branded app. Tied to our own Primary instead, same 6% strength.

   `tr:hover > td` (direct child), NOT `tr:hover td` (any descendant) — bug found live
   on Clients.razor, confirmed via getComputedStyle, not just by eye: with the plain
   descendant combinator, hovering ANY row of the nested rate-history table (itself
   inside a `<td>` of the OUTER grid's hierarchy-expanded row) put `:hover` on that
   OUTER wrapper `<tr>` too (bubbling always reaches every ancestor), and
   `tr:hover td` matched EVERY `<td>` anywhere under that ancestor — not just the one
   row actually under the cursor, but the wrapper cell itself AND every other row of
   the nested table at once, all tinted identically together. `>` restricts the match
   to a `<tr>`'s own immediate `<td>` children, so only the row genuinely under the
   cursor lights up, at any nesting depth — matches this app's real DOM everywhere
   (MudBlazor never puts anything between a `<tr>` and its `<td>`s, that would be
   invalid table markup), so this is a strict correctness fix, not a behavior change,
   for every other page already using this rule (Roles/Features/Projects/etc.).
   `:not(.mud-table-child-content)` on top of that: the wrapper `<tr>` still has this
   one direct `<td>` child of its own (MudDataGrid's hierarchy-expansion cell, see the
   `.horus-section .mud-table-child-content` rule below) — excluded so that cell keeps
   its own deliberate canvas-gray background (explicit user request) instead of
   flashing pink the moment the mouse enters anything nested inside it. */
/* `:not(.horus-no-row-hover)` on the grid's own root: a grid whose rows carry INPUTS opts out
   (explicit request). The row-wide highlight fires while somebody is typing and reads as feedback
   about the field they are in — hover belongs on rows you pick, not on rows you fill.

   The opt-out has to live HERE and not in the component's `Hover` parameter: this rule paints the
   hover for every table inside a `.horus-section` regardless of what MudBlazor was told, so
   `Hover="false"` did nothing. Two grids had it set and hovered anyway (/settlements' salaries and
   /admin/pto-balances), each with a comment claiming otherwise. */
.horus-section :not(.horus-no-row-hover) > .mud-table-container tbody tr:hover > td:not(.mud-table-child-content) {
    background-color: color-mix(in srgb, var(--md-sys-color-primary) 6%, transparent);
}

/* `.vtl-issue-table th`: padding 10px 16px, uppercase, 12px, letter-spacing 0.06em,
   low-emphasis color, background = the SAME token as the page canvas (`--color-bg-
   surface-02`) — not a mix/tint hack. That's the reference's actual technique for a
   "distinct header band": reuse the canvas tone verbatim inside the (Surface-colored)
   card, since canvas and card are already two different tones by design (see
   HorusMudTheme.cs) — no color-mix needed here anymore. */
/* The column-header TREATMENT (uppercase, 12px, 600, 0.06em, low emphasis) is shared with the
   notification popover's title — explicit user request that it read as a table header. Declared
   once here for both, rather than copied into the popover's block: these four values are the
   thing that has to stay identical, and two copies is how they stop being. What's specific to a
   real table cell (the band background and the cell padding) stays in the rule below, since in
   the popover both come from its own header band. */
.horus-section .mud-table-container thead th.mud-table-cell,
.horus-notification-header .mud-typography {
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--horus-text-secondary);
}

.horus-section .mud-table-container thead th.mud-table-cell {
    background-color: var(--md-sys-color-background);
    padding: 10px 16px;
}

/* `.vtl-issue-table td`: padding 12px 16px, 14px. MudBlazor's own DataGrid cell padding
   varies by internal rule specificity — pinned explicitly here rather than relying on
   whichever built-in rule happens to win. */
.horus-section .mud-table-container tbody td.mud-table-cell {
    padding: 12px 16px;
    font-size: 14px;
}

/* Pager footer: a hint of the canvas tone (same token the header uses at full strength)
   so it doesn't read as just another row, but at 40% — noticeably lighter than the
   header band, since it's a UI chrome strip, not a labeled column heading. */
.horus-section .mud-table-pagination-toolbar {
    background-color: color-mix(in srgb, var(--md-sys-color-background) 40%, var(--md-sys-color-surface));
}

/* Inline form (RoleFormPanel/prototype) header/footer bands — explicit user request so
   the form visually matches the table it pushes down. Same two tones the grid already
   uses above (no new values): header at thead th's exact intensity (plain
   --md-sys-color-background), footer at the pager's exact intensity (40% mixed with
   Surface) — but without the column-header typography (uppercase/12px makes no sense
   for a form title like "Nuevo rol", that's a column label, not a heading).
   .horus-section's overflow:hidden (above) is what clips these bands to the card's
   rounded corners, no border-radius needed here. */
/* .horus-section-header/-footer are the same bands under a name that isn't tied to forms —
   the timesheet grid uses them for its week selector and its "Add row". The
   .horus-inline-form-* names are kept as-is so the existing form screens don't churn.
   Geometry is shared; the header BACKGROUND is the one thing that differs, so it's split
   below — a form header sits above white form fields and needs the tone to separate them,
   while a section header sits above a tinted content area and has to be the lighter of the
   two (explicit user request). */
/* The wrapper InlineFormHost scrolls into view when an admin listing's form opens. The
   scroll-margin is what makes it stop a bit ABOVE the form's own top edge (explicit user
   request) instead of flush against the content area: scrollIntoView treats the element as
   if it were this much taller on top, so the form lands with a strip of breathing room over
   its header rather than glued to the toolbar. Expressed in CSS rather than as an offset in
   the JS on purpose — the browser applies it natively for any block alignment, so it keeps
   working whether the scroll resolves to 'nearest' or 'start'.
   Only the admin listings: the timesheet panels scroll themselves and are deliberately
   untouched (see InlineFormHost.razor.js). */
.horus-inline-form-host {
    scroll-margin-top: 16px;
}

.horus-inline-form-header,
.horus-section-header {
    /* Vertical-only shrink (explicit request) — horizontal stays at 24px to line up
       with the MudForm's own padding (pa-6) below. Border-bottom matches the exact
       color that already separates table rows/header (var(--mud-palette-table-lines)). */
    padding: 8px 24px;
    border-bottom: 1px solid var(--mud-palette-table-lines);
}

.horus-inline-form-header {
    background-color: var(--md-sys-color-background);
}

.horus-section-header {
    background-color: var(--md-sys-color-surface);
}

/* Named because the suggestion scroller's fade-out has to end in exactly this colour — see
   .horus-ts-has-more::after. Defined here, next to its only other use, rather than in
   md3-tokens.css: it's a component tone, not a palette role. */
.horus-inline-form-footer,
.horus-section-footer {
    --horus-section-footer-bg: color-mix(in srgb, var(--md-sys-color-background) 40%, var(--md-sys-color-surface));
    background-color: var(--horus-section-footer-bg);
    padding: 8px 24px;
    border-top: 1px solid var(--mud-palette-table-lines);
    display: flex;
    justify-content: flex-end;
    gap: 16px;
}

/* The one place the two diverge: a FORM footer trails with Save/Cancel on the right, a
   SECTION footer leads with its action ("Add row") on the left. */
.horus-section-footer {
    justify-content: flex-start;
    align-items: center;
    flex-wrap: wrap;
}

/* All text buttons (MudButton — "Nuevo rol", "Nueva feature", Cancelar/Guardar, etc.)
   from here on: bolder text, no shadow. Explicit user request, extended from a
   one-off ask (the inline form's Cancelar/Guardar) to the whole app — deliberately
   global, not scoped to one screen. `:not(.mud-icon-button)` excludes MudIconButton
   (row Editar/Eliminar/Permisos, the theme/language menu, the drawer toggle): those
   have no text (font-weight is a no-op) and never had their own shadow, so they didn't
   need touching — excluding them makes the selector state exactly what it controls.
   Gilroy only ships 3 static weights (see the longer comment on this above, near the
   "Sistema" tag/tooltips) — 700 and 600 are the SAME file (Gilroy-Bold.ttf, mapped to
   font-weight 600-700 in fonts.css), either works. box-shadow:none removes the
   elevation MudBlazor paints by default on Variant.Filled — Variant.Text/Outlined had
   no shadow of their own already, so this is a harmless no-op for those. */
.mud-button-root:not(.mud-icon-button) {
    font-weight: 700;
    box-shadow: none;
}

/* Text buttons (Cancel in the inline form, Cancel/Delete in confirmation
   MudMessageBoxes) had LESS horizontal padding than Filled/Outlined by MudBlazor's own
   default — explicit user request, "they don't have the same padding as the filled
   ones". Confirmed via document.styleSheets: .mud-button-text = 6px 8px vs
   .mud-button-filled = 6px 16px vs .mud-button-outlined = 5px 15px — three different
   horizontal values for what are, in this app, variants of the same button size.
   Matches Text's horizontal to Filled's (16px) — vertical (6px) already matched, no
   need to touch it. Doesn't touch .mud-icon-button: those use a completely separate
   size class (mud-icon-button-size-*), never .mud-button-text. */
.mud-button-text {
    padding: 6px 16px;
}

/* A MudMenu used as a page's header action sits on the subtitle's baseline, like the plain
   MudButton every other listing puts there. MudBlazor ships `.mud-menu { align-self: center }`,
   which quietly overrides the container's own `align-items` — measured on /reports/time, the
   Exportar menu came out 12px above the bottom edge its neighbours share (a 37px control
   centred against a 62px title+subtitle block).
   Scoped to `.align-end` — the utility class MudStack renders for `AlignItems.End`, and in this
   app that combination means exactly one thing: the header band at the top of a page, title and
   subtitle on the left, the action on the right. Two classes out-specify MudBlazor's single one,
   and this file loads after theirs, so no !important. */
.align-end > .mud-menu {
    align-self: flex-end;
}

/* AppBar/Drawer as Surface panes instead of flat AppbarBackground/DrawerBackground
   (both = Background in HorusMudTheme.cs) — explicit user request after noticing the
   collapsed rail (ClipMode="Always", see MainLayout.razor) looked like it was
   "floating" with no background panel delimiting it from the rest of the page: since
   Background and DrawerBackground are intentionally the same tone, a rail with no
   color of its own didn't stand out from the page behind it.
   Solid, not translucent — a first version used `color-mix(in srgb, Surface 50%,
   transparent)` for an MD3 "glass" effect, but with .mud-main-content now being its
   own scroll context (see that rule below), content ends up VISUALLY behind the
   AppBar/Drawer while scrolling, not just below it in the layout — translucency let
   that content show through, exactly the bug the user flagged ("content shows through
   under the top bar"). `color-mix(X 50%, transparent)` only scales X's alpha — the
   result at alpha=1 is simply X with no blend against anything behind it, so the fix
   is direct: same token, no color-mix.
   Borders: 1px solid var(--mud-palette-table-lines) — the exact color that already
   separates table rows (MudBlazor doesn't expose TableLines on the theme for us to
   set directly, but it's the real variable .mud-table-cell already consumes, so
   reusing it instead of inventing our own gray guarantees it matches the tables in
   any theme). Bottom on the AppBar, right on the Drawer — the border separating each
   from the content it frames.
   Specificity: same 1-class selector MudBlazor.min.css uses for these two rules
   (`.mud-appbar`/`.mud-drawer`) — since this file loads later in <head>, it wins the
   tie without needing !important (same pattern as the rest of this file). */
.mud-appbar {
    background-color: var(--md-sys-color-surface);
    border-bottom: 1px solid var(--mud-palette-table-lines);
}

.mud-drawer {
    background-color: var(--md-sys-color-surface);
    border-right: 1px solid var(--mud-palette-table-lines);
}

/* .mud-main-content becomes the real scroll container instead of <html>/<body> —
   explicit user request: the visible scrollbar should start below the top bar, not at
   the very top. AppBar/Drawer are already position:fixed (MudBlazor default);
   MudBlazor's own `padding-top: var(--mud-appbar-height)` on this element only pushes
   the INNER content down while the element's own box (and the scrollbar it would draw
   once it scrolls) still starts at y=0 — margin-top instead of padding-top actually
   moves the box itself below the AppBar, so its own native scrollbar spans exactly
   from there to the bottom of the viewport. `!important` on padding-top only: this
   file loads after MudBlazor.min.css so background-color/height/overflow already win
   on specificity alone (same pattern as the rest of the file), but MudBlazor's own
   padding-top rule is itself duplicated across several breakpoint-scoped selectors of
   equal specificity — the last one in source order would normally win, and relying on
   "whichever MudBlazor rule happens to be declared last" is fragile against a MudBlazor
   version bump reordering them, so this is the one exception in the file that forces
   the point. */
.mud-main-content {
    margin-top: var(--mud-appbar-height);
    padding-top: 0 !important;
    height: calc(100vh - var(--mud-appbar-height));
    overflow-y: auto;
    box-sizing: border-box;
}

/* MD3 navigation drawer — container is a neutral surface tone now (see HorusMudTheme.cs
   DrawerBackground), NOT Primary: the m3.material.io/components/navigation-drawer/specs
   page doesn't put a brand color on the container at all, only on the active indicator
   (secondary-container, below). A first pass here copied a reference project's solid
   brand-red rail — that reference itself deviates from spec on this one point; matching
   it exactly wasn't actually "doing MD3", just "doing that reference". Insetting the
   whole row of links via `padding` on the LIST container (`.mud-navmenu`), with each
   link spanning the full (now-inset) width, is still correct and kept as-is — that part
   does match the reference's own technique. A first attempt at THAT used `margin: 4px
   8px; width: auto` directly on `.mud-nav-link`, which broke it: MudBlazor's own base
   rule is `.mud-nav-link{width:100%}`, and `width: auto` on that (still `display:flex`)
   element made it shrink to fit its own content instead of the row's — hover/active only
   painted a wash around the label, not across the row. Fixed by moving the horizontal
   inset to the container and keeping the link's own width at 100%.
   Shape: MD3's active indicator is a full pill ("full" shape, stadium) — 100px is safely
   above half the item's own height at any of our sizes, same formula as the buttons. */
.mud-navmenu {
    padding: 8px;
    box-sizing: border-box;
}

.mud-nav-link,
.mud-nav-group > .mud-nav-link {
    padding: 12px 16px;
    border-radius: 100px;
    margin: 0 0 4px;
    width: 100%;
    box-sizing: border-box;
}

/* Nested items (Roles/Features under Administration) only need a LIGHT indent to read as
   "belongs to the group above" — MudBlazor's own nested-item rule
   (`.mud-nav-group * .mud-navmenu .mud-nav-item .mud-nav-link`, 4 classes) sets
   `padding-left: 36px`, which read as "mucho padding" once the group also got its own
   background panel (the panel already does most of the work of showing hierarchy — the
   indent is just a light reinforcement now, not the only cue). Matching that same
   4-class chain (not just `.mud-nav-link`, which loses to it) to bring it down to 20px;
   !important because this exact specificity trap has already bitten a plain match
   without it elsewhere in this file. */
.mud-nav-group .mud-navmenu .mud-nav-item .mud-nav-link {
    padding-left: 20px !important;
}

/* Inactive text/icon is already the correct tone at full opacity via DrawerText/DrawerIcon
   (on-surface-variant, set in HorusMudTheme.cs) — no alpha-reduction override needed here
   anymore now that the container isn't a solid brand color (the old 82%-white treatment
   existed specifically to make white-on-red read as "quieter" than the active state; MD3
   doesn't call for that on a neutral surface, on-surface-variant already reads as
   secondary text on its own).

   Hover: a plain state-layer wash, on-surface at 8% — the spec's own convention for hover
   feedback on a neutral surface (this is the same "tint of the container's own ink"
   technique used for the icon-button hover wash above, just spelled out with on-surface
   directly since the drawer isn't tied to `currentColor` the same way a button is). Text
   color doesn't change on hover per spec — the wash alone is the affordance. */
.mud-nav-link:hover:not(.active) {
    background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
}

/* Active indicator: MD3 spec calls for `secondary-container` / `on-secondary-container`
   here — not a translucent wash of the container's own color, and not Primary (that's
   reserved for high-emphasis actions like buttons/FABs, not passive nav state). Matching
   MudBlazor's own more specific selector
   ".mud-nav-link.active:not(.mud-nav-link-disabled)" (the :not() counts toward
   specificity same as a class) is what actually makes this override win — confirmed the
   hard way earlier this session with the previous (also-overridden) wash version. */
.mud-nav-link.active:not(.mud-nav-link-disabled) {
    background-color: var(--md-sys-color-secondary-container) !important;
    color: var(--md-sys-color-on-secondary-container) !important;
}

.mud-nav-link.active:not(.mud-nav-link-disabled) .mud-nav-link-icon {
    color: var(--md-sys-color-on-secondary-container) !important;
}

/* MudNavGroup's expand/collapse chevron: MudBlazor's own rule
   `.mud-nav-link-expand-icon.mud-transform { fill: var(--mud-palette-primary); }` repaints
   the icon Primary-red the moment the group expands (`.mud-transform` is added along with
   the 180° rotation). Originally that was flat-out invisible (DrawerBackground WAS Primary
   red, so it was red-on-red) — the drawer isn't colored anymore, so it wouldn't vanish
   either way now, but it would still show up as a stray off-palette red accent among an
   otherwise neutral+tonal drawer, which is just as wrong. `fill` on the SVG wins over
   `color` for icon components that set it explicitly, so both need overriding, with
   !important since the built-in rule already carries one dimension of extra specificity
   (2 classes). Force it to the drawer's own (now on-surface-variant) icon color in every
   state, matching the rest of the inactive drawer icons. */
.mud-nav-link-expand-icon,
.mud-nav-link-expand-icon.mud-transform {
    color: var(--mud-palette-drawer-icon) !important;
    fill: var(--mud-palette-drawer-icon) !important;
}

/* The expanded group's children sit on their own panel, distinct from the drawer's own
   neutral canvas. A first version used a flat, opaque Surface swap here (the same trick
   used for .horus-section) — in dark mode that reads fine (a real, if fairly big, jump
   from Background T10 to Surface T17), but in light mode it meant jumping from a soft
   light-gray drawer (T96) all the way to pure white (T100) in one hard-edged,
   square-cornered rectangle — no in-between step, so it read as a stark white box glued
   onto the sidebar rather than a subtle grouping cue.
   Fix: a translucent on-surface wash over the drawer's own Background instead of a solid
   color swap — self-adjusting per theme the same way the hover wash above does (on-surface
   is dark ink in light mode, so it gently DARKENS/tints the panel; it's light ink in dark
   mode, so it LIGHTENS it, same direction as before). Started at 7% (verified: landed
   within ~1 tone step of the old Surface value in dark, much softer than stark white in
   light) — dialed down to 5% on user feedback once seen live, still enough to read as a
   distinct panel without the darker value's slightly heavier feel. Rounded corners
   (matching the app's own DefaultBorderRadius, 12px, from HorusMudTheme.cs) plus
   `overflow: hidden` so it reads as a soft floating panel instead of a flush rectangle —
   it already sits inset from the drawer's true edges via the ancestor .mud-navmenu's own
   8px padding, so the radius has a visible gap to show against, no extra margin needed
   here.
   Dark-mode-only bump (light stays at the 5% tuned above) — explicit user request after
   seeing it live: in dark mode, on-surface is LIGHT ink over an already-dark Background,
   so the same 5% that reads well in light (dark ink lightening a light background) is
   barely perceptible there — it needs a higher percentage for the same perceived tone
   jump. Tested live at 15%, settled on 10% (still clearly visible, a bit lighter). Same
   `:root[data-theme="dark"]` mechanism md3-tokens.css uses for its own tokens — not
   MudBlazor's `IsDarkMode`, since this color is built from our own `--md-sys-color-*`,
   not `--mud-palette-*`.

   `margin-bottom: 4px` — bug report: top-level nav spacing was inconsistent. Measured
   live with getBoundingClientRect(): every top-level `.mud-nav-link` pair (Home→
   Administration, etc.) is separated by the 4px from that rule's own `margin: 0 0 4px`
   (above in this file) — but this children panel, lacking that same margin, sat flush
   (0px) against the next top-level item when it's the last visible thing in an expanded
   group (e.g. "Administration" open right above "Projects"). Same value as everything
   else so spacing stays uniform whether the previous element is a plain link or an
   expanded group. This margin is UNCONDITIONAL — always 4px, whether the group is open
   or closed — it's the ONLY gap between a group and the next top-level item (see
   `.horus-nav-group-collapsed` below, which instead turns off the HEADER LINK's own
   margin when appropriate, so two 4px gaps never stack). */
.mud-navgroup-collapse {
    background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 5%, var(--md-sys-color-background));
    border-radius: 12px;
    overflow: hidden;
    margin-bottom: 4px;
}

:root[data-theme="dark"] .mud-navgroup-collapse {
    background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 15%, var(--md-sys-color-background));
}

/* `.horus-nav-group-collapsed` — class set by NavMenu.razor.cs (GroupClass) from the
   component's own `_administrationExpanded`/`_projectsExpanded` bool, NOT from
   MudCollapse's internal classes (`.invisible`/`.entering`/`.exiting`/`.entered`). Two
   earlier attempts used those classes and both proved unreliable: verified live with
   getBoundingClientRect() that closing a group via a real click (not on initial page
   load) leaves the panel on the transient `.mud-collapse-exiting` class — it never
   reaches `.invisible` — even once its real height is already 0px, so any CSS selector
   tied to those classes is broken about half the time depending on when it's measured.
   The C# bool has no such problem: it's the component's real source of truth, no
   ambiguous in-between states.

   Why this is needed ON TOP OF the unconditional margin above: an expanded group needs
   TWO separate gaps — (1) between the header link and its own children panel (so
   "Administration" and "Roles/Features" don't look glued together) and (2) between the
   panel and the next top-level item (this file's original bug). (2) is always solved by
   the unconditional margin above. (1) comes from the general `.mud-nav-link{margin:0 0
   4px}` rule — the header link is ALSO a `.mud-nav-link`, so it already gets that gap
   for free while the group is expanded. The problem is only when the group is CLOSED:
   the panel is 0px tall then, so gap (1) is invisible (nothing to separate), but its
   4px margin still pushes — stacked with the unconditional margin from (2), that's 8px
   instead of 4px before the next item. This rule turns off specifically that (1) margin
   when the C# state says the group is closed — never when expanded, so as not to lose
   the (1) gap that's genuinely needed there. */
.horus-nav-group-collapsed > .mud-nav-link {
    margin-bottom: 0;
}

/* Mini rail, closed only: the subitem panel's icons (Roles/Features) sat flush against
   the panel's own top/bottom edges — no breathing room between the group header above
   and the panel, or between the last icon and the panel's bottom edge. Vertical only
   (explicitly not horizontal — the icons are already centered edge-to-edge with the
   hamburger above via the .mud-nav-link rules further down, adding horizontal padding
   here would just re-introduce that misalignment). MudBlazor's Collapse component
   measures this container's own natural height for its expand/collapse animation, so
   padding added here is picked up automatically — no separate height fix needed.

   `margin-bottom: 0` overrides the base rule's `margin-bottom: 4px` — the mini rail's
   own convention (established earlier in this file, see the `!important margin:0 auto`
   rules further down) is that top-level rows sit flush with ZERO gap between them, not
   the open-drawer's 4px rhythm; every gap in the icon-only rail is 0 by design,
   including this one. */
.mud-drawer-mini.mud-drawer--closed .mud-navgroup-collapse {
    margin-bottom: 0;
    padding: 8px 0;
}

/* Complements `.horus-nav-group-collapsed` above, for the mini rail: without this, a
   CLOSED group in the rail (Administration closed, showing only its icon) still left
   the rule above's `padding: 8px 0` (16px) between its own icon and the next one,
   because that padding is fixed — unlike margin, padding always takes up space even
   at 0px height, regardless of whether the panel has visible content. With the group
   genuinely closed (per the C# bool, not MudCollapse's class), that breathing room
   isn't needed — there's nothing to separate — so it's zeroed out, matching the same
   0px gap that separates any other pair of items in the rail. When the group IS
   expanded inside the closed rail (e.g. navigating to /admin/clients with the rail
   collapsed to icons), this rule doesn't apply (it lacks the class) and the padding
   above still gives real breathing room around the visible nested icons. */
.mud-drawer-mini.mud-drawer--closed .horus-nav-group-collapsed .mud-navgroup-collapse {
    padding: 0;
}

/* Mini drawer (MainLayout.razor: Variant="DrawerVariant.Mini" + OpenMiniOnHover), CLOSED
   state only (".mud-drawer--closed" — the 56px icon-only rail; hovering flips MudBlazor's
   own class to ".mud-drawer--open" and restores the full 240px layout below, so none of
   this applies there — verified live: nothing needs fixing in the hover-expanded state).
   Two real overflow bugs found by measuring getBoundingClientRect() in the closed rail,
   both invisible until this reported "submenu looks cut off" bug: our own full-width-
   drawer styling (nested-item indent, the expand chevron) doesn't know the rail has
   shrunk to 56px, so both bleed past its right edge instead of being clipped or
   reflowing — MudBlazor's own overflow:auto on .mud-drawer doesn't hide them because
   they overflow their FLEX ROW, not the drawer's scroll box.
   1) The NavGroup header's own expand/collapse chevron measured at x 48-72px — 16px past
      the rail's 56px edge. There's no room for it in a 56px rail anyway (MudBlazor's
      hover-to-open already covers "let me see/toggle this group"), so hide it outright.
   2) Nested items' (Roles/Features) padding-left:20px (set above, for the light indent
      under an OPEN 240px group panel) leaves only 16px of the rail's ~36px-wide nested
      link box for a 24px icon — measured overflowing to x 60px, 4px past the rail edge
      AND 8px past its own link box.
   A first fix for #2 dropped that padding-left to 8px, landing the nested icon at the
   same x-position (24-48px) as the top-level Home/Administration icons — no longer
   overflowing, but user feedback on that live fix ("look uncentered") turned out
   to be right: x 24-48px is NOT the rail's true center. Measured precisely via
   getBoundingClientRect(): rail is 0-56px (center 28px), but EVERY icon — top-level
   and nested alike — had its own center at x=36, a consistent 8px rightward drift.
   Root cause for BOTH cases is the same shape: the padding-left values above (16px
   general, 20px nested-under-8px-fix) were sized against the icon having a LABEL to
   its right filling the rest of an open 240px row — with that label now display:none
   (mini rule below) there's nothing to its right anymore, so any nonzero, non-
   symmetric left/right padding just shoves the icon off from wherever centered would
   actually land, and by how much depends on the exact ancestor .mud-navmenu nesting
   depth (why top-level and nested needed different-looking numeric "fixes" that both
   secretly landed on the same wrong x=36).
   Real fix: stop reasoning about padding-pixel arithmetic per nesting depth entirely —
   MudBlazor's own mini-rail rule already makes every .mud-nav-link a flex row
   (`.mud-drawer-mini .mud-nav-link{display:flex;...}`), so zero the left/right padding
   and let `justify-content: center` center the one remaining visible child (the icon)
   directly, for top-level AND nested links in one rule. This can't drift again if the
   nesting depth or icon size ever changes — flexbox centers whatever's actually there.
   Verified live: icon center now lands at x=28 (exactly the rail's own center) for
   Home, Administration, Roles, and Features alike. */
.mud-drawer-mini.mud-drawer--closed .mud-nav-link-expand-icon {
    display: none;
}

.mud-drawer-mini.mud-drawer--closed .mud-nav-link,
.mud-drawer-mini.mud-drawer--closed .mud-nav-group .mud-navmenu .mud-nav-item .mud-nav-link {
    padding-left: 0 !important;
    padding-right: 0 !important;
    justify-content: center;
}

/* Centering the icon inside the rail (above) got them right relative to EACH OTHER,
   but user feedback comparing against the appbar's own hamburger button ("they're
   offset from the hamburger menu button... they should look equally circular")
   found the real reference point was off: the hamburger is a true 48×48 circle
   (measured live: MudIconButton Size.Medium, border-radius 50%) sitting at x 12-60
   (center 36) — but the rail was only 56px wide (center 28) with nav-link boxes
   39×48 (top-level) or 24×40 (nested), i.e. neither circular (width ≠ height) nor
   the same size as the hamburger, and centered on the rail's own 28px center rather
   than the hamburger's 36px one.
   Widening the rail to 72px isn't an arbitrary rounder number — it's chosen so that
   CENTERING a 48px box in it lands exactly on x=36, matching the hamburger's own
   center with no per-item margin math: 72/2 = 36. Forcing every nav-link (top-level
   AND nested, whose padding/height math differ enough that they'd otherwise land at
   different sizes — 48 vs 40 tall, verified live) to an explicit 48×48 box makes
   `border-radius: 100px` (set earlier, on the general .mud-nav-link rule) resolve to
   a true circle instead of a stadium, matching the hamburger's own circle exactly.
   `margin: 0 auto` centers that fixed-width box inside .mud-navmenu's own 100%-wide
   flex column — auto-computed, so it stays centered even if navmenu's own 8px
   padding or the rail width ever change again, no hardcoded left-offset to keep in
   sync by hand.
   `flex-shrink: 0` is load-bearing, not defensive filler — without it the nested
   case (Roles/Features) silently stayed 39px wide despite the width:48px !important
   above actually winning the cascade (confirmed live via document.styleSheets).
   Root cause: nested links live inside `.mud-nav-item`, itself a flex item of its
   own row, and THAT wrapper's width is still only ~39-40px (unaffected by any of
   this — it's sized by the nested .mud-navgroup-collapse panel, not by the drawer's
   width directly). A flex item's default flex-shrink:1 means an explicit `width`
   is only a *starting point* the browser is free to shrink to fit a narrower
   parent, and 48 > 39 forced exactly that shrink. Top-level links never hit this
   because their own `.mud-nav-item` wrapper happens to already be wide enough
   (~56px) — same bug, just not triggered there, which is why it wasn't caught
   until this got compared side-by-side against nested. flex-shrink:0 makes the
   width non-negotiable regardless of the parent's own box, for both cases.
   Verified live: nav-link box now lands at x 12-60, i.e. flush with the
   hamburger's own left/right edges, not just center-aligned to it — true for
   Home, Administration, Roles, and Features alike.
   Scoped to `:root`, not `.mud-drawer-mini` (a first version scoped it there) — the
   drawer itself isn't the only thing that reads this variable. MudBlazor's own CSS
   also sets `.mud-main-content`'s `margin-left` from this exact same variable when
   the rail is closed, but `.mud-main-content` is a SIBLING of `.mud-drawer`, not a
   descendant — a custom property set ON the drawer element only cascades to ITS
   OWN descendants, so main-content never saw the override and kept computing its
   margin from the un-overridden 56px default. Result (user-reported, confirmed by
   measuring live): the gutter between the rail and the page content was 24px in
   the open state but only 8px collapsed — visibly tighter, not the same spacing
   at both widths. `:root` is a shared ancestor of the drawer AND main-content (and
   the appbar), so every rule that reads this variable resolves consistently no
   matter which sibling it's declared on. Verified live: container's own left edge
   now lands at x=72 (matching the drawer's real width), gutter back to 24px in
   both open and closed states.
   `!important` is required here, not stylistic — MudBlazor itself injects a runtime
   `<style>` block containing its own `:root{--mud-drawer-width-mini-left:56px;...}`
   default (confirmed via document.styleSheets: an inline sheet, no href, distinct
   from this linked file), and that block gets appended to <head> AFTER this
   stylesheet loads. Same selector (:root), same specificity, later in the cascade
   — MudBlazor's own default was winning the tie even though this rule matched
   first, no ordering trick fixes that short of !important. */
:root {
    --mud-drawer-width-mini-left: 72px !important;
}

.mud-drawer-mini.mud-drawer--closed .mud-nav-link,
.mud-drawer-mini.mud-drawer--closed .mud-nav-group .mud-navmenu .mud-nav-item .mud-nav-link {
    width: 48px !important;
    height: 48px !important;
    flex-shrink: 0 !important;
    margin: 0 auto !important;
}

/* Sizing the nested link box fixed the WIDTH, but `margin: 0 auto` above still isn't
   enough to align nested items (Roles/Features) with the hamburger: measured live,
   they landed at x 16-64 instead of the hamburger's own x 12-60 — a 4px rightward
   drift that top-level items (Home/Administration) don't have (they land at
   11.5-59.5, correctly matching). Root cause: `.mud-navmenu{padding:8px}` (declared
   near the top of this file) is a plain class selector with no depth scoping, so it
   applies to BOTH the drawer's outer navmenu (giving top-level items one 8px inset)
   AND the nested navmenu inside .mud-navgroup-collapse (giving nested items a
   SECOND, stacked 8px inset on top of the outer one — 16px total instead of 8px).
   That extra 8px shrinks the nested .mud-nav-item wrapper down to 39px, and
   centering a 48px box (wider than its own 39px container) inside a flex row makes
   the "auto" margins resolve to 0 instead of negative, so the box just sits flush
   against the container's own (already 16px-inset) left edge instead of truly
   centering — hence the observed 4px drift (16 vs the correct 12).
   Zeroing the nested navmenu's OWN padding — but only the copy that's a descendant
   of a .mud-nav-group, not the outer one — brings the nested wrapper back to the
   same ~55px width the top-level one has, so the exact same `margin:0 auto` rule
   above produces the exact same centered result for both, no separate position
   value to hand-tune per nesting depth. Verified live: Roles/Features now land at
   x 11.5-59.5, matching Home/Administration and the hamburger's own 12-60 (the
   0.5px is float rounding from the auto-margin division, present equally on both —
   not a real misalignment). */
.mud-drawer-mini.mud-drawer--closed .mud-nav-group .mud-navmenu {
    padding: 0 !important;
}

/* The rail keeps its full 72px even once the nav list is taller than the viewport — explicit
   user request after seeing the icons jump left as soon as the drawer started scrolling.
   `.mud-drawer` is the scroll container (MudBlazor's own `overflow: auto`), and a classic
   scrollbar is an INSIDE gutter: measured live at a 320px-tall viewport, it took 12px off the
   drawer's clientWidth (72 → 60), which the 8px `.mud-navmenu` padding and the `margin: 0 auto`
   above then re-centred inside — landing the 48px link boxes at x 8-56 instead of the 11.5-59.5
   they sit at everywhere else, i.e. no longer aligned with the hamburger they are supposed to
   line up under, and with a 4px gutter on the right where there should be 12.
   Hiding the scrollbar rather than reserving room for it (`scrollbar-gutter: stable` would
   only make the 12px loss permanent, at every height): the rail is a column of icons with no
   horizontal room to give, wheel and trackpad still scroll it, and each icon's own tooltip is
   what names it — there is no text to lose track of. Both properties are needed: Firefox reads
   `scrollbar-width`, Chromium/WebKit the pseudo-element.
   Scoped to the CLOSED mini rail on purpose — expanded, the drawer is 240px wide and 12px of
   scrollbar costs it nothing, so the affordance stays where it can be afforded. */
.mud-drawer-mini.mud-drawer--closed {
    scrollbar-width: none;
}

.mud-drawer-mini.mud-drawer--closed::-webkit-scrollbar {
    display: none;
}

/* Tooltips — more contrast + more breathing room, explicit user request after seeing
   them live (the "Cannot delete..." tooltip on the admin grids). MudBlazor's
   own default (`.mud-tooltip-default`) is a flat mid-gray (`--mud-palette-gray-darker`,
   #616161) with white text — decent but not far from the rest of the app's own
   mid-tone grays, and it's a FIXED gray that doesn't actually invert for dark mode (it
   stayed the same #616161 whether the app was in light or dark, verified live), so in
   dark mode it barely stood out from its surroundings.
   Fixed by swapping to MD3's own Inverse Surface/On-Surface pair instead (added to
   md3-tokens.css) — the one MD3 role whose entire purpose is "pop out of the current
   theme, deliberately", which a tooltip fundamentally needs to do since it renders on
   TOP of arbitrary content. Auto-inverts per theme (light gets a near-black tooltip,
   dark gets a near-white one), unlike the flat gray it replaces.
   Padding: vertical only (explicitly not horizontal — a one-off user request), from
   MudBlazor's own 4px up to 8px, still using the base rule's `8px` for horizontal (not
   touched here, so no need to restate it).
   Selector is `.mud-tooltip.mud-tooltip-default` (2 classes), not the single
   `.mud-tooltip-default` it might look like it should be — MudBlazor's own rule for
   this exact property uses that 2-class selector, and a 1-class version of this rule
   silently lost the specificity tie despite loading later (confirmed live via
   document.styleSheets: the computed background stayed the old gray until matching
   that specificity exactly) — the same specificity trap that's bitten several other
   overrides in this file. */
.mud-tooltip.mud-tooltip-default {
    background-color: var(--md-sys-color-inverse-surface);
    color: var(--md-sys-color-inverse-on-surface);
}

.mud-tooltip {
    padding-top: 8px;
    padding-bottom: 8px;
    /* "Just a bit" bolder, per the user — but Gilroy only ships 3 static files (Medium
       400-500, Bold 600-700, ExtraBold 800-900, see fonts.css), not a weight
       continuum: there's no real value between "Medium" and "Bold" to ask the browser
       for, any 600-700 renders the exact same Gilroy-Bold.ttf glyphs. 500 (MudBlazor's
       default, already at the ceiling of the Medium file) → 600 (the smallest step
       that crosses into Bold) is the only real increment available. */
    font-weight: 600;
    /* MudBlazor ships 12px, which is smaller than any body text in the app — and a tooltip is
       read once, quickly, often at the edge of where somebody is looking. Explicit request to
       raise it, applied globally rather than per screen: a tooltip that needs its own size on
       one page is a tooltip that was too small everywhere.

       No !important needed. MudBlazor's own rule is `.mud-tooltip` (one class), the same
       specificity as this one, and this stylesheet loads after — the tie breaks our way. That
       is worth stating because several overrides in this file DO need the extra weight, and the
       difference is always whether MudBlazor used a multi-class selector or an !important. */
    font-size: 14px;
}

/* The "Sistema" tag a bit bolder — same font limit as the comment above (3 static
   weights only), starting from a lower baseline too (chips had no font-weight of
   their own, inheriting MudChip-content's general 400 — Gilroy Medium). Scoped to
   this specific class (added on the MudChip itself via Class=), not to
   .mud-chip-content in general — the request was only for the system tag, bolding
   every chip in the app (e.g. the filter's Activo/Inactivo) wasn't asked for. */
.horus-system-chip .mud-chip-content {
    font-weight: 600;
}

/* ---- Fill of EVERY `Variant.Text` chip, by COLOUR and not by class ----
   MudBlazor paints the background of its Text variant with the colour at 6% alpha, which composited
   over our cards gives 1.06:1 in light and 1.10:1 in dark: the label reads, the pill does not exist.
   Reported by Pablo in both themes and measured afterwards.

   **It goes by colour selector and not by an opt-in class**, which is how it was for a while: with a
   hand-applied `horus-state-chip*` class, the next green chip somebody adds on another screen comes out
   invisible again, and "remembering the class" is not a system. Now `Variant.Text` + `Color.X` just
   comes out right, with nothing to remember — same criterion as the row hover or the pill buttons,
   which are also our own rules over MudBlazor's default.

   Three colours because they are the three the app uses today. **A new colour needs its own
   measurement**: the percentages do NOT carry over — with green's, info's blue ended up with
   its text at 4.50:1, right on the line (see the tokens). */
.mud-chip.mud-chip-text.mud-chip-color-default {
    background-color: var(--horus-chip-bg);
    color: var(--horus-chip-text);
}

.mud-chip.mud-chip-text.mud-chip-color-success {
    background-color: var(--horus-chip-success-bg);
    color: var(--horus-chip-success-text);
}

.mud-chip.mud-chip-text.mud-chip-color-info {
    background-color: var(--horus-chip-info-bg);
    color: var(--horus-chip-info-text);
}

.mud-chip.mud-chip-text.mud-chip-color-warning {
    background-color: var(--horus-chip-warning-bg);
    color: var(--horus-chip-warning-text);
}

/* The OUTLINED chip — today the one for the comprobante a cash row collects, and the app's first.

   A GLOBAL rule per variant and not a screen class, for the same reason as the four above: an
   opt-in class means the next outlined chip somebody adds comes out with the 1px border and the
   default weight, and "remembering the class" is not a system.

   Two changes over MudBlazor, both requested: **a 1.5px border**, because at 1px over a dense grid
   the pill is hinted at more than seen, and **600**, which in Gilroy is the Bold file (the family
   has three weights and no regular one: 400-500 falls on Medium, 600-700 on Bold). Without the weight,
   a chip with no fill reads weaker than the text next to it, which is exactly the opposite of what
   a chip is there to do. */
.mud-chip.mud-chip-outlined {
    border-width: 1.5px;
    font-weight: 600;
}

/* The date chip of the exchange quotes. It only removes the MARGIN (MudBlazor puts 4px on each side,
   which would add to the `gap` of the strip and of the label) and adds tabular figures; the padding,
   the height and the font size are those of MudBlazor's Small chip, the SAME as the month's
   status chips.

   For a while it had a smaller size of its own (padding 1px 6px, 0.7rem) to fit the dense grid,
   and Pablo noticed right away: "those info chips have very little padding... the Cerrada or
   Borrador one seems to have more". A chip is a chip — the row grows 6px and that's it. */
.horus-rate-card-chip.mud-chip.mud-chip-size-small {
    margin: 0;
    font-variant-numeric: tabular-nums;
}

/* Row action icon buttons (Roles.razor / Features.razor — Edit/Delete/Permissions) —
   an explicit size between MudBlazor's built-in Small (32px button / 20px icon, what
   these rendered at before) and Medium (48px / 24px, the topbar/sidebar icon-button
   size, e.g. ThemeToggle/LanguageToggle/the drawer toggle) — MudBlazor's Size enum has
   no built-in step in between, so this overrides the Small-size classes directly.
   Safe to target globally: Size="Size.Small" MudIconButtons are used ONLY on these two
   pages' row actions, nowhere else in the app (verified via grep before adding this).
   Both selectors need the .horus-row-action-btn class for its 2-class specificity to
   beat MudBlazor's own 1-class .mud-icon-button-size-small / .mud-icon-size-small —
   same rule that bit the tooltip background override earlier in this file. */
.horus-row-action-btn.mud-icon-button-size-small {
    width: 40px;
    height: 40px;
    padding: 9px;
}

.horus-row-action-btn .mud-icon-size-small {
    width: 22px;
    height: 22px;
}

/* A value that must never be broken across lines. Today: each date in the merged
   StartDate–EndDate "Período" column of ProjectAssignmentsSection — a wrapped
   "Desde 02/01/2023" reads badly and makes every row taller. Applied per VALUE (a span
   inside the cell), not to the whole cell via CellClass: locking the cell would make a
   full range the column's min-content and overflow the table (measured, see the comment
   on that CellTemplate). MudBlazor ships no nowrap utility (checked its own stylesheet:
   13 hardcoded white-space:nowrap declarations, no reusable class), hence this one.
   It is also safe directly on a <td> if a future column needs it that way: unlike a flex
   utility it doesn't touch `display`, so the cell stays a table-cell and keeps stretching
   to the row's height (see the wrapper-div comment on Roles.razor's Active column). */
.horus-cell-nowrap {
    white-space: nowrap;
}

/* A cell holding a FIGURE: right-aligned and tabular, so a column of numbers can be compared
   down its digits. `tabular-nums` here and NOT in a KPI — at hero size it makes a number look
   loose, because every digit gets the width of a zero (see .horus-hs-hero-value).

   Scoped to `.horus-section` rather than to one screen: it was `.horus-hs-num`, private to the
   hours dashboard, until the reconciliation report needed the same thing — the move
   `.horus-filter-group` and `.horus-kpi-*` already made. Every table in this app lives inside a
   `.horus-section`, and the 4-classes-plus-a-type selector is what beats MudBlazor's own
   `.mud-table-cell` alignment (its own is 1 class, but the grid's header rules are 3 classes +
   2 types — see the specificity note in Lineamientos-UI §3). */
.horus-section .mud-table-container th.mud-table-cell.horus-cell-num,
.horus-section .mud-table-container td.mud-table-cell.horus-cell-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* For the column that is a CONCLUSION rather than an input (a total, a utilization, a
   difference), so the eye lands on it first without the row needing a colour. */
.horus-section .mud-table-container td.mud-table-cell.horus-cell-num-strong {
    font-weight: 600;
}

/* A country's flag emoji, wherever one is shown next to its name (the country catalog, the
   holiday calendars listing and its detail heading, the users grid).

   The font stack is explicit rather than left to the browser's own fallback: Gilroy carries no
   emoji glyphs at all, and naming the platform emoji fonts keeps the pair of regional-indicator
   characters resolving to one flag instead of two tofu boxes on a machine with an unusual font
   configuration. Windows ships NO flag glyphs in Segoe UI Emoji, so Chrome and Edge there render
   the two letters ("UY") — a readable fallback, and the reason the country NAME is always beside
   the flag rather than replaced by it (see CountryDisplay.FlagEmoji).

   A fixed line-height because emoji metrics differ from the surrounding text's: without it the
   rows carrying a flag sit a pixel or two taller than the ones that don't, and a listing where
   only some rows have a country (the users grid) visibly stripes. */
.horus-country-flag {
    font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
    font-size: 1.125rem;
    line-height: 1.25rem;
}

/* The unlabelled flag column in the users listing. Takes what the glyph needs and no more, so
   the width goes to the name and the email — the same call the import preview's expander,
   checkbox and row-number columns make (.horus-import-narrowcol), kept as its own class because
   that one is named for a screen this isn't.

   The padding override needs THREE classes to outrank MudDataGrid's own cell padding — a
   single-class rule loses and the column silently keeps its 16px a side, which around a
   ~20px glyph is more padding than content. Header and cell are listed separately because the
   grid classes them independently (HeaderClass/CellClass). */
.horus-flag-col {
    width: 1px;
    white-space: nowrap;
}

.horus-section .mud-table-container thead th.horus-flag-col,
.horus-section .mud-table-container tbody td.horus-flag-col {
    padding-left: 16px;
    padding-right: 0;
}

/* The country filter in the holiday-calendars toolbar. A fixed basis rather than letting it grow:
   this toolbar has no search box to absorb slack (a calendar has no text of its own to search), so
   without a width the autocomplete stretches across the row and pushes the year picker off the
   end. Wide enough for the longest names in either language plus the flag. */
.horus-country-filter {
    flex: 0 0 auto;
    min-width: 220px;
}

/* Displayed text that carries its own line breaks. HTML collapses "\n" to a space, so a
   description imported (or typed — the manual entry field is multi-line too) as several lines
   renders as one long run without this. `pre-line` and not `pre-wrap`: the line breaks are real
   content, but runs of spaces and stray indentation from whatever exported the file are not.

   Generic on purpose: any place that shows a user-written description has the same problem, and a
   second declaration of the same rule is how two of them start disagreeing. Used by the import
   preview and by the monthly view's day detail. */
.horus-preserve-lines {
    white-space: pre-line;
}


/* Clients.razor's HierarchyColumn toggle (custom CellTemplate, see the comment there) —
   a single icon (HorusIcons.KeyboardArrowDown, the same chevron NavMenu already uses)
   instead of two separate open/closed glyphs. Closed state rotates it -90° to read as
   "chevron pointing right" (6 o'clock -> 3 o'clock) — same trick MudBlazor itself uses
   for MudNavGroup's own expand chevron (.mud-nav-link-expand-icon.mud-transform, see
   above), just driven by our own state (context.Open) instead of MudBlazor's. Targets
   the real .mud-icon-root class MudBlazor renders internally (confirmed against the
   installed package's own stylesheet), not a guessed one. transform-origin: center so
   it spins in place instead of around the icon's top-left corner. */
.horus-hierarchy-toggle-closed .mud-icon-root {
    transform: rotate(-90deg);
    transform-origin: center;
}

/* Clients.razor's expanded rate-history row (ChildRowContent) — explicit user
   request: it should read as "sunken" instead of floating, with the same tone as the
   page's real background. The background goes on `.mud-table-child-content` (the
   real <td> MudDataGrid builds for the expanded content, confirmed by inspecting the
   DOM — not on a div of our own inside it): that <td> is the one with the native
   padding (12px 16px), so painting an inner div instead of it left a frame of
   `.horus-section`'s Surface color (the real ancestor) peeking through that padding —
   reported and fixed live. No padding added on purpose (explicit request, that was
   the old pa-4) — the MudSimpleTable inside (its own Class="horus-section
   horus-subtable") already reads as a mini white card sitting on this gray
   canvas, the same pattern as the main card sitting on the page's own background.
   --mud-palette-background, NOT --md-sys-color-background: these are two DISTINCT
   variables that happen to resolve to the same hex today on purpose
   (HorusMudTheme.cs Background = "#F3F3F7"/"#1A1C1E", duplicated by hand in
   md3-tokens.css — see the "lives duplicated in 3 places" note in
   horuserp-conventions/SKILL.md), but the one that actually paints THIS page's
   background is MudBlazor's own (generated by PaletteLight/PaletteDark), not ours —
   explicit user request after inspecting the real rule. */
.horus-section .mud-table-child-content {
    background-color: var(--mud-palette-background);
}

/* Any mini table sitting inside an expanded row — the rate history of a client or a project, a
   project's tasks, its assignments, and the file rows behind a merged import line. One definition
   rather than one per screen: the four are the same object in the same place, and the name says
   the role instead of naming whichever screen got here first (it used to be
   .horus-rate-history-table, which stopped being true the second time it was used).

   The MudSimpleTable above reuses .horus-section for the Surface background + shadow,
   but explicit user request: without the border or rounded corners .horus-section
   brings by default (a border of its own floating inside a panel that already has its
   own visual boundary doesn't make sense), and with a negative vertical margin
   (-12px) that compensates for MudDataGrid's own padding on the ChildRowContent cell
   (.mud-table-child-content, 12px 16px — see the rule above) — without this there was
   a double gap above/below between that native padding and this table. 56px
   horizontal (bumped up from 16px — explicit user request, more breathing room on the
   sides than the native padding alone gives) so it doesn't sit flush against the
   cell's edges. Same specificity as .horus-section (1 class each) — wins by source
   order, since this rule comes later in the file. */
.horus-subtable {
    border: none;
    margin: -12px 56px;
    border-radius: 0;
}

/* Edit row-action icon color — explicit user request: "navy" (the brand's neutral
   seed, #0D1721 — see the Neutral comment in HorusMudTheme.cs), not a MudBlazor
   Color="..." (Primary/Error looked nearly identical to each other — the original
   complaint —, Info didn't land well, Secondary turned into a pale, off-palette pink
   in dark mode). Uses the --md-sys-color-on-surface token instead of the raw hex: it
   resolves to #1A1C1E in light (navy in practice), and inverts to #E2E2E5 in dark —
   the raw #0D1721 hardcoded would go nearly invisible against the dark background
   (#1A1C1E, practically the same navy). */
.horus-edit-icon-btn {
    color: var(--md-sys-color-on-surface);
}

/* PROTOTYPE — inline-form pattern (Roles.razor for now): while the form panel above the
   listing is expanded, the grid card underneath is inert. pointer-events:none blocks
   every click/hover on it (search, filters, switches, row actions); the ::after scrim
   is a LEVE (explicit user request) on-surface tint, not a heavy modal-style dark
   overlay — just enough to read as "disabled", the same job a modal backdrop does but
   without covering the whole screen or floating above the page's own flow. position:
   relative on the wrapper is what gives the absolutely-positioned scrim something to
   anchor to; border-radius matches .horus-section's own 8px so the tint doesn't square
   off past the card's rounded corners. */
.horus-listing-disabled {
    position: relative;
    pointer-events: none;
}

.horus-listing-disabled::after {
    content: "";
    position: absolute;
    inset: 0;
    background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 6%, transparent);
    border-radius: 8px;
    pointer-events: none;
}

/* The one control under a scrim that has to stay live — the monthly day panel's day arrows,
   whose job is precisely to leave the open form (they ask before dropping changes). Lifted
   above the ::after tint rather than carved out of it, so it reads as the active thing in a
   dimmed panel. Use sparingly: anything exempted here bypasses what the scrim is protecting. */
.horus-listing-disabled .horus-scrim-exempt {
    position: relative;
    z-index: 1;
    pointer-events: auto;
}

/* EmptyState.razor — MudDataGrid NoRecordsContent on every admin listing, explicit
   user request: a 64x64 icon "in some gray" + text. on-surface-variant is the same
   muted tone .horus-toolbar-icon/DrawerText already use — not a new gray of its own.
   font-size, not width/height: the component's svg is sized at 1em×1em below, the same
   mechanism .horus-toolbar-icon uses for its own 20px. Generous vertical padding —
   without it, it looks cramped against the grid's toolbar/header above, being the
   table body's only real row. */
.horus-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 48px 24px;
    color: var(--horus-text-secondary);
}

/* The tinted disc behind the icon — explicit user request: "a circle in a very transparent
   shade of red, visually pink". The colour itself is --horus-empty-state-bg, defined with the
   rest of the palette in md3-tokens.css because its tint strength differs per theme; see that
   comment for the why, including why the value is opaque rather than a translucent layer. Only
   the icon stays gray — the tint is the ground, never the glyph.

   The disc is deliberately a WRAPPER around the svg rather than a background on the svg
   itself: the svg is sized in em (font-size, so the notification popover can shrink it with
   one declaration — see .horus-notification-body below), and padding measured in px on the
   same box as a 1em width is the kind of mixed-unit sizing that quietly breaks the next time
   the icon size changes. Here the ring thickness is the wrapper's padding and nothing else. */
.horus-empty-state-figure {
    display: inline-flex;
    padding: 24px;
    border-radius: 50%;
    background-color: var(--horus-empty-state-bg);
}

.horus-empty-state-icon {
    width: 1em;
    height: 1em;
    font-size: 64px;
    fill: currentColor;
}

/* Reads as the gap around the badge, so it is painted in the disc's colour, not in a
   background of its own. */
.horus-empty-state-badge-notch {
    fill: var(--horus-empty-state-bg);
}

.horus-empty-state-text {
    color: inherit;
}

/* HorusMultiAutocomplete (Components/Shared/) — "Gmail-style" multi-select, explicit
   user request. Doesn't clone MudBlazor's internal Outlined chrome (animated floating
   label, etc. — cloning that by hand is exactly the kind of thing this file already
   flagged as fragile against a version bump, see several comments above) — the user
   clarified the look doesn't need to be a MudSelect's, reading as a plain text field
   is enough: border + MD3 radius (same --md-sys-color-outline and 12px as every other
   Outlined input in the app, via DefaultBorderRadius in HorusMudTheme.cs) with a
   plain label above, not one animated inside the border. */
.horus-multiselect-label {
    display: block;
    color: var(--horus-text-secondary);
    font-size: 12px;
    font-weight: 600;
    margin-bottom: 4px;
}

.horus-multiselect-body {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    min-height: 40px;
    padding: 6px 12px;
    border: 1px solid var(--md-sys-color-outline);
    border-radius: 12px;
    box-sizing: border-box;
}

/* Focus ring — same pair color/width as MudBlazor paints on its own Outlined fields when focused (2px Primary), 
   so this box "matches" the rest of the form even if it doesn't share its markup. padding -1px compensates for 
   the border +1px (1px→2px) so the content doesn't jump when focused. */
.horus-multiselect-body:focus-within {
    border-color: var(--md-sys-color-primary);
    border-width: 2px;
    padding: 5px 11px;
}

.horus-multiselect-disabled .horus-multiselect-body {
    opacity: 0.38;
    pointer-events: none;
}

/* The search box has to share the row with the chips and only take what's left.
   TARGETS THE LAST DIRECT CHILD, not the class we hand the component: MudBlazor puts that class
   on an INNER element, so the actual flex item is its own wrapper — which comes with
   `flex: 1 1 auto` and a computed width of 100% of the box. Its hypothetical main size is
   therefore the whole row, and flex breaks lines on the hypothetical size, so it dropped below
   the chips the moment one existed and then grew to fill the new line (measured: it wanted 277px
   inside a 279px content box that already held a 109px chip — the box went from 40px to 84px
   tall with a single chip selected). A basis of 120px lets it sit beside the chips and wrap only
   when there genuinely isn't room. `:last-child` rather than `.mud-select` so this survives the
   next change to that internal markup — the chips ride ahead of it in a `display: contents`
   container, so the search box is always the last child. */
.horus-multiselect-body > :last-child {
    flex: 1 1 120px;
    min-width: 120px;
    width: auto;
}

/* The internal MudAutocomplete uses Variant.Text (without its own border/background) — this only
   trims the padding/height that it inherits from the same MudBaseInput base as
   any other field, so it feels "flush" within our own box instead of
   adding a second layer of air around the text. */
.horus-multiselect-input .mud-input-slot {
    padding: 0;
}

/* Text/underline variant still draws its own `.mud-input-underline` bottom border (resting
   AND hover) on top of `.horus-multiselect-body`'s own border above — same fix, same
   specificity trap as `.horus-inline-search` earlier in this file: MudBlazor's own hover
   rule is `.mud-input-underline:hover:not(.mud-disabled):before`, and :hover/:not() each
   count toward specificity same as a class, so a plain `:before`/`:after` override loses
   silently without also matching that exact hover selector (plus !important as a safety
   net). Global on purpose (this is the shared HorusMultiAutocomplete component, not a
   per-page override) — applies to every current/future use, not just Assignments. */
.horus-multiselect-input .mud-input-underline:before,
.horus-multiselect-input .mud-input-underline:after,
.horus-multiselect-input .mud-input-underline:hover:not(.mud-disabled):before {
    border-bottom: none !important;
}

/* Selected-item chips — explicit user request, a bit bigger and with more room before the
   close ("x") button than MudBlazor's own tight small-size defaults (12px font, 4px gap).
   Both need !important: `.mud-chip.mud-chip-size-small` (font-size) and
   `.mud-chip.mud-chip-size-small .mud-chip-close-button` (margin-left, also mirrored as
   margin-inline-start — same physical property in this app's LTR-only UI) both carry 3
   classes' worth of specificity, one more than a plain `.horus-multiselect-body .mud-chip`/
   `.mud-chip-close-button` descendant selector can outweigh on its own — same specificity
   trap as the underline fix right above. */
.horus-multiselect-body .mud-chip {
    font-size: 13px !important;
}

.horus-multiselect-body .mud-chip-close-button {
    margin-left: 8px !important;
    margin-inline-start: 8px !important;
}

.horus-multiselect-helper {
    color: var(--horus-text-secondary);
    font-size: 12px;
    margin-top: 4px;
    padding: 0 12px;
}

.horus-multiselect-error {
    color: var(--md-sys-color-error);
}

.mud-drawer .mud-drawer-content {
    padding: 8px 0;
}

/* Default MudExpansionPanel styling, app-wide — not scoped to a project-specific class,
   explicit user request after seeing it first on ProjectFormPanel's "Additional info"
   panel ("make this the default style"): every MudExpansionPanels in this app (also
   RoleFeatures.razor's per-module feature groups) gets the same border + header-band
   treatment as a table listing (.horus-section's outline/radius, `.horus-section
   .mud-table-container thead th.mud-table-cell`'s canvas-toned uppercase header)
   instead of MudExpansionPanel's bare default (no border of its own beyond a faint 1px
   separator line meant for stacking multiple panels, plain-weight text). No box-shadow
   here (unlike .horus-section) — explicit user request, a flat border reads better for
   a form-embedded panel than a floating-card shadow. Confirmed live via DOM/computed-
   style inspection (no .razor samples ship with the NuGet package, same reason
   HierarchyColumn/MudAutocomplete needed direct inspection earlier this phase): the
   header's chevron icon is an inline SVG with no explicit fill, inheriting
   `fill: currentColor` from a MudBlazor base rule — so recoloring it only takes setting
   `color` on `.mud-expand-panel-header`, no separate icon selector needed. */
.mud-expansion-panels {
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: 8px;
    overflow: hidden;
}

/* MudExpansionPanel's own `.mud-expand-panel-border` (a 1px bottom rule meant to
   separate stacked panels within one MudExpansionPanels group) stays for panels stacked
   above another one (e.g. RoleFeatures.razor's per-module groups) but is dropped on the
   last (or only) panel — otherwise that one draws a second, redundant line just inside
   the wrapper's own bottom border. */
.mud-expand-panel:last-child {
    border-bottom: none;
}

.mud-expand-panel-header {
    background-color: var(--md-sys-color-background);
    padding: 16px 14px 16px 16px!important;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--horus-text-secondary);
    min-height: unset;
}

.mud-expand-panel-content {
    padding: 16px;
}

/* MudNumericField's stepper (increment/decrement) — explicit user request: global
   trailing-icon consistency across all controls. Confirmed live via DOM measurement
   (Rate field vs. Client's dropdown chevron/Effective from's calendar icon on
   ProjectFormPanel): MudBlazor's own buttons are 28px tall each (24px `.mud-icon-root`
   + the button's own 2px top/bottom padding), stacked with `justify-content:
   space-between` inside a wrapper pinned `top:0;bottom:0` — since the two buttons'
   heights already sum to exactly the field's own height (56px), there's zero leftover
   space for `space-between` to distribute, so they silently fill the ENTIRE field
   edge-to-edge instead of reading as one small, centered unit — unlike every other
   trailing icon/adornment in this app (a single ~24px icon vertically centered with
   visible padding around it, via `.mud-input-adornment{height:.01em;...}`). Shrinking
   the icons/button padding gives `justify-content:center` real leftover space to work
   with, so the pair now groups into one small centered unit instead of stretching
   top-to-bottom. `right: 14px` (was `right: 0`) matches the exact horizontal inset
   every other trailing icon already sits at (measured live: 837px from viewport left
   on both, at 900px width) — without it the stepper sits flush against the field's own
   border instead of sharing the same right margin as a dropdown chevron/calendar icon. */
.mud-input-numeric-spin {
    justify-content: center !important;
    right: 2px !important;
}

.mud-input-numeric-spin button {
    padding: 0;
    height: 24px;
}

.mud-input-numeric-spin .mud-icon-root {
    width: 36px;
    height: 24px;
}

.mud-container {
    padding-bottom: 32px !important;
}

/* Generic weight utility — MudBlazor ships no font-weight helper, and MudText's Typo
   scale fixes weight per level, so bumping one label without changing its size needs a
   class. 700 because Gilroy ships Medium/Bold/ExtraBold only: 600 would be synthesised.
   Not timesheet-scoped despite where it started — used by the Timesheets month title and
   by the role name in RoleFeatures. */
.horus-strong {
    font-weight: 700;
}

/* ─────────────────────────────────────────────────────────────────────────────
   MD3 segmented button — a SHARED control, not timesheet-specific, which is why it
   sits above the Timesheets block. Two users today: the Weekly/Monthly view switch
   (Timesheets.razor, single-select) and the Active/Inactive status filter of the
   admin listings (Components/Shared/StatusFilter.razor, multi-select).
   Spec: m3.material.io/components/segmented-buttons/specs.

   MudToggleGroup's own look is Material 2: 12px shape, 31px tall at Size.Small, 13px
   labels, 3px padding, no state layers, and a weaker outline-variant border. Practically
   every spec'd value has to be restated here, so this block is long on purpose. What comes
   from the spec, in order: container 40dp tall with FULL (stadium) shape and a 1dp
   `outline` border; segments divided by that same 1dp outline; label-large typography
   (14sp / 500 / 0.1px tracking); selected segment filled `secondary-container` with an
   `on-secondary-container` label; unselected label `on-surface`; 12dp horizontal padding;
   18dp leading check icon; 8% state layers on hover.

   SelectedClass/CheckMarkClass are passed explicitly by the callers rather than guessed
   from MudBlazor's markup — both are documented parameters, so there's no internal class
   name to break against on a version bump.
   ───────────────────────────────────────────────────────────────────────────── */
.horus-segmented.mud-toggle-group {
    height: 40px;
    border-radius: 999px;
    border-color: var(--md-sys-color-outline);
    /* Clips the selected segment's fill to the pill — without it the fill squares off the
       rounded ends. */
    overflow: hidden;
    background-color: transparent;
}

.horus-segmented .mud-toggle-item {
    height: 100%;
    padding: 0 12px;
    border-radius: 0;
    font-size: .875rem;
    font-weight: 500;
    letter-spacing: .00625em;
    text-transform: none;
    color: var(--md-sys-color-on-surface);
    background-color: transparent;
}

/* Divider between segments: the spec uses the same 1dp outline as the container, not a
   lighter hairline. */
.horus-segmented .mud-toggle-item + .mud-toggle-item {
    border-left: 1px solid var(--md-sys-color-outline);
}

/* Bold label on the selected segment — explicit user request, a deliberate deviation from
   the spec (MD3 keeps label-large on both and lets the fill + tick carry the selection).
   700, not 600: Gilroy ships Medium/Bold/ExtraBold only, so 600 would be synthesised.
   The tick is bolded too, but that can't be done here — an SVG ignores font-weight — so it
   comes from a heavier GLYPH, HorusIcons.CheckBold, set in Timesheets.razor. */
.horus-segmented .horus-segmented-selected {
    background-color: var(--md-sys-color-secondary-container);
    color: var(--md-sys-color-on-secondary-container);
    font-weight: 700;
}

/* State layers — 8% of the segment's OWN label colour over its own container, which is why
   the two differ instead of sharing one wash. */
.horus-segmented .mud-toggle-item:hover {
    background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
}

.horus-segmented .horus-segmented-selected:hover {
    background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 8%, var(--md-sys-color-secondary-container));
}

/* 18dp tick with an 8dp gap to the label and nothing on its left — the segment's own 12dp
   padding already sets it off (spec). `horus-segmented-check` comes from the group's
   CheckMarkClass parameter — a documented hook — so this doesn't depend on MudBlazor's
   internal class names surviving a version bump.
   Qualified with .horus-segmented so it can outrank MudBlazor's own
   `.mud-toggle-item .mud-toggle-item-check-icon{margin:0 6px;font-size:20px}`: that's two
   classes, so a bare .horus-segmented-check loses to it and the gap comes out at 6px. */
.horus-segmented .horus-segmented-check {
    width: 18px;
    height: 18px;
    font-size: 18px;
    margin: 0 8px 0 0;
}


/* ─────────────────────────────────────────────────────────────────────────────
   Timesheets (/timesheets) — weekly grid + monthly calendar.

   The information hierarchy here reads in black/gray with WEIGHT doing the work,
   not in the brand red (explicit user request). That is now just "use the palette
   correctly" — it needs NO local colour system: the reddish cast that made these
   screens look wrong was a defect in the palette itself (neutralVariant seeded
   from the red instead of the navy — see HorusMudTheme.cs), fixed at the source,
   so text-secondary/lines-default are genuinely neutral app-wide.

   What remains local are the SURFACE TINTS with no MD3 role behind them — the
   weekend/hover planes of a calendar grid, and the emphasis fill on the Total column.
   They're built out of MD3 SURFACE roles (surface / surface-variant /
   secondary-container) rather than hardcoded, so they stay correct in both themes and
   don't override any global definition.

   Out-of-month days are NOT one of these either: they use --md-sys-color-background
   straight, on the reasoning spelled out at the .horus-ts-outside rule far below (short
   version: absent content, not a disabled control — and a disabled state layer inverts
   between themes here).
   ───────────────────────────────────────────────────────────────────────────── */
:root {
    /* A weekend cell is the middle of three tiers, and the two ends are fixed: a normal day
       is `surface`, an out-of-month day is the page canvas (`background`). So the whole
       range available is surface..background — only 12 luminance points in light and 14 in
       dark — and the only real choice is where inside it the weekend sits. Painted over the
       canvas, this mix IS that position: 0% = flush with out-of-month, 100% = flush with a
       normal day.

       Biased DOWN to 30% rather than centred at 50% (explicit user request for more
       weekday/weekend contrast). Centring maximises the smaller of the two gaps, which was
       right while the fill was all that separated the three tiers. It isn't any more: an
       out-of-month day now carries its own gray border (--horus-disabled-container) while
       every live cell is outlined warm, so it stays legible on the border alone and the
       fill budget can go where it's actually read. 30% buys the weekday step 6->8 in light
       and 7->10 in dark, at the cost of the weekend/out-of-month step 6->4 and 7->4.

       Below ~20% the weekend fill collapses into out-of-month and only the border would
       tell them apart — that's the floor. For a genuinely bigger step the palette's
       Surface/Background gap would have to widen, which is a global change. */
    --horus-ts-weekend-bg: color-mix(in srgb, var(--md-sys-color-surface) 30%, transparent);
    --horus-ts-hover-bg: color-mix(in srgb, var(--md-sys-color-surface-variant) 50%, transparent);

    /* secondary-container, not primary-container: both are #FFDAD6 in light, but in dark
       primary-container is #93000C, a saturated red that made the Total column the loudest
       thing on the screen and put it in competition with the Submit button.
       secondary-container is #5D3F3C there — same emphasis, even weight across themes. */
    --horus-ts-total-bg: var(--md-sys-color-secondary-container);
}


/* "Total: X" — explicit user request that these read heavier. */
.horus-ts-total {
    font-weight: 700;
    color: var(--mud-palette-text-primary) !important;
}

/* Only the NUMBER goes brand red, not the label (explicit user request) — which is why the
   two are separate elements now instead of one localised "Total: {0}" string.
   --horus-primary-text, not --md-sys-color-primary: the frozen brand red fails contrast as
   a foreground in dark, see that token's comment in md3-tokens.css.
   The parent's `color: ... !important` doesn't fight this: an inherited value always loses
   to a declaration made directly on the child, importance on the parent notwithstanding. */
.horus-ts-total-value {
    color: var(--horus-primary-text);
}

/* The MONTH total only (the page header) — the week and day totals keep the primary tone.
   Explicit user request: its label and its "de {target}" tail recede to secondary so the
   red actual total is the only thing competing for attention in a header that also carries
   the month picker, the status chip and the submit button. Still bold.
   Two classes deep AND !important because the base rule above is itself !important at
   0-1-0: a same-importance declaration wins on specificity, so 0-2-0 takes it. */
.horus-ts-total.horus-ts-total-month {
    color: var(--mud-palette-text-secondary) !important;
}

/* ...and the MONTH total's number is NOT red — explicit user request, narrowing the rule
   above it. The header already carries the status chip, the submit button and the month
   picker; the red made the one neutral fact in that row read like a warning, which the
   MonthHoursWarning icon right beside it is the actual carrier of.
   Scoped to the month only, deliberately: the week and day totals sit inside their own cards
   where the brand tone is the loudest thing present, and they were not part of the request.
   0-3-0 beats the 0-1-0 base rule; no !important needed, since that one isn't important. */
.horus-ts-total.horus-ts-total-month .horus-ts-total-value {
    color: var(--mud-palette-text-primary);
}

/* The warning icon: centred against the text, and off it (reported: it sat
   high and flush against the hours). */
.horus-ts-dayhours .mud-tooltip-root,
.horus-ts-monthcell-meta .mud-tooltip-root,
.horus-ts-total .mud-tooltip-root {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    margin-left: 2px;
    margin-bottom: 2px;
}

/* ── Weekly view ─────────────────────────────────────────────────────────────
   The grid now lives INSIDE a .horus-section, with the week selector in a header band and
   "Add row" in a footer band. Within that card it still reads flat: the day headers and
   the task-name column carry NO frame or fill — only the cells that hold data are boxed,
   so each task reads as one bar of data sitting under its column labels rather than a
   second panel nested in the card. table-layout:fixed + explicit widths on the first/last
   columns is what keeps the 7 day columns evenly sized regardless of task-name length.

   border-collapse must be `separate` for the rounded ends below to render at all —
   border-radius is ignored on cells of a collapsed table. That costs two things, both
   handled: vertical spacing separates the rows (so each task is its own rounded bar, which
   is the point) and .horus-ts-cell drops its left border (so the shared edge between two
   cells stays one line instead of doubling to two). */
/* The card has no padding of its own (.horus-section is a bare frame), so the grid brings
   its own breathing room. Top padding is small because border-spacing already opens a gap
   under the header band.

   The canvas tone, same as the page background (explicit user request: grid area tinted,
   header white, so the two contrast). This is load-bearing, not decoration: the weekend
   tint and the out-of-month fill are both defined RELATIVE to this tone — weekend is a 50%
   surface mix that composites over whatever sits behind it, and out-of-month is `background`
   itself. While this wrapper was transparent the cells composited over the card's white
   surface instead, which silently flattened the weekend tier to pure white (measured: 255
   for both a weekday and a Saturday). Tinting the wrapper is what restores the three-step
   ladder the grid is tuned around. */
.horus-ts-gridwrap {
    /* 24px horizontal, matching .horus-section-header/-footer above and below it — the grid
       sits BETWEEN those two bands inside the same card, so anything else reads as the
       content sliding out from under its own header (reported: the task column started 8px
       to the left of the week selector right above it). The vertical values stay asymmetric
       for the reason below; only the sides were ever wrong. */
    padding: 8px 24px 12px;
    /* The grid has a floor below which its columns can't hold their content — with hours
       rendered as "8h 30min" a day column needs ~72px, and squeezing past that made the
       value wrap onto a second line, which also pushed that one day's header taller than
       its neighbours. Scrolling sideways is the honest degradation for a dense grid;
       reflowing it isn't. */
    overflow-x: auto;
    /* Paired with the max-height JS sets while a cell's form is open (see
       TimesheetWeeklyView.razor.js): this is what actually hides the rows below the selected
       one. Must be an explicit `hidden` rather than left at `visible` — a box with one axis
       visible and the other scrollable has the visible axis promoted to `auto` by the spec,
       which would put a second, vertical scrollbar on the grid instead of clipping it. */
    overflow-y: hidden;
    background-color: var(--md-sys-color-background);
}

/* The animated collapse, shared by the weekly grid and the monthly calendar (see
   TimesheetCollapse.js, which caps max-height and reads this duration back off the element —
   so this declaration is the single source of truth for the timing).
   Only the timing is shared, not the clipping: .horus-ts-gridwrap needs overflow-x:auto for
   its own sideways scroll and hides just the Y axis, while the calendar has nothing to scroll
   and hides both. */
.horus-ts-collapsible {
    transition: max-height .22s ease;
}

.horus-ts-monthwrap {
    overflow: hidden;
}

/* Someone who asked the OS not to animate gets the same collapse without the motion — the
   rows still hide, instantly. */
@media (prefers-reduced-motion: reduce) {
    .horus-ts-collapsible {
        transition: none;
    }
}

/* The weekly card used to force `overflow: visible` here, because the cell editor was an
   absolutely-positioned popover that got clipped by .horus-section's own overflow:hidden on
   the last row (measured: 30px, including its confirm/cancel buttons). That popover is gone
   — the entry form is now a normal in-flow block at the bottom of the card — so the override
   and the two band border-radius rules that compensated for it are gone with it, and the
   card clips its bands to its rounded corners like every other .horus-section does. */

.horus-ts-grid {
    width: 100%;
    /* 240 (task column) + 7x76 (days) + 92 (total) — see .horus-ts-gridwrap's overflow. */
    min-width: 880px;
    border-collapse: separate;
    border-spacing: 0 4px;
    table-layout: fixed;
}

.horus-ts-grid th {
    background: transparent;
}

.horus-ts-taskhead,
.horus-ts-totalhead {
    padding: 0 12px 10px 0;
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--horus-disabled-content);
}

.horus-ts-taskhead {
    text-align: left;
    width: 240px;
}

.horus-ts-totalhead {
    text-align: center;
    width: 92px;
    padding-right: 0;
}

.horus-ts-dayhead {
    padding: 0 6px 10px;
    text-align: center;
    vertical-align: bottom;
    font-weight: 400;
}

.horus-ts-dayname {
    font-size: .75rem;
    font-weight: 500;
    text-transform: capitalize;
    color: var(--horus-disabled-content);
}

/* The date takes the QUIET tone and the day's total takes the loud one — the two swapped
   (explicit user request: "I want the total to stand out more than the date"). It reverses what this
   pair used to say, and the reasoning is that the date is scaffolding: which column you are in is
   already obvious from the day name above it and from the week selector, while the total is the
   number you are actually reading the row for.
   Only the COLOUR was swapped, which is what was asked. The date keeps its slightly larger size,
   so the two still read as different kinds of thing rather than one replacing the other. */
.horus-ts-daydate {
    font-size: .9375rem;
    font-weight: 600;
    text-transform: capitalize;
    color: var(--horus-text-secondary);
}

/* Matches .horus-ts-cell-value's size and weight (explicit user request: the day's total
   has to carry at least as much as a single cell), and now takes the full-strength tone as well —
   see the note on .horus-ts-daydate above for the swap. */
.horus-ts-dayhours {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    margin-top: 2px;
    font-size: .875rem;
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
}

.horus-ts-progress {
    margin-top: 6px;
}

/* The track is a LIGHTER STEP OF THE BAR'S OWN COLOUR, which is what MudProgressLinear does on
   its own — it paints the track as a `::before` on the ROOT element
   (`.mud-progress-linear-background` is a class ON the root, NOT a child element — verified live
   in the DOM) at 20% of the bar's palette entry. So there is deliberately NO override here.

   There used to be one, forcing a neutral track: with the bar in brand red, that 20% wash turned a
   week of untouched 0h days into seven red underlines. The bar is Tertiary now
   (see TimesheetDayProgress.ProgressColor), so the component's own behaviour became the desired
   one — a blue bar over a pale blue track, which is also what the data-viz rule asks for (the
   unfilled part is a lighter step of the same ramp, so the state reads across the whole bar).
   Removing the override rather than repainting it is the point: the reason for it was the red. */

/* The bar's warning fill, for the same reason as the icon: MudBlazor paints it with the
   full-strength amber, which measures 1.85:1 against the grid plane in light. Matching its own
   selector shape (equal specificity) and relying on load order — md3-components.css comes after
   MudBlazor.min.css — rather than on `!important`.

   The TRACK is deliberately left alone: MudBlazor paints it at 20% of the undarkened amber, which
   over the plane reads as a pale peach and still says "amber". A wash of the darkened tone would
   read as a warm gray. */
.horus-ts-progress.mud-progress-linear-color-warning:not(.mud-progress-linear-buffer) .mud-progress-linear-bar {
    background-color: var(--horus-warning-mark);
}

/* The warning amber that clears 3:1 on our surfaces — see --horus-warning-mark. Light darkens it;
   dark is MudBlazor's own, untouched.

   Was `.horus-ts-warn-icon`, private to the timesheet grids, until the reconciliation report
   needed the same mark next to a block's status — promoted rather than copied, the same move
   `.horus-filter-group`, `.horus-kpi-*` and `.horus-cell-num` already made. There is one amber
   mark in this app, and a second class for it would be a second amber the day one is adjusted. */
.horus-warn-icon {
    color: var(--horus-warning-mark) !important;
    font-size: 1rem !important;
}

/* The holiday / time-off mark on a day (DayAbsenceIcon). Muted by default: on most days it is a
   statement of fact, not a problem. When the day IS a problem the component also applies
   .horus-warn-icon, whose `!important` colour beats this one — the same mechanism the import
   grid's day flags already rely on, and the reason the amber lives in ONE class instead of being
   restated here. Size matches .horus-warn-icon so a day carrying both marks reads as one row
   of icons rather than two sizes. */
.horus-ts-absence-icon {
    color: var(--horus-text-secondary);
    font-size: 1rem !important;
}

/* Both containers are laid out for a TRAILING icon (see the .mud-tooltip-root rule above, which
   adds a left margin for the hours warning). The absence mark leads instead — it says what kind
   of day this is before the day's number — so it gives that margin back. */
.horus-ts-dayhours .mud-tooltip-root:first-child,
.horus-ts-monthcell-meta .mud-tooltip-root:first-child {
    margin-left: 0;
}

.horus-ts-taskcell {
    padding: 8px 16px 8px 0;
    vertical-align: middle;
}

/* Client > Project > ProjectTask, the three levels of the path a time entry belongs to.
   Project is the anchor (bold, full text colour) because it's what a person scans for;
   Client sits above it as context in the brand tone, and the task below as the detail.

   The task line leans on COLOUR, not weight, to read lighter: Gilroy ships Medium / Bold /
   ExtraBold and has no Light face, so anything under 500 would either snap back to Medium
   or get faked. 500 is the lightest real weight available, and the muted
   on-surface-variant is what actually carries the "quieter" reading. */
.horus-ts-task-client {
    font-size: .6875rem;
    font-weight: 600;
    line-height: 1.35;
    color: var(--horus-primary-text);
}

.horus-ts-task-project {
    font-size: .875rem;
    font-weight: 600;
    line-height: 1.35;
    color: var(--md-sys-color-on-surface);
}

.horus-ts-task-name {
    font-size: .8125rem;
    font-weight: 500;
    line-height: 1.35;
    color: var(--horus-text-secondary);
}

/* No padding on the cell itself — it lives on .horus-ts-cell-hit instead, so the click
   target is the WHOLE cell rather than the cell minus a dead 6px ring. The non-clickable
   states (out-of-month "—", the lock icon on a submitted month) don't need it: the cell is
   a fixed 56px with vertical-align:middle + text-align:center, so their content is centred
   by the cell, not by padding. */
.horus-ts-cell {
    position: relative;
    height: 56px;
    padding: 0;
    border: 1px solid var(--md-sys-color-outline-variant);
    border-left: none; /* the previous cell's right border is the shared edge — see below */
    background: var(--md-sys-color-surface);
    text-align: center;
    vertical-align: middle;
}

/* The two ends of a task's bar. Monday is addressed as "the cell right after the task
   label" rather than :first-child, because the label column is itself a <td>. These were
   the only square corners left in the grid. */
.horus-ts-taskcell + .horus-ts-cell {
    border-left: 1px solid var(--md-sys-color-outline-variant);
    border-top-left-radius: 8px;
    border-bottom-left-radius: 8px;
}

.horus-ts-rowtotal {
    border-top-right-radius: 8px;
    border-bottom-right-radius: 8px;
}

.horus-ts-cell-hit {
    box-sizing: border-box; /* padding must fit INSIDE the 100% box, not stretch past it */
    padding: 6px 2px;
    cursor: pointer;
    width: 100%;
    height: 100%;
    display: flex;
    /* Column: a saved start (OpenRangeMark) stacks under the hours instead of squeezing beside
       them in a ~90px cell. A cell with one child centres it exactly as before. */
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.horus-ts-cell-hit:hover {
    background: var(--horus-ts-hover-bg);
}

.horus-ts-cell-value {
    font-size: .875rem;
    font-weight: 600;
}

.horus-ts-cell-empty {
    color: var(--horus-disabled-content);
}

/* The padlock on a submitted/approved month's cells. Explicit user request: smaller, in a
   light grey, and with air between it and the hours below it.
   --horus-disabled-content, not .mud-text-secondary as before: on-surface at 38% is MD3's
   own disabled-content value, which is exactly what this icon means — the cell is locked,
   not merely de-emphasised. It's also the tone .horus-ts-cell-empty already uses for a day
   you can't type into, so the two states of "nothing to do here" now agree.
   font-size rather than a MudBlazor Size: MudIcon renders its svg at 1em×1em, so the font
   size IS the icon size (same mechanism .horus-toolbar-icon uses). Size.Small's 20px was
   bigger than the 14px hours it sits above; 16px lets the number lead. */
.horus-ts-lock {
    font-size: 1rem;
    color: var(--horus-disabled-content);
}

.horus-ts-rowtotal {
    font-size: .875rem;
    font-weight: 700;
    background: var(--horus-ts-total-bg);
}

/* One entry of the selected day, in the monthly view's day panel. Not a MudDataGrid: a
   day holds a handful of rows with no sorting, paging or filtering of its own — the same
   call ProjectDetail's small tables make. */
.horus-ts-dayentry {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 8px 0;
}

.horus-ts-dayentry + .horus-ts-dayentry {
    border-top: 1px solid var(--mud-palette-table-lines);
}

/* Client › Project › Task on one line. The three tone classes come from the weekly grid
   untouched — they set only typography, never display, so the same classes read as stacked
   blocks there and as inline spans here. */
.horus-ts-dayentry-path {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
}

/* The separator is generated, not typed into the markup: it belongs to the relationship
   between the parts, so a row with a missing client can't leave a dangling "›" behind.
   Shared with the suggestion chips' own two-part path (see .horus-ts-suggestion-path below) — one
   declaration so the character, its spacing and its tone can't drift between the two places that
   draw a path. */
.horus-ts-dayentry-path > * + *::before,
.horus-ts-suggestion-path > * + *::before {
    content: "›";
    margin: 0 4px;
    color: var(--horus-disabled-content);
    font-weight: 500;
}

/* "Project › Task" inside a suggestion chip. Same separator as the day-entry path above, but
   inline-flex and explicitly NO wrapping: a chip has to keep its natural single-line width, which
   is the whole basis of the scroller (see .horus-ts-suggestions .mud-chip — the chips overflow and
   therefore scroll precisely because nothing squeezes them). Reusing .horus-ts-dayentry-path here
   would have brought its `flex-wrap: wrap` along, which is right for a full-width row and wrong
   for a chip. */
.horus-ts-suggestion-path {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: nowrap;
    white-space: nowrap;
}

.horus-ts-dayentry-description {
    margin-top: 2px;
    font-size: .8125rem;
    line-height: 1.35;
    color: var(--horus-text-secondary);
}

/* A start saved without its end (OpenRangeMark, §70): the timer, plus the start in the weekly
   cell. Muted while the day runs — it's a note, not a problem — and in --horus-warning-mark once
   the day is over and it was left open (user decision), the amber the grids already use for
   "look at this day again". Text and icon move together because the icon fills with currentColor. */
/* Two classes on purpose: MudBlazor's .mud-tooltip-root.mud-tooltip-inline (inline-block) has two,
   and with one this rule lost silently — measured, the root stayed a block and the mark sat 3px high. */
.mud-tooltip-root.horus-ts-open-mark-root {
    display: inline-flex;
    align-items: center;
}

/* Same size and weight as the hours (.horus-ts-cell-value), explicit user request: it's the cell's
   content, not a footnote — the grey is what tells it apart. */
.horus-ts-open-mark {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: .875rem;
    font-weight: 600;
    line-height: 1;
    color: var(--horus-text-secondary);
    white-space: nowrap;
}

.horus-ts-open-mark-left {
    color: var(--horus-warning-mark);
}

.horus-ts-open-icon {
    font-size: 1rem !important;
}

/* DayStepper's pair of arrows: kept together as one inline unit so a wrapping header can't
   split them across lines. */
.horus-day-stepper {
    display: inline-flex;
    align-items: center;
}

/* One row of the entry form's "add by time range" calculator: two time fields sharing the
   width equally (flex: 1 1 0, Lineamientos-UI §2), then what the range adds and its remove
   button at their natural size. */
.horus-ts-span-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.horus-ts-span-row > .horus-ts-span-field {
    flex: 1 1 0;
    min-width: 0;
    position: relative;
}

/* The "now" button laid over the field's end, so it reads as inside it (user request) while being
   a real MudIconButton that can carry a MudTooltip — MudTextField's own adornment can't. The input
   gets the room an end adornment would have taken, so typed text never runs under the button; the
   selector has to outrank MudBlazor's .mud-input > input.mud-input-root-outlined (0-2-1), which
   sets the 14px and silently won over a two-class rule (measured). */
.horus-ts-span-field-now .mud-input > input.mud-input-root-outlined {
    padding-right: 40px;
}

/* Spans the OUTLINED BOX, not the wrapper: the control carries its own 8px top / 4px bottom margin
   (measured), so 50% of the wrapper sat 2px above the box's centre. */
.horus-ts-span-now {
    position: absolute;
    right: 6px;
    top: 8px;
    bottom: 4px;
    display: flex;
    align-items: center;
}

.horus-ts-span-row + .horus-ts-span-row,
.horus-ts-span-issue + .horus-ts-span-row {
    margin-top: 12px;
}

/* Wide enough for "+10h 30min" so the column doesn't jump as ranges are typed. */
.horus-ts-span-hours {
    min-width: 5.5em;
    text-align: right;
    font-weight: 700;
    white-space: nowrap;
}

.horus-ts-span-issue {
    display: block;
    margin-top: 4px;
}

/* Spans every column, so it's a bar on its own — rounded on BOTH ends, unlike a task row
   whose left end is the Monday cell. */
/* Padding dropped to 0: the EmptyState this now wraps brings its own generous vertical
   padding (see .horus-empty-state), and stacking both made the bar twice as tall as the
   same empty state on every listing screen. */
.horus-ts-norows {
    padding: 0;
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: 8px;
    background: var(--md-sys-color-surface);
    text-align: center;
    color: var(--horus-text-secondary);
}

/* Every period selector: the week range in the weekly grid's header band, the month in the
   /timesheets page header, the month FILTER in the review inbox's toolbar, and the two menus
   of the shared PeriodPicker (year + month, for the screens that reach across years). Each
   doubles as its picker's activator, so they share everything but size — which tracks how
   loud the period is at that level: the /timesheets month is the page's own heading (it
   replaced a Typo.h5), the week sits one level down inside the card, and the inbox's is one
   filter among others in a toolbar, so it takes ordinary control size and does NOT shout
   over the search box beside it. The PeriodPicker takes its size from whichever of these
   classes the call site passes it, which is why it adds no size of its own. */
.horus-ts-weekpicker .mud-button-root,
.horus-ts-monthpicker .mud-button-root,
.horus-review-monthpicker .mud-button-root,
.horus-period-picker-year .mud-button-root,
.horus-period-picker-month .mud-button-root {
    padding: 4px 8px;
    font-weight: 700;
    text-transform: none;
    color: var(--mud-palette-text-primary);
}

.horus-ts-weekpicker .mud-button-root {
    font-size: 1.125rem;
}

.horus-ts-monthpicker .mud-button-root {
    font-size: 1.5rem;
}

/* Inside a PeriodPicker the same class takes the WEEK picker's size, not the month label's.
   The period used to be one 1.5rem label ("Septiembre 2026"); as two controls it is 332px
   instead of 220, which at /timesheets pushed the page header's two groups to 1.164px of
   content inside a 1.164px band and wrapped the actions onto a second row (measured, not
   guessed). At 1.125rem the pair is 287px and the row closes with 44px to spare — enough for
   a wider status chip than "Borrador".

   It is also the honest size: this control was built to mirror the week selector sitting
   directly below it ("deliberately the same shape"), and now it is the same size too. The
   standalone MudMenu of the assignments Gantt keeps 1.5rem — it is a heading with nothing
   below it to match. */
.horus-ts-monthpicker.horus-period-picker-year .mud-button-root,
.horus-ts-monthpicker.horus-period-picker-month .mud-button-root {
    font-size: 1.125rem;
}

.horus-review-monthpicker .mud-button-root {
    font-size: 0.875rem;
    white-space: nowrap;
}

/* The two menus sit tight together so they read as one control split in two, not as two
   filters that happen to be adjacent. The gap is smaller than the toolbar's own so the pair
   stays visually bound. */
.horus-period-picker {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

/* ── Monthly view ──────────────────────────────────────────────────────────── */
.horus-ts-month {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 8px;
}

.horus-ts-monthhead {
    padding: 0 4px 4px;
    text-align: center;
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--horus-disabled-content);
}

.horus-ts-monthcell {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-height: 96px;
    padding: 8px;
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: 8px;
    background: var(--md-sys-color-surface);
    cursor: pointer;
    transition: background-color .15s ease;
}

.horus-ts-monthcell-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 4px;
}

/* A very faint tonal band across the top of each day (explicit user request), which gives the
   date and the day's total a strip of their own now that the cell also lists projects.
   secondary-container at 10% — the same tone the selected cell and the Total column use, just
   barely stated: strong enough to separate the two zones, quiet enough that a month of them
   doesn't turn the calendar into stripes. Being a mix with `transparent` rather than a flat
   colour, it also composites correctly over whatever the cell underneath is (plain surface,
   the weekend tint, or the selected fill).

   The negative margins are what make it a full-bleed BAND: the cell carries 8px of padding,
   so without them the strip would float inset on three sides. Padding is added back so its
   contents stay on the cell's own 8px rule, and the top corners are rounded to just under the
   cell's 8px so the band doesn't poke past its 1px border.

   In-month days only: the out-of-month cells are deliberately muted, and a tonal band would
   draw the eye to the days that matter least. */
.horus-ts-monthcell:not(.horus-ts-outside) .horus-ts-monthcell-head {
    /* The 4px bottom margin rides on top of the cell's own 4px flex gap, so the project lines
       start 8px under the band instead of hugging it (explicit user request). */
    margin: -8px -8px 4px;
    padding: 6px 8px;
    border-radius: 7px 7px 0 0;
    /* A neutral grey rather than the brand-tinted secondary-container (trying it out at the
       user's suggestion). on-surface at 6% is a STATE LAYER — the one role that's meant to
       sit on top of a container's own colour, which is exactly what this band does. It also
       self-corrects per theme: on-surface is dark in light mode and light in dark mode, so
       the band darkens the cell in one and lightens it in the other, staying a step away from
       the surface either way instead of a fixed colour that works in only one.
       (The token file warns against using this pair to REPLACE a background against the page
       canvas — that's the opposite case, and not what's happening here.) */
    background: color-mix(in srgb, var(--md-sys-color-on-surface) 6%, transparent);
}

/* Quiet tone, with the day's total taking the loud one — the same inversion the weekly view's day
   header got, extended here at the user's request. See .horus-ts-monthhours below.
   NOTE the difference in strength between the two views: there, the date keeps a slightly larger
   size, so the swap only moves the colour. Here the two are deliberately the SAME size, so tone is
   the only thing separating them and inverting it inverts the hierarchy outright. */
.horus-ts-monthday {
    font-size: .875rem;
    font-weight: 600;
    color: var(--horus-text-secondary);
}

/* The day's own numbers, pinned to the top-right of the cell beside the date: its total and,
   when there is one, its warning. Kept out of the flow below so the per-project lines get the
   cell's vertical space to themselves. */
.horus-ts-monthcell-meta {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
}

/* Same .875rem as .horus-ts-monthday, never smaller (explicit user request): the two share
   the header band and are the day's two headline numbers, so sizing the hours down made them
   read as a footnote to the date rather than its peer.
   The TOTAL now takes the full-strength tone and the date the quiet one (explicit user request,
   reversing what this pair used to say). Same reasoning as the weekly header: which day a cell is
   is already obvious from its position in the calendar, while the total is the number the cell
   exists to show. Since the two are the same size here, tone carries the whole hierarchy — see the
   note on .horus-ts-monthday.
   Untouched: .horus-ts-monthline-hours, the per-project breakdown below. Those are not the day's
   total, and making every line loud would leave nothing for the total to stand out from. */
.horus-ts-monthhours {
    font-size: .875rem;
    font-weight: 600;
    white-space: nowrap;
    color: var(--md-sys-color-on-surface);
}

/* One project per line inside a calendar cell: name on the left, its hours on the right.
   The name is the part that gives, so it takes the flexible track and truncates; the hours
   never do, since a clipped number would read as a different number. min-width:0 is what
   actually lets the name shrink — a flex item refuses to go below its content otherwise, and
   the ellipsis would never appear. */
.horus-ts-monthline {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font-size: .6875rem;
    line-height: 1.4;
}

.horus-ts-monthline-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: var(--md-sys-color-on-surface);
}

.horus-ts-monthline-hours {
    flex: 0 0 auto;
    color: var(--horus-text-secondary);
}

/* "+2 más" — deliberately quieter than the lines it stands for: it's a pointer to open the
   day, not information in itself. */
.horus-ts-monthmore {
    font-size: .6875rem;
    line-height: 1.4;
    color: var(--horus-disabled-content);
}

/* Pinned to the bottom of the cell regardless of whether that day has an hours line —
   explicit user request ("the progress at the very bottom of each cell"). */
.horus-ts-monthprogress {
    margin-top: auto;
}

/* The air above the bar (explicit user request) lives on whatever sits IMMEDIATELY BEFORE it,
   never on the bar itself. Padding on the bar looks like the obvious answer and is a trap:
   MudProgressLinear sets a fixed `height: 8px`, and under the app's global border-box that
   height INCLUDES padding — so `padding-top: 8px` left a 0px content box and the bar rendered
   8px outside its own frame, spilling past the bottom of the cell (measured: bar bottom at
   579 against a box ending at 571). That was the "broken design" this replaced.
   :has() rather than a fixed target because what precedes the bar varies — the last project
   line, the "+N" counter, or on an empty day the header band itself. */
.horus-ts-monthcell > :has(+ .horus-ts-monthprogress) {
    margin-bottom: 8px;
}

/* Saturday/Sunday: a slightly darker plane, still fully selectable — explicit user
   request to mark them as different without taking them out of play. Semi-transparent
   over the page background rather than an opaque tone, so it stays correct in both
   themes. */
.horus-ts-monthhead.horus-ts-weekend,
.horus-ts-dayhead.horus-ts-weekend .horus-ts-dayname {
    color: var(--horus-text-secondary);
}

.horus-ts-cell.horus-ts-weekend,
.horus-ts-monthcell.horus-ts-weekend {
    background: var(--horus-ts-weekend-bg);
}

/* Days belonging to the neighbouring month. Declared AFTER the weekend rules on purpose:
   an out-of-month Saturday must look like every other out-of-month day, not like a
   weekend — explicit user request that the padding on both edges of the month reads as
   one uniform gray.

   The page canvas, NOT --horus-disabled-container. That token (on-surface @ 12%) is a MD3
   state LAYER: it's meant to be painted over a control's own container to flatten it
   against its surroundings. Here it replaces the cell background against the page canvas
   instead, and since on-surface flips lightness between themes, so does the layer — it
   darkened the cell in light but LIGHTENED it in dark, making out-of-month days the
   brightest thing in the grid (measured: luminance 52 vs 42 for a real day). Dropping to
   7% didn't fix the direction, it just shrank it to invisible (40 vs 42).
   `background` is the honest role: an out-of-month day isn't a disabled control, it's
   absent content — "no cell here", the canvas showing through. It recedes in BOTH themes
   (light 243 vs 255 / dark 28 vs 42), which is what the two need to agree on. */
/* The BORDER, unlike the fill, is a disabled outline and takes the state layer — MD3 uses
   on-surface @ 12% for both disabled containers and disabled outlines, which is exactly
   --horus-disabled-container. Explicit user request that these read gray: every other cell
   in the grid is outlined in the warm outline-variant (the palette deliberately keeps edges
   brand-tinted), and carrying that warmth onto a day you can't type into made it look like
   a live cell. Neutral here says "not part of this month" without competing. */
.horus-ts-cell.horus-ts-outside,
.horus-ts-monthcell.horus-ts-outside {
    background: var(--md-sys-color-background);
    border-color: var(--horus-disabled-container);
    cursor: default;
}

/* A divider is gray only when BOTH days it separates are out of month.
   In the weekly grid each cell draws its own RIGHT border and the next one draws no left
   border (that's what keeps the shared edge a single 1px line under border-collapse:
   separate) — so the line to the LEFT of a day is painted by the day before it. When the
   month starts mid-week that neighbour is out of month, and the first day of the month
   inherited a gray left edge even though it's a live cell. Same for the TOTAL column when
   the week's Sunday falls outside. :has() lets the out-of-month cell keep the active
   colour on just that one shared edge.
   The mirror case needs no rule: the line to the RIGHT of the month's LAST day is painted
   by that day itself, which is in-month, so it was already warm — verified before writing
   this. */
.horus-ts-cell.horus-ts-outside:has(+ .horus-ts-cell:not(.horus-ts-outside)) {
    border-right-color: var(--md-sys-color-outline-variant);
}

.horus-ts-dayhead.horus-ts-outside .horus-ts-dayname,
.horus-ts-dayhead.horus-ts-outside .horus-ts-daydate,
.horus-ts-monthcell.horus-ts-outside .horus-ts-monthday {
    color: var(--horus-disabled-content);
    font-weight: 500;
}

.horus-ts-monthcell:not(.horus-ts-outside):hover {
    background: var(--horus-ts-hover-bg);
}

/* The open cell (weekly) / the selected calendar day (monthly). The entry form used to be
   a popover anchored to the cell, so "which coordinate am I editing" was answered by the
   popover's own position; the form now lives at the bottom of the card (weekly) or under
   the calendar (monthly), so the cell has to answer it. secondary-container is the same
   tonal fill the Total column already uses — see its comment above for why not
   primary-container.

   Declared AFTER the weekend rules for the same reason the out-of-month ones are: these
   selectors tie on specificity (0-2-0), so order is what decides, and a SELECTED Saturday
   has to read as selected rather than as a weekend (caught live — the fill was silently
   losing to .horus-ts-weekend on exactly those two columns while weekdays looked right). */
.horus-ts-cell.horus-ts-selected,
.horus-ts-monthcell.horus-ts-selected {
    background: var(--md-sys-color-secondary-container);
    border-color: var(--md-sys-color-primary);
}

/* The selected cell's LEFT edge isn't its own (reported: three sides went red, the left one
   stayed the original colour). .horus-ts-cell drops border-left so the shared edge between
   two cells stays a single 1px line under border-collapse:separate — which means the line to
   the left of a cell is painted by the cell BEFORE it, and border-color on the selected cell
   can't reach it. Same :has() trick already used above to keep the month's first day from
   inheriting a grey edge from its out-of-month neighbour.

   TWO selectors, and the duplication is load-bearing — the naive single
   `.horus-ts-cell:has(+ .horus-ts-selected)` (0-2-0) silently lost to that very
   out-of-month rule, `.horus-ts-cell.horus-ts-outside:has(+ .horus-ts-cell:not(...))`,
   which scores 0-4-0. Measured live: with the month starting mid-week, selecting the 1st
   left its shared edge painted outline-variant while the other three sides went red. The
   first selector below matches that 0-4-0 and, being declared later, wins the tie; the
   second covers the ordinary in-month neighbour.

   Monday needs no rule at all: it's `.horus-ts-taskcell + .horus-ts-cell`, which re-adds a
   real border-left of its own, and .horus-ts-selected above is declared later so its colour
   already wins there. */
.horus-ts-cell.horus-ts-outside:has(+ .horus-ts-cell.horus-ts-selected),
.horus-ts-cell:has(+ .horus-ts-cell.horus-ts-selected) {
    border-right-color: var(--md-sys-color-primary);
}

/* The entry form's scroll-into-view target (see TimeEntryFormPanel.razor.js). scroll-margin
   is the declarative way to say "leave this much air around me when scrolled to" — the
   browser adds it to the element's scroll box, so scrollIntoView stops short of the edge
   instead of parking the panel flush against it (explicit user request: it landed exactly on
   the bottom border and needed a few more px). Bigger at the end than at the start because
   the weekly view scrolls DOWN to it and that's the edge it lands on; the start value covers
   the monthly view, which scrolls up to a form that opens above the calendar.
   Note this is padding for SCROLLING only — it adds no space to the layout, so the panel
   still sits flush inside its card. */
/* Scroll-into-view targets: the entry form (see TimeEntryFormPanel.razor.js) and the monthly
   view's day panel, which opens the same way and wants the same air around it. */
.horus-entry-form,
.horus-ts-daypanel {
    scroll-margin-block: 16px 28px;
}

/* Scroll margin for the inline form of an admin listing — what keeps "bring the form into view"
   from meaning "push the page heading off the top".
   The form is the FIRST thing on its page, so aligning its own top edge with the top of the
   scrollport lands a few dozen pixels down: enough to clip the h4 + subtitle above it, which reads
   as the page having scrolled somewhere odd (reported: "I can see the form's header but the page's
   title shows up cut off"). Reserving the heading's own height means the same alignment resolves to
   the very top of the page here, and still leaves context above the form on any screen where it
   sits lower. Browsers clamp the result to the scroll range, so there is nothing to guard against.
   96px = h4 (~42) + subtitle (~20) + the container's py-4 and the block's mb-4. */
.horus-inline-form-host {
    scroll-margin-top: 96px;
}

/* Quick-add suggestion chips (both views). NEUTRAL grey fill with on-surface content,
   including the leading "+" — explicit user request, reverting an earlier pass that made them
   secondary-container with a brand-red "+". A row of five tonal chips in the footer band was
   pulling more attention than the grid they sit under, and the same reasoning the timesheet
   views already follow for hierarchy applies here: weight and shape carry the offer, not colour.

   `background`, NOT `surface-variant`: this palette splits the neutral-variant family by role
   and puts SURFACES on the warm seed (see Arquitectura.md §8), so surface-variant is #F5DDDA
   in light — pink, not the grey that was asked for. `background` is the neutral step of the
   palette and reads grey in both themes.

   Against the footer band (--horus-section-footer-bg, itself a 40% mix of background over
   surface) that leaves a deliberately small step — 10 points of luminance in light, 8 in dark,
   about the same as the weekend/normal-day step in the grid. The outline below is what keeps
   each chip legible as its own pressable object at that distance; without it a row of chips
   blurs into one grey band.
   Two classes deep because MudChip's own .mud-chip-color-* already sets a background at that
   specificity — this replaces it rather than fighting it with !important. */
.horus-timesheet .horus-ts-suggestion {
    background-color: var(--md-sys-color-background);
    color: var(--md-sys-color-on-surface);
    border: 1px solid var(--md-sys-color-outline-variant);
}

.horus-timesheet .horus-ts-suggestion .mud-icon-root {
    color: var(--md-sys-color-on-surface);
}

/* MudAlert lays its row out with `align-items: start`, which is invisible while the message
   is one line (icon and text are the same height) and wrong the moment it wraps: the icon
   sticks to the top while the text block grows past it. Measured on the "previous month
   still open" alert at a narrow width — icon centred at 386px, message at 429px.
   Fixed app-wide rather than on that one alert (explicit user preference for fixing the
   system): nothing about it is specific to that message, and every multi-line alert in this
   app has the same tell. */
.mud-alert .mud-alert-position {
    align-items: center;
}

/* Warning alerts: an opaque amber fill and readable content in LIGHT, unchanged in dark.
   The whole decision — including the measurements that show light failing and dark passing —
   lives on the token pair in md3-tokens.css, because only a per-theme declaration can change
   one theme without touching the other. This rule just spends them.

   Fixed app-wide rather than on the timesheet page that reported it: nothing about the
   problem is specific to that screen (it's MudBlazor's default Warning pair against this
   palette's canvas), and a local override would be a second copy of a global definition.
   Two classes deep to beat MudBlazor's own .mud-alert-text-warning at the same importance.

   The ICON needs its own rule and does NOT inherit — assumed otherwise at first, then
   measured: MudBlazor declares `.mud-alert-text-warning .mud-alert-icon { color:
   var(--mud-palette-warning) }`, i.e. the icon is pinned to the FULL-strength #FF9800 while
   the message gets the darkened tone. Left alone, that icon lands at 1.88:1 on the new amber
   fill — under even the 3:1 WCAG asks of non-text graphics, so the fix for the text would
   have traded a washed-out box for an invisible icon. Its selector is 0-2-0, so this one is
   0-3-0 to take it. */
.mud-alert.mud-alert-text-warning {
    background-color: var(--horus-alert-warning-bg);
    color: var(--horus-alert-warning-text);
}

.mud-alert.mud-alert-text-warning .mud-alert-icon {
    color: var(--horus-alert-warning-icon);
}

/* The info alert, same treatment and same two selectors — see the token file for the numbers. Only
   the CONTENT diverges here: MudBlazor's 6% fill is fine, it is the message tone (`info-darken`)
   that misses 4,5:1 in both themes, and the icon that misses 3:1 in light. Global rather than a
   class on the screen that found it: five screens carry an info alert and they must not start
   disagreeing about what info reads like. */
.mud-alert.mud-alert-text-info {
    color: var(--horus-alert-info-text);
}

.mud-alert.mud-alert-text-info .mud-alert-icon {
    color: var(--horus-alert-info-icon);
}

/* "Sugeridas:" — bold (explicit user request), and never the thing that wraps: it's the
   label for the scroller beside it, so breaking it away from its list would be worse than
   shortening the list. */
.horus-ts-suggested-label {
    font-weight: 700;
    white-space: nowrap;
}

/* The chips scroll sideways instead of wrapping, so the footer band stays exactly one row
   tall no matter how many suggestions come back (explicit user request).
   `flex: 1 1 0` + `min-width: 0` is the load-bearing pair: a flex item refuses to shrink
   below its content's own width unless min-width is overridden, and without that this box
   would push the band wider and wrap anyway — the default `min-width: auto` is the classic
   reason "overflow on a flex child" silently does nothing.
   The scrollbar is hidden on purpose (explicit user request) via all three vendor spellings;
   the chips still scroll by trackpad, shift+wheel and keyboard. Accepting the known cost:
   with no visible bar, the only hint that more chips exist is the last one being clipped
   mid-way. */
/* Holds the scroller plus its overflow affordance. It's the flex item the footer sizes, so
   the shrink pair lives HERE now and the scroller inside just fills it. */
.horus-ts-suggestions-wrap {
    position: relative;
    display: flex;
    flex: 1 1 0;
    min-width: 0;
}

/* The two chevrons, and the fade that keeps a chip from colliding with each — every part
   shown only while something is actually hidden on that side (the .horus-ts-has-more /
   .horus-ts-has-previous classes come from JS, see SuggestionChips.razor.js). The fade reads
   as "this continues" on its own; the chevron is what you can press.

   Both directions are styled from one declaration, differing only in which edge they pin to:
   the left one was added after the right (reported: with the scrollbar hidden there was no
   way back to the first chips), and writing it as a mirrored copy is how the two would start
   drifting apart on the next tweak. */
.horus-ts-suggestions-more,
.horus-ts-suggestions-back {
    display: none;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    /* Above the fades (z-index 1) — see the note on them for why nothing here can rely on paint
       order alone. */
    z-index: 2;
}

.horus-ts-suggestions-more {
    right: 0;
}

.horus-ts-suggestions-back {
    left: 0;
}

.horus-ts-has-more .horus-ts-suggestions-more,
.horus-ts-has-previous .horus-ts-suggestions-back {
    display: inline-flex;
}

/* ::after is the right-hand fade, ::before the left-hand one — one pseudo-element each, so
   the two can be on at the same time (mid-list, both chevrons showing) without competing for
   the same slot. */
/* z-index on BOTH, and it is load-bearing rather than tidiness. MudChip renders its button as
   `position: relative`, so the chips are POSITIONED elements: they paint in the same step as these
   pseudo-elements, and tree order decides between them. ::before is the wrap's first child, so the
   chips painted over the LEFT fade and it disappeared, while ::after (the last child) painted over
   the chips and worked — the reported asymmetry, where the back chevron sat directly on the content
   with no fade under it.
   An earlier version did have z-index here and it was removed, on the reasoning that positioned
   pseudo-elements already paint above in-flow content. True, but the chips are not in-flow content.
   Stating both layers explicitly is what keeps this from depending on that distinction at all. */
.horus-ts-has-more::after,
.horus-ts-has-previous::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 72px;
    z-index: 1;
    pointer-events: none;
}

/* Fades into the band's own colour — hence the shared token rather than a hardcoded
   copy of that color-mix, which would drift the day the footer tone changes. */
.horus-ts-has-more::after {
    right: 0;
    background: linear-gradient(to right, transparent, var(--horus-section-footer-bg));
}

.horus-ts-has-previous::before {
    left: 0;
    background: linear-gradient(to left, transparent, var(--horus-section-footer-bg));
}

/* The scroller needs room for whichever chevron is showing, or the first/last chip sits
   underneath one. Padding rather than a margin on the scroller: it has to scroll THROUGH the
   gap, so the space belongs to its content box, not outside it. */
.horus-ts-has-more .horus-ts-suggestions {
    padding-right: 32px;
}

.horus-ts-has-previous .horus-ts-suggestions {
    padding-left: 32px;
}

.horus-ts-suggestions {
    display: flex;
    flex: 1 1 0;
    min-width: 0;
    gap: 2px;
    overflow-x: auto;
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none; /* legacy Edge */
}

.horus-ts-suggestions::-webkit-scrollbar {
    display: none; /* Chromium, WebKit */
}

/* Chips keep their own width instead of being squeezed by the flex container, which is what
   makes them overflow (and therefore scroll) rather than compress into truncated slivers.
   Targets .mud-chip and NOT the direct child: MudChip renders a `.mud-chip-container`
   wrapper around its button, and that wrapper is `display: contents` — it has no box at
   all, so the real flex item is the BUTTON one level down. A `> *` rule lands on the
   wrapper, where flex is meaningless, and the button keeps the default `flex: 0 1 auto` and
   shrinks. Measured before the fix: the last chip's content was 225px squeezed into 216px,
   clipped (its overflow is hidden) with no scrolling anywhere, which looked like a scroller
   that simply didn't work. */
.horus-ts-suggestions .mud-chip {
    flex: 0 0 auto;
    /* MudChip ships `max-width: 100%`, which is the SECOND way these got squeezed and the
       one flex-basis can't defend against: a max-width caps the used width no matter what
       flex says, so as the band narrowed every chip was re-clamped to the scroller's width.
       Measured at a 620px viewport: five chips whose natural widths are 199/123/187/280/225
       all rendered at 47px, clipped. `none` lets them keep their real width and lets the
       overflow — and therefore the scrolling — actually happen, which is the whole point of
       the scroller. Safe here precisely because this container scrolls; the 100% default
       exists for chips inside boxes that don't. */
    max-width: none;
}

/* Brand-coloured foreground text — for a label that identifies WHAT the screen is about
   (the role name in RoleFeatures), so it reads as context rather than as another subtitle.
   Uses --horus-primary-text, not --md-sys-color-primary: see the token's own comment in
   md3-tokens.css for why the two differ (the frozen brand red fails contrast as a
   foreground in dark). */
.horus-brand-text {
    color: var(--horus-primary-text);
}

/* An hours value is one token — "8h 30min" must never break across lines. Without this the
   space inside it is a legal wrap point, and a narrow column splits it into "8h" / "30min",
   which also drags that column's header taller than the rest of the row. */
.horus-ts-dayhours,
.horus-ts-cell-value,
.horus-ts-rowtotal,
.horus-ts-monthhours {
    white-space: nowrap;
}


/* ─────────────────────────────────────────────────────────────────────────────
   Notifications — the app bar bell's drop-down (NotificationBell.razor).

   A MudMenu's panel is a MudList of MudMenuItems, which is the right structure here
   (each row is a target that navigates) but comes styled for one-line commands. These
   rules give it a width, let a row hold two lines, and mark unread ones — nothing more.
   ───────────────────────────────────────────────────────────────────────────── */

/* The unread badge, sitting ON the bell instead of floating above it (explicit user request:
   lower, more overlapped, with a white outer ring).

   MudBadge positions itself against the ACTIVATOR — MudMenu's 48px icon button — not against
   the 24px glyph inside it, and the button's 12px of padding is what pushed the badge clear of
   the bell. Measured before touching anything: the badge's bottom edge landed exactly on the
   glyph's top edge (zero overlap) and its own top sat at y=-4, i.e. outside the app bar.

   Two things about HOW to move it, both of which cost a failed attempt:

   1. **MudBlazor anchors this badge with `inset`, via `bottom`/`left` — not `top`/`right`.**
      Its rule is `.mud-badge.mud-badge-top.right.mud-badge-overlap { inset: auto auto
      calc(100% - 12px) calc(100% - 12px) }`. Overriding `top`/`right` does nothing: those are
      `auto` there, and the `top: -8px` a browser reports is the USED value the layout works out
      from `bottom` (48 - 36 - 20 = -8), not something any rule declared. So this override has
      to go through `inset` as well.
   2. The selector needs to out-specify four classes, hence the long chain.

   Both axes move, not just the vertical one: horizontally the badge began precisely where the
   glyph ended, so lowering it alone would have left it resting on the corner still touching
   nothing. The offsets are from the activator's bottom/left edges, kept relative like
   MudBlazor's own so a differently-sized activator degrades instead of breaking. The same 24px
   on both (user's own value, over a first attempt at 26/22): it puts the badge's top at 4px
   (48 - 24 - 20) and its left at 24px, which is a symmetric 12x12px over the glyph's top-right
   corner — the asymmetric version was 10 wide by 14 tall and read as slightly off. */
.horus-notification-badge .mud-badge.mud-badge-top.right.mud-badge-overlap {
    inset: auto auto calc(100% - 24px) calc(100% - 24px);
}

/* NO border-color override here, on purpose — `Bordered="true"` already rings the badge in
   `--mud-palette-surface`, which IS the top bar's colour in this app, so the badge reads as cut
   out of the bar in both themes (dark ring in dark, light in light — the requested behaviour).
   Two wrong turns got here, both worth not repeating:

   - A fixed white ring: fine in light, wrong the moment the theme flipped.
   - `--mud-palette-appbar-background`: sounds like the right token and isn't. This project
     paints `.mud-appbar` with `--md-sys-color-surface` (see the .mud-appbar rule above),
     deliberately NOT with the theme's AppbarBackground — so that token holds a tone the bar
     never actually shows (#1A1C1E against the bar's real #282A2D in dark; measured, they didn't
     match).

   If the `.mud-appbar` background above ever stops being surface, this ring stops matching its
   backdrop and needs an explicit colour again — that rule and this comment are a pair. */

/* The popover is one of this app's listing SECTIONS, not a plain menu (explicit user request
   to unify the two): .horus-section's own plane/border/radius on the panel, a
   .horus-section-header band, the list, and a .horus-section-footer band. Only what a floating
   panel needs on top of that lives here — a width, and a body that scrolls between the two
   fixed bands.

   MudBlazor already gives the popover a surface and elevation, so this takes the section's
   border and radius and lets the popover keep its own shadow (a section shadow on top of a
   popover shadow reads as two stacked cards). overflow:hidden is what keeps the header/footer
   bands from painting over the rounded corners — same reason .horus-section carries it. */
.horus-notification-popover {
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: 8px;
    overflow: hidden;
    /* The height cap goes HERE, not on the list: MudBlazor's own rule is
       `.mud-popover .mud-list { max-height: inherit }`, i.e. the list is designed to inherit
       the popover's cap. Setting it on the list instead silently does nothing — that rule has
       two classes and wins, and `inherit` then resolves to the popover's `none`. */
    max-height: 60vh;
}

/* Width: enough for "Fulano de Tal envió sus horas de septiembre 2026 a revisión" on two lines
   at the 24px band padding this style implies (380 - 48 = 332px of text). min() so a narrow
   window still fits it. */
.horus-notification-list {
    width: min(380px, calc(100vw - 32px));
    /* The three bands stack; only the middle one scrolls, so the header stays readable and the
       footer's action stays reachable with a long backlog. */
    display: flex;
    flex-direction: column;
    padding: 0;
}

/* Two classes to out-specify MudBlazor's `.mud-popover .mud-list { overflow-y: auto }` (ties on
   specificity, wins on order since this file loads later). That auto is what would scroll the
   header and footer away with the content — the whole point of the band layout is that they
   don't move, so the LIST stops scrolling and the body below does it instead. */
.mud-popover .horus-notification-list {
    overflow-y: hidden;
}

.horus-notification-body {
    flex: 1 1 auto;
    overflow-y: auto;
    /* min-height:0 is what actually lets this shrink below its content and scroll: a flex item
       defaults to min-height:auto, which refuses to be smaller than its content, so without it
       the body would push the popover past its max-height instead of scrolling. */
    min-height: 0;
    /* 2px between notifications (explicit user request) — via flex gap rather than a margin on
       each item, so nothing has to special-case the last one and the empty state (a single child)
       is unaffected. The seam shows the body's own surface, which is what makes each unread row
       read as its own bar instead of one continuous tinted block — same idea as the 4px
       border-spacing between task rows in the weekly grid. */
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* The bands come from .horus-section-header/-footer; these two only add what a popover needs:
   they must not shrink when the body scrolls, and the title sits alone in the header now that
   "mark all as read" moved to the footer (the section pattern leads with its action there). */
.horus-notification-header,
.horus-notification-footer {
    flex: 0 0 auto;
}

/* Only the HEADER takes the form header's tone; the footer keeps the shared
   .horus-section-footer mix, which is a step LIGHTER. That ladder — grey header, lighter footer,
   white body — is the form panels' own, not something invented here: measured on "Nuevo rol" in
   light, header #EEEDF1 / footer #F8F8F9 / fields #FFFFFF. Making the two bands equal (tried, and
   wrong) flattens that into a sandwich and stops matching the nine form panels.

   Why the header changes at all: the rule stated where the two header variants split is about
   what sits BELOW them — a section header goes over a tinted content area so it has to be the
   lighter one, a form header goes over white fields and needs the tone to separate itself. This
   popover's body is plain surface, i.e. the second case.

   Single-class selector like .horus-section-header, so it has to come after that rule in this
   file to win — it does. Geometry and borders stay shared. */
.horus-notification-header {
    background-color: var(--md-sys-color-background);
}

/* A notification is a sentence plus a timestamp, so the row has to grow to two lines —
   MudMenuItem ships `white-space: nowrap` and a fixed height for single-line commands. The 24px
   horizontal padding matches the header/footer bands, so text lines up across all three. */
.horus-notification-item {
    height: auto;
    white-space: normal;
    padding: 10px 24px;
    /* `height: auto` is NOT enough, and this is the whole bug (reported: the list "overlaps
       itself and isn't scrollable"). These rows are flex items of a COLUMN whose height is capped by
       the popover, so with the default `flex-shrink: 1` flexbox squeezes them instead of letting
       the body scroll: measured live, each row collapsed from 92px to 32px while its content
       still needed more, so the text spilled over the row below and the body had nothing to
       scroll (scrollHeight == clientHeight, which is why `overflow-y: auto` looked broken).

       With shrinking off, ten rows come to 798px inside a 344px body and the scroll appears —
       and the header and footer bands, which are outside it, stay put. */
    flex-shrink: 0;
}

/* Unread: a tonal wash plus a brand-red rail on the leading edge. The wash alone tested too
   subtle at 8% against surface, and going darker started to read as "selected" rather than
   "unread" — the rail carries the signal and the wash just backs it up. Both are role-derived
   (no hardcoded colour), so they hold in either theme. */
.horus-notification-unread {
    background-color: color-mix(in srgb, var(--mud-palette-secondary) 8%, transparent);
    box-shadow: inset 2px 0 0 0 var(--mud-palette-primary);
}

/* The in-flight state, which has no row to sit in. The empty state is the shared EmptyState
   component instead (see the .razor) — that's the whole point of the unification, so there is
   deliberately no local empty-state rule here any more. */
.horus-notification-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 96px;
    padding: 16px;
}

/* EmptyState was built for a data grid's full width; inside a 380px popover — or a home
   dashboard card — its 48px vertical padding and 64px icon are more air than the panel has to
   give. Scoped down here rather than changed at the source — every listing still wants the
   generous version. The dashboard joined the popover's rule instead of writing a second copy. */
.horus-notification-body .horus-empty-state,
.horus-dashboard-card-body .horus-empty-state {
    padding: 24px;
    gap: 8px;
}

.horus-notification-body .horus-empty-state-icon,
.horus-dashboard-card-body .horus-empty-state-icon {
    font-size: 48px;
}

/* The tinted disc scales with the icon it wraps: 16px of ring around a 48px glyph keeps the
   same proportion the listings get from 24px around 64px. */
.horus-notification-body .horus-empty-state-figure,
.horus-dashboard-card-body .horus-empty-state-figure {
    padding: 16px;
}

/* The search box is the control that yields width first when this row has to fit three
   filters: the month selector and the segmented button are both fixed-size (and the
   segmented one can't shrink at all without clipping its segments), so the flexible one has
   to be this. The magnifier travels with it — see the comment in TimesheetReviews.razor. */
.horus-review-search {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1 1 200px;
    min-width: 160px;
}

.horus-review-search .horus-inline-search {
    flex: 1 1 auto;
}

/* The facts about the month in the review detail header — the period, the two captions and
   the two values (see TimesheetReviewDetail.razor) — in bold. It's how the timesheet screens
   carry hierarchy in the first place: weight does the work that colour does elsewhere, so the
   person's name above stays the heading by being LARGER rather than by being the only bold
   thing on the card.

   700, not 600: Gilroy ships Medium/Bold/ExtraBold only, so 600 would be synthesised by the
   browser (same reason the segmented button's selected label is 700 — see md3-components' note
   there). A single class wins over MudBlazor's own .mud-typography-* weights because this file
   loads after MudBlazor.min.css (see App.razor), which is the mechanism every override in here
   relies on.

   Was referenced by the markup for a while without existing anywhere — those five elements
   were simply not bold, contradicting the comment right above them. Found while working on the
   import screen, added on request. */
.horus-review-fact {
    font-weight: 700;
}

/* The row of facts at the top of the leave decision panel (PtoReviewPanel.razor): who, what
   kind, which dates, how many days, when it was filed.

   `flex: 1 1 auto` on every child, not `1 1 0`: with a zero basis all five boxes come out the
   same width regardless of what is in them, which truncates "María Fernanda Rodríguez" to give
   "2 días" room it has no use for. An `auto` basis starts each box at its own content width and
   then splits only the LEFTOVER space equally — so a long name keeps what it needs and the rest
   share the remainder evenly, which is what was asked for.

   `min-width: 0` is what lets a box shrink below its content once the panel is narrow enough
   that there is no leftover to share — without it a flex item's automatic minimum is its
   content, and the row overflows the panel instead of wrapping. */
.horus-review-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 24px;
}

.horus-review-facts > * {
    flex: 1 1 auto;
    min-width: 0;
}

@media (max-width: 900px) {
    .horus-timesheet-reviews .horus-toolbar-divider {
        display: none;
    }
}

/* ============================================================================
   Team leave calendar (PtoCalendar.razor)

   A month at day resolution: a label column plus a strip of one cell per day, repeated for
   every person. Structurally the same 2-column grid as the assignments Gantt — label track,
   then a nested strip whose column count is a custom property — with one deliberate
   difference: here the day cells are real elements, because each carries its own state and
   tooltip, which a tiled background can't.
   ============================================================================ */

.horus-pto-calendar {
    /* The three fills, declared once here because the legend swatches and the grid cells both
       have to use them — a swatch that drifts from the thing it explains is worse than no
       legend. Percentages over `surface`, not over `transparent`: the wrap behind them is
       tinted (`background`), so a translucent fill would compose against it and read as a
       different colour inside the grid than in the legend.

       Approved is a pastel green off the theme's Success role (explicit request). Note Success
       is the ONE semantic this theme overrides — #26814D light / #3DB570 dark, chosen for
       contrast — so mixing from it keeps the calendar in step with every check icon in the app
       instead of inventing a second green.

       Pending is amber on white (explicit request). MudBlazor's Warning is the only orange the
       palette has; it's used here as a STRIPE against surface rather than as a flat fill, which
       is what keeps it clear of the light-mode contrast problem documented for
       --horus-alert-warning-* (that one fails as a background behind text; there is no text
       on these cells).

       Holidays are `secondary-container` — the same role the timesheet totals use, and pink in
       light. It has to be a different FAMILY from time off, not a different shade: a holiday is
       a different kind of day off, not more or less of one. */
    --horus-cal-pto-bg: color-mix(in srgb, var(--mud-palette-success) 25%, var(--md-sys-color-surface));
    --horus-cal-pending-stripe: color-mix(in srgb, var(--mud-palette-warning) 55%, var(--md-sys-color-surface));
    --horus-cal-holiday-bg: var(--md-sys-color-secondary-container);

    /* Sticky header needs this: .horus-section ships overflow:hidden to keep its bands off the
       rounded corners, but an overflow:hidden ancestor becomes the scrollport, and this card
       has no height constraint so it never scrolls — the band would stick to a box that never
       moves. Same trade (and the same cost: the bands round themselves) as the Gantt, the
       weekly timesheet card and the import screen. */
    overflow: visible;
}

.horus-pto-calendar .horus-section-header {
    position: sticky;
    top: 0;
    z-index: 4;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    /* flex-wrap, not a media query: when the filters and the legend stop fitting, the ROW
       wraps — the controls are never squeezed. */
    flex-wrap: wrap;
    /* 12px rather than the shared 8px: the people multi-select is taller than a caption once
       it holds chips, and at 8px the band clamped it. */
    padding: 12px 24px;
    border-radius: 8px 8px 0 0;
}

.horus-pto-cal-filters {
    display: flex;
    align-items: center;
    gap: 8px;
    /* basis 0 so this group takes the space the legend doesn't, instead of its max-content
       breaking the line before anything gets a chance to shrink — the same flexbox trap the
       Gantt header documents. */
    flex: 1 1 0;
    min-width: 320px;
    flex-wrap: wrap;
}

/* The people selector takes ALL the slack in the header (same as the Gantt's): the month
   selector and the legend are content-sized, so whatever the header doesn't need lands here.

   Two rules because two elements have to grow — the shared .horus-filter-group wrapper, which
   ships `flex-shrink: 0` for the fixed-size filters and needs singling out, and the control
   inside it. `min-width: 0` on the wrapper is what lets it shrink below its content when the
   header gets tight; the min-width on the control is the floor below which it stops giving. */
.horus-pto-cal-filters .horus-filter-group:has(.horus-pto-cal-people) {
    flex: 1 1 0;
    min-width: 0;
}

.horus-pto-cal-people {
    flex: 1 1 auto;
    min-width: 180px;
}

/* Wrapped rows would leave a divider hanging at the end of a line, where it reads as a stray
   mark rather than as a separator between two things. Same treatment, and the same breakpoint,
   as the timesheet review inbox's toolbar. */
@media (max-width: 900px) {
    .horus-pto-calendar .horus-toolbar-divider {
        display: none;
    }
}

.horus-pto-cal-legend {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    flex-shrink: 0;
    font-size: 12px;
    color: var(--horus-text-secondary);
}

.horus-pto-cal-legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
}

.horus-pto-cal-swatch {
    width: 14px;
    height: 14px;
    border-radius: 3px;
    border: 1px solid var(--mud-palette-table-lines);
}

/* Tinted and scrolling sideways, exactly like the Gantt's wrap and for both the same reasons:
   the cells are drawn on `surface`, so they need a darker ground to read as boxes against, and
   31 columns squeezed into a narrow viewport stops being a calendar. */
.horus-pto-cal-wrap {
    /* A variable, not a number, because the current-day overlay cancels exactly this to reach the
       wrap's own edges — the same reason --horus-cal-gap is one. Two hardcoded 16s are how the
       overlay ends up 4px short of the bottom the next time the padding is tuned. */
    --horus-cal-wrap-pad-y: 16px;
    background: var(--md-sys-color-background);
    padding: var(--horus-cal-wrap-pad-y) 24px;
    overflow-x: auto;
    border-radius: 0 0 8px 8px;
}

.horus-pto-cal-grid {
    --horus-cal-label-width: 220px;
    /* The gap between day cells, as a variable because the current-day overlay has to do
       arithmetic with it — a hardcoded 2px in two places is how the overlay ends up half a
       column off the cells it's meant to sit on. */
    --horus-cal-gap: 2px;
    /* One column's width, gaps discounted: N columns and N-1 gaps share the track. */
    --horus-cal-col: calc(
        (100% - var(--horus-cal-label-width) - (var(--horus-cal-days) - 1) * var(--horus-cal-gap))
        / var(--horus-cal-days));
    position: relative;
    display: grid;
    grid-template-columns: var(--horus-cal-label-width) 1fr;
    align-items: stretch;
    row-gap: 6px;
    /* 31 days at a width where a two-digit number still fits, plus the label column. Below
       this the wrap scrolls rather than shrinking cells into unreadable slivers. */
    min-width: 1060px;
}

/* The day strip, used twice over — the header and every person's row — so both stay in
   lockstep off the same column count. If they ever disagreed, every cell would be off by a
   day. */
.horus-pto-cal-days,
.horus-pto-cal-row {
    display: grid;
    grid-template-columns: repeat(var(--horus-cal-days), 1fr);
    gap: var(--horus-cal-gap);
}

/* .horus-pto-cal-corner is intentionally unstyled and empty: the label column has no header.
   A "Persona" caption would be the only text in the band and would read as a table this isn't. */

.horus-pto-cal-dayhead {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    padding-bottom: 4px;
    font-size: 11px;
    color: var(--horus-text-secondary);
}

.horus-pto-cal-dayhead.is-weekend,
.horus-pto-cal-dayhead.is-weekend .horus-pto-cal-daynum {
    /* Same 38% state layer MD3 defines for disabled content, and the same one the timesheet
       grids use for a day you can't act on. */
    color: var(--horus-disabled-content);
}

.horus-pto-cal-daynum {
    font-weight: 700;
    color: var(--md-sys-color-on-surface);
}

/* Today's number in the brand primary (explicit request). AFTER the weekend rule and at the same
   specificity (0-3-0), which is what makes today win when it falls on a Saturday — the more
   important fact about that column, and the tinted overlay is already saying so underneath.
   `--md-sys-color-*` and not `--mud-palette-*`: this is a plain span, not a Mud component. */
.horus-pto-cal-dayhead.is-today .horus-pto-cal-daynum {
    color: var(--md-sys-color-primary);
}

/* Today, down the full height of the grid — the same overlay the assignments Gantt draws over
   its current month (explicit request), and the same reasoning: it reads as a highlight laid
   OVER the chart rather than a column painted into it, so it crosses the header, the cells and
   the gaps and each keeps showing through.

   Absolutely positioned, so the offset has to clear the label column first and then step the
   day's index across columns-plus-gaps — hence the arithmetic on the same variables the grid
   template uses, rather than numbers repeated here.

   ABOVE the cells, like the Gantt's: a highlight has to tint what it crosses, or a filled cell
   passing through shows its own flat colour and the column reads as a hole. `pointer-events:
   none` is what keeps those cells hoverable underneath — they carry the tooltips. */
.horus-pto-cal-nowcol {
    position: absolute;
    /* Out past the grid by the wrap's own vertical padding, so the column runs the full height of
       the tinted area instead of stopping 16px short at each end (explicit request). It lands
       exactly on the wrap's padding box — its scrollport — so nothing is clipped, and the wrap's
       rounded bottom corners trim the column with them. */
    top: calc(-1 * var(--horus-cal-wrap-pad-y));
    bottom: calc(-1 * var(--horus-cal-wrap-pad-y));
    left: calc(var(--horus-cal-label-width)
        + var(--horus-cal-today-index) * (var(--horus-cal-col) + var(--horus-cal-gap)));
    width: var(--horus-cal-col);
    z-index: 2;
    background: color-mix(in srgb, var(--md-sys-color-secondary-container) 40%, transparent);
    pointer-events: none;
    border-radius: 3px;
}

.horus-pto-cal-label {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
    padding-right: 12px;
    min-height: 28px;
    overflow: hidden;
    border-bottom: 1px solid var(--mud-palette-table-lines);
    margin-right: 24px;
}

.horus-pto-cal-person {
    font-size: 15px;
    /* 700, not 600: Gilroy ships Medium/Bold/ExtraBold only, so 600 would be synthesised by the
       browser — the same reason the segmented button's selected label is 700. */
    font-weight: 700;
    color: var(--md-sys-color-on-surface);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The year's allowance: the one fact in this row that isn't about the month on screen, so the
   line stays quiet as a whole — only the figure inside it is emphasised. */
.horus-pto-cal-quota {
    font-size: 13px;
    color: var(--horus-text-secondary);
}

/* What's LEFT — the largest thing in the cell, on purpose and by explicit request. It is the
   number somebody comes to this column for ("how many do I still have"), so it outranks both the
   name above it and the "de 25" beside it. The three sizes together are what make the hierarchy:
   balance 16, name 15, context 13. */
.horus-pto-cal-remaining {
    font-size: 16px;
    font-weight: 700;
    color: var(--md-sys-color-on-surface);
}

/* Red only once there is nothing left (or the year went over). Deliberately NOT always red: a
   healthy balance is not a problem, and Error used for a normal state is how a palette stops
   meaning anything — the same reason the month-hours warning only fires off target. */
.horus-pto-cal-remaining.is-spent {
    color: var(--mud-palette-error);
}

.horus-pto-cal-cell {
    min-height: 28px;
    border-radius: 3px;
    background: var(--md-sys-color-surface);
    border: 1px solid var(--mud-palette-table-lines);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* The backgrounds don't stack, they rank: time off over holiday over weekend (see Describe in
   the .razor for why that order). Each state is a single class, so the ranking lives in the C#
   that picks one — not in a pile of CSS specificity nobody can read. */
.horus-pto-cal-cell.is-weekend {
    /* The same mix the timesheet grids use for a weekend, against the same tinted ground. */
    background: color-mix(in srgb, var(--md-sys-color-surface) 50%, transparent);
}

.horus-pto-cal-cell.is-holiday,
.horus-pto-cal-swatch.is-holiday {
    background: var(--horus-cal-holiday-bg);
    border-color: var(--horus-cal-holiday-bg);
}

.horus-pto-cal-cell.is-pto,
.horus-pto-cal-swatch.is-pto {
    background: var(--horus-cal-pto-bg);
    border-color: var(--horus-cal-pto-bg);
}

/* Pending stays HATCHED and only changes colour: the difference from approved is "not decided
   yet", which is a state rather than a different kind of absence, so the texture is what
   carries it. The amber just makes the state legible at a glance instead of needing the
   legend. */
.horus-pto-cal-cell.is-pending,
.horus-pto-cal-swatch.is-pending {
    background: repeating-linear-gradient(
        45deg,
        var(--horus-cal-pending-stripe) 0 4px,
        var(--md-sys-color-surface) 4px 8px);
    border-color: var(--horus-cal-pending-stripe);
}

/* Half a day, drawn as half a cell — the fill stops at the middle. Paired with the state that
   set the fill, so it overrides it rather than racing it on specificity. */
.horus-pto-cal-cell.is-pto.is-half {
    background: linear-gradient(
        to right,
        var(--horus-cal-pto-bg) 0 50%,
        var(--md-sys-color-surface) 50%);
}

.horus-pto-cal-cake {
    /* 18px, not the 14px this started at: in a 28px cell the smaller glyph read as a smudge
       rather than as a cake. */
    font-size: 18px !important;
    color: var(--md-sys-color-tertiary);
}

/* MudTooltip wraps its child in an element of its own, so a cell that has a tooltip IS that
   wrapper (the class goes on RootClass). This keeps the wrapper behaving as the grid item —
   without it the cell inside would size to its content and the columns would stop lining up. */
.horus-pto-cal-row > .mud-tooltip-root {
    display: flex;
}

/* The leave-limit editor (PtoLimitsEditor.razor), shared by the country form and the user
   form — one cell per leave kind, its name over a small number.

   Auto-fill rather than a fixed column count: the same control sits in a full-width country
   form and inside a collapsed panel on the user form, and there are eight kinds today with no
   reason that number stays put. The track minimum is what the widest kind name needs
   ("Licencia sin goce de sueldo"), so cells never wrap mid-label; below that width it simply
   drops to one per row.

   Name ABOVE the number, not beside it: side by side, the longest label would set a column
   width every other row then pads out, and the eight inputs would drift far from their names. */
.horus-pto-limits {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px 16px;
}

.horus-pto-limits-row {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.horus-pto-limits-name {
    display: flex;
    align-items: center;
    color: var(--horus-text-secondary);
}

.horus-pto-limits-input {
    /* Zeroes MudBlazor's own top margin on a form field, which would otherwise open a gap
       between each number and the name it belongs to. */
    margin-top: 0;
}

/* The leave-type filter in the time-off review inbox (PtoReviews.razor) — a
   HorusMultiAutocomplete on a row of its own below the main filter strip, spanning the whole
   width of it.

   It gets its own row because it is the one filter that GROWS: each kind picked adds a chip,
   and sharing a line with the year selector and the segmented status button (which cannot
   wrap — see SegmentedMultiFilter) would have it shove them around as it fills. Alone on the
   row, there is nothing to take width from, so it takes all of it — the alternative, a
   content-width box with the rest of the row empty beside it, is just a filter that looks
   like it failed to load.

   Two rules because two elements have to grow: `.horus-filter-group` ships `flex-shrink: 0`
   for the fixed-size filters in the strip above and has to be singled out (the same pair the
   leave calendar's people selector documents), and then the control inside it. */
.horus-pto-type-filter-group {
    flex: 1 1 auto;
    min-width: 0;
}

.horus-timesheet-reviews .horus-pto-type-filter {
    flex: 1 1 auto;
    min-width: 0;
}

/* ============================================================================
   CSV import preview (TimesheetImport.razor)

   Everything below styles ONE screen's MudDataGrid. Four constraints came up over and over while
   building it, so they're stated once here instead of in every rule:

   1. MudBlazor's own cell rules — and this file's padding rule for them — are selectors of THREE
      classes (`.horus-section .mud-table-container tbody td.mud-table-cell`). Anything that has to
      beat them for a property they already set must be spelled out just as long. A single-class
      selector silently loses: it cost a border-top and, later, the padding of two columns.
   2. Some MudBlazor pieces come with `!important` (utility classes like `ma-n3`, and this file's
      own `.horus-warn-icon`). Only an `!important` beats an `!important` — where one appears
      below, that's why.
   3. `HierarchyColumn` accepts `HeaderClass` but NOT `CellClass` (the MUD0002 analyzer flags it),
      so its cell is reached by position: it is always this grid's first column.
   4. Sticky positioning dies inside a clipping ancestor. `.horus-section` sets `overflow: hidden`
      and `.mud-table-container` sets `overflow: auto` on both axes — the first is why the card
      needs the override below, the second is why the column headers can't pin at all.
   ============================================================================ */

/* ---------- The card and its pinned header band ---------- */

/* The band carries the "N de M filas · Xh" counter, which is the whole point of reviewing a file
   and used to scroll away (explicit user request). It is NOT repeated in the footer — that
   button already says "Importar N entradas", and a third copy of the same number is a third
   thing that can drift.

   `overflow: visible` is required, not cosmetic (constraint 4): with the card clipping, the band
   would never pin. The two radius rules are what that override costs — with the card no longer
   clipping, square band corners would paint outside its rounded ones. Same trade the weekly card
   made while its cell editor was a popover (see the note above .horus-ts-grid).

   `top: 0` with no offset because the app's real scroll container is .mud-main-content, which
   already starts below the fixed AppBar. */
.horus-import-preview {
    overflow: visible;
}

.horus-import-preview > .horus-inline-form-header {
    position: sticky;
    top: 0;
    z-index: 2;
    border-radius: 8px 8px 0 0;
}

.horus-import-preview > .horus-inline-form-footer {
    border-radius: 0 0 8px 8px;
}

/* Why the COLUMN headers are not sticky too — tried, measured, reverted. Per constraint 4 the
   .mud-table-container is the sticky scrollport for anything inside the table, and since it
   doesn't scroll vertically, `position: sticky` on the header cells applies and does nothing. CSS
   offers no way out: `overflow-x: auto` with `overflow-y: visible` is not a valid pair.
   Dropping the container's clip was rejected on measurement, not taste: the table's min-content is
   666px and the card is ~612px wide at a 900px viewport with the drawer open, so the page would
   scroll sideways there. The only supported alternative is MudDataGrid's FixedHeader + a fixed
   Height, which gives the grid an inner scrollbar — deliberately not taken, since on a laptop that
   leaves ~6 rows visible on a screen whose whole job is reviewing rows. */

/* ---------- Column geometry ---------- */

/* The three columns holding a control or a couple of digits — expander, checkbox, row number —
   take what they need and no more, so the width goes to the task path and the description, which
   is what gets read (reported twice: first the row number, then the checkbox). */
.horus-import-narrowcol {
    width: 1px;
    white-space: nowrap;
}

/* 16px a side around a 32px checkbox is more padding than control. Three selectors for one
   decision: the two classed columns (header and cell) plus the expander's cell by position
   (constraint 3). Spelled out at three classes per constraint 1 — an earlier version of this rule
   lost to the grid's own padding and left two of the three columns at 16px. */
.horus-section .mud-table-container thead th.horus-import-narrowcol,
.horus-section .mud-table-container tbody td.horus-import-narrowcol,
.horus-import-preview .mud-table-container tbody td.mud-table-cell:first-child {
    padding-left: 8px;
    padding-right: 8px;
}

/* MudDataGrid renders the hierarchy button with `ma-n3 pa-1` — a flat `margin: -12px !important`
   so a 32px icon button doesn't inflate a dense row. In a column this narrow that pulled the
   chevron 12px outside its cell, hanging off the row's left edge (reported). -4px still keeps the
   button from growing the row (24px effective against cells already ~44px tall). */
.horus-import-preview .mud-table-container tbody td.mud-table-cell:first-child > button {
    margin: -4px !important;
}

/* A row that grew — a wrapped description, a stacked task path — left its checkbox pinned to the
   top of the cell (reported). The wrapper takes the full row height so the box stays centred
   whatever the tallest cell does. Height on a wrapper, never a CellClass on the <td>: making the
   cell itself a flex container is what broke the alignment in the first place, and it's the rule
   the IsFullTime switch already follows in ProjectAssignmentsSection. */
.horus-import-checkcell {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
}

/* ---------- Cell content: values, and the errors under them ---------- */

/* Row problems as plain text under the value they belong to, one per line — NOT chips (explicit
   user request). A chip reads as a value or a filter you could act on; these are just the reasons
   a row can't come in. */
.horus-import-issue {
    display: block;
    margin-top: 2px;
    font-size: .8125rem;
    line-height: 1.35;
    font-weight: 600;
    color: var(--md-sys-color-error);
}

/* Possible duplicates don't block the row, so they aren't error-coloured. MD3 defines no `warning`
   role — one of the documented exceptions where the MudBlazor palette token is the right source
   (see HorusMudTheme.cs on Success/Warning/Info). */
.horus-import-issue-warning {
    color: var(--mud-palette-warning);
}

/* The file's own text for a row that matched nothing: same Client › Project › Task shape as a
   resolved row (so the column reads consistently) but in one muted tone, because it is NOT a
   resolved task and must not look like one. Overrides the three shared .horus-ts-task-* tones via
   the parent, leaving those classes untouched for their own screens. */
.horus-import-path-raw > * {
    color: var(--horus-text-secondary);
    font-weight: 500;
}

/* Scanning a preview is mostly scanning the numbers (explicit user request). */
.horus-import-hours {
    font-weight: 700;
}

/* ---------- Day-level flags (rendered next to a row's date) ---------- */

/* Error tone for the hard 24h ceiling. The band warning has no colour rule of its own: it reuses
   .horus-warn-icon, the amber the timesheet grids already use for exactly this question. */
.horus-import-dayflag-error {
    color: var(--md-sys-color-error);
}

/* Size and alignment for both flags, kept apart from the colour classes because one of those is
   shared with two grids and MonthHoursWarning — resizing it there would silently resize the icon
   on three other screens. `!important` per constraint 2 (.horus-warn-icon pins its font-size
   with one).

   MudIcon renders an inline-block, and under the default `baseline` its BOTTOM edge lands on the
   text's baseline: the descender hangs below and the icon reads as sitting high (reported, measured
   at 3px between centres). `text-bottom` seats it on the text's font box instead — picked by the
   user after comparing against `middle`. The left margin keeps an 18px glyph from reading as part
   of the date rather than as a mark about it. */
.horus-import-dayflag {
    font-size: 1.125rem !important;
    vertical-align: text-bottom;
    margin-left: 6px;
}

/* ---------- Row-level: possible duplicates ---------- */

/* A duplicate is a statement about the ROW, not a problem with one of its values, so it's marked
   on the row as a whole with a message across the bottom of it.

   Three versions, and the reasons are worth keeping. First: a `secondary-container` band at 55%,
   left-aligned, 12px — it read as a separator BETWEEN two rows rather than as something about one
   of them ("it isn't really clear"). Second: a 2px error-coloured outline around the row with the
   message centred — the outline did answer "which row?", but reads as "this row is broken" when
   the row in fact imports fine. Now: no outline, a barely-there tint and room to breathe, both
   explicit user requests.

   Colour and icon stay error/stop even though a duplicate does NOT block — the row stays ticked
   and importing it is the user's call — because it's the one thing on this screen that silently
   doubles real hours if it goes unnoticed. */
.horus-import-duprow {
    position: relative;
}

/* The strip is absolutely positioned and takes no space of its own, so the cells reserve it:
   20px of line box (14px at 1.4) + 8px of its own padding + 8px of air. Measured — without this
   the message overlapped the row's content by 5px. Three classes per constraint 1. */
.horus-section .mud-table-container tbody tr.horus-import-duprow > td.mud-table-cell {
    padding-bottom: 36px;
}

/* Centred and a step larger than the per-field errors (explicit user request): those annotate one
   value, this qualifies the whole row. The tint is deliberately near-invisible — enough to read as
   a band belonging to the row, not enough to compete with the row's own content or with the red
   errors under a date or an hours value. */
.horus-import-dupstrip {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 4px 12px;
    background-color: color-mix(in srgb, var(--md-sys-color-error) 8%, transparent);
    color: var(--md-sys-color-error);
    font-size: .875rem;
    font-weight: 600;
    line-height: 1.4;
}

/* ---------- The fix-it controls inside an expanded row ---------- */

/* Wide enough to read, not so wide they stretch across a 990px grid for a two-digit hours value.
   The task pickers are NOT capped here: they hold "Client — Project" and get the full row (see the
   note in TimesheetImport.razor about ChildRowContent already being a cell). */
.horus-import-fixfield {
    max-width: 260px;
}

/* ── Assignments Gantt (/assignments) ────────────────────────────────────────
   The team's assignments for one year: a label column plus twelve month columns, with one
   bar per assignment grouped under each person's name. Built out of the same vocabulary as
   the timesheet weekly grid (explicit user request that it work like that view) — a
   .horus-section card with a header band carrying the period selector and a footer band
   carrying the actions, a tinted plane for the chart itself, and boxed cells inside it.

   It is NOT a table: the label column and the month strip are two cells of a 2-column grid,
   and each month strip is its own nested 12-column grid. That nesting is what lets a bar be
   positioned as a PERCENTAGE of its own row (see BarExtent in Assignments.razor for why it
   can't be a grid item sharing tracks with the cells) while still lining up exactly with the
   month headers above it, which are the same nested grid. */
.horus-gantt-grid {
    --horus-gantt-label-width: 220px;
    /* 18% is what makes a bar read as filled against the white track without competing with the
       text on it: rgb(213,214,214) in light, rgb(75,77,80) in dark. Declared here rather than in
       md3-tokens.css because it's one component's fill, not a palette role. */
    --horus-gantt-bar-bg: color-mix(in srgb, var(--md-sys-color-on-surface) 18%, var(--md-sys-color-surface));
    position: relative;
    display: grid;
    grid-template-columns: var(--horus-gantt-label-width) 1fr;
    /* 12 months at a legible width plus the label column. Below this the wrap scrolls
       sideways rather than squeezing the months into unreadable slivers — a Gantt whose
       columns are 20px wide has stopped being a Gantt. */
    min-width: 880px;
}

/* The header band: sticky, and the only band this card has now (the filters and the create
   action moved up here from a footer — explicit user request, so that everything qualifying the
   chart stays on screen while it scrolls).

   Sticky needs `overflow: visible` on the CARD (below): .horus-section ships `overflow: hidden`
   to keep its bands off the rounded corners, but an `overflow: hidden` ancestor becomes the
   scrollport, and since this card has no height constraint it never scrolls — so the band would
   position against a box that never moves and never stick. Same trade the timesheet weekly card
   and the import screen already make, with the same cost: the bands have to round themselves.
   `top: 0` because the app's real scroller is .mud-main-content, which already starts below the
   fixed app bar.
   z-index above the bars (1) and the current-month overlay (2), so the chart passes UNDER the band
   rather than through it. */
.horus-gantt .horus-section-header {
    position: sticky;
    top: 0;
    z-index: 4;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    /* flex-wrap, not a media query: the header carries a year selector, a people multi-select, a
       switch, a count and a button. When they stop fitting, the ROW wraps — the controls
       themselves are never squeezed (the rule already established for the segmented filters). */
    flex-wrap: wrap;
    /* 12px, not the shared 8px: a multi-select with chips in it is taller than a caption, and at
       8px the band clamped it. */
    padding: 12px 24px;
    border-radius: 8px 8px 0 0;
}

.horus-gantt {
    overflow: visible;
}

.horus-gantt-header-filters,
.horus-gantt-header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* `flex-basis: 0`, NOT `auto` — this is what keeps the actions group on the same line.
   Flex decides where to BREAK a line using each item's hypothetical main size (its basis clamped by
   min/max), and only shrinks items afterwards, within a line. With `auto` the basis is this group's
   max-content, which became effectively unbounded once the people selector lost its max-width — so
   the header broke before it ever tried to shrink, and the count and the button dropped to a second
   row. Measured: basis auto put this group at the full 1102px with the actions pushed below; at
   basis 0 the actions take their 304px first and this group grows into the remaining 782px.
   The min-width is the real floor: below it the header wraps as a whole, which is the intended
   behaviour at genuinely narrow widths. */
.horus-gantt-header-filters {
    flex: 1 1 0;
    min-width: 280px;
}

/* The people filter's own group grows with the row, so the selector inside it has slack to take.
   Without this the group stays content-sized and `flex: 1 1 auto` on the child has nothing to
   expand into — `.horus-filter-group` ships `flex-shrink: 0` for fixed-size filters, and this one
   is the exception. */
.horus-gantt-header-filters .horus-filter-group:has(.horus-gantt-people) {
    flex: 1 1 0;
    min-width: 0;
}

.horus-gantt-header-actions {
    gap: 16px;
    flex-shrink: 0;
}

/* Takes ALL the slack in the header (explicit user request) — no max-width. It's the only control
   here that can grow: the year selector, the switches, the count and the button are all
   content-sized, so whatever the header doesn't need lands on this one. The min-width is what makes
   the row wrap instead of crushing it when there genuinely isn't room. */
.horus-gantt-people {
    flex: 1 1 auto;
    min-width: 180px;
}

/* MudSwitch defaults to a bottom margin meant for stacked form fields; in a single-line band it
   just pushes the switch off centre. Shared by the Gantt's two filters and the import preview's
   grouping toggle — it was `.horus-gantt-internal-toggle` while the Gantt was its only user, and
   was renamed rather than copied when the second band needed the same single line of CSS. */
.horus-inline-switch {
    margin: 0;
}

/* The chart's own plane, inset from the card's bands — same role .horus-ts-gridwrap plays for
   the weekly grid, and the same reason it carries the tint: the cells below are drawn on
   `surface`, so they need a darker ground to read as boxes against. */
.horus-gantt-wrap {
    background: var(--md-sys-color-background);
    padding: 16px 24px;
    overflow-x: auto;
    /* Rounds its own bottom corners: with the footer band gone this is the card's last child, and
       the card can no longer clip it (see the overflow note on the header above). */
    border-radius: 0 0 8px 8px;
}

/* The 12-column strip, used three times over: the month header, an assignment's track, and the
   placeholder track of a person with nothing this year. Declared once so all three stay in
   lockstep — if the header and the tracks ever disagreed, every bar would be off by a column. */
.horus-gantt-months {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
}

/* A track is the row's month strip: the plane a bar is positioned over, and the thing that
   draws the month gridlines.

   The lines are a TILED BACKGROUND, not twelve cell elements. The first version rendered a div
   per month per row, which on a year like the dev data (18 people, 296 assignments) came to
   3,552 nodes drawing nothing but eleven vertical lines each. One gradient tile, repeated every
   1/12 of the width, is the same picture: `background-size` does the division, so the lines stay
   locked to the month headers above at any width without any element to keep in sync.

   The tile paints a line at the START of each twelfth, which lands one on the left edge too —
   that one IS the track's left border, so no separate border-left is needed; the right edge is
   the only one the tile can't reach. */
.horus-gantt-track {
    position: relative;
    background-color: var(--md-sys-color-surface);
    background-image: linear-gradient(to right, var(--mud-palette-table-lines) 0 1px, transparent 1px);
    background-size: calc(100% / 12) 100%;
    background-repeat: repeat-x;
    border: 1px solid var(--mud-palette-table-lines);
    border-left: none;
    /* Square on all four corners (explicit user request). It used to round only the right pair —
       the left one couldn't round anyway, since that edge is drawn by the tiled background rather
       than by a border, so the result was lopsided. */
    border-radius: 0;
}

/* The current month, down the full height of the chart. Absolutely positioned over the grid, so
   the offset has to clear the label column first — hence the arithmetic on the same variable the
   grid template uses, rather than a number repeated here.
   Behind the bars (they sit at z-index 1) and never a click target. */
.horus-gantt-nowcol {
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(var(--horus-gantt-label-width)
        + var(--horus-gantt-now-index) * (100% - var(--horus-gantt-label-width)) / 12);
    width: calc((100% - var(--horus-gantt-label-width)) / 12);
    /* secondary-container at 40% (explicit user request, replacing a neutral on-surface tint).
       Semi-transparent and not opaque so it reads as a HIGHLIGHT laid over the chart rather than a
       column painted into it: it crosses the tracks, the gaps and the person bands, and each of
       those keeps showing through.

       40% is what makes it visible over both grounds without competing with the bars: over the
       plane it lands on rgb(245,229,230) in light and rgb(53,42,42) in dark, and over a person
       band (which is `on-surface-variant`, a DIFFERENT role — see .horus-gantt-person) it still shifts the
       tone enough for the column to read as continuous.

       ABOVE the bars (explicit user request): it's a highlight laid OVER the chart, so it has to
       tint the bars it crosses too. It used to sit below them (bars are z-index 1), and a bar
       passing through the current month showed its own flat grey — which read as a hole punched in
       the column. `pointer-events: none` is what keeps those bars clickable underneath. */
    z-index: 2;
    background: color-mix(in srgb, var(--md-sys-color-secondary-container) 40%, transparent);
    pointer-events: none;
}

.horus-gantt-corner,
.horus-gantt-monthhead {
    font-size: .75rem;
    font-weight: 700;
    text-align: center;
    padding-bottom: 8px;
    /* Same muted tone the weekly grid's day headers use — a column label is scaffolding, not
       content. --horus-disabled-content, not --mud-palette-text-disabled: see md3-tokens.css. */
    color: var(--horus-disabled-content);
}

/* The month you're in, in the year you're looking at. Full-strength and bold against the muted
   others — the same device the weekly grid uses to pick today's column out of the week. */
.horus-gantt-monthhead.horus-gantt-now {
    color: var(--md-sys-color-on-surface);
}

/* Person band — the group header its assignment rows hang under, and the control that folds
   them away. Spans both columns of the outer grid.

   Filled with `on-surface-variant` (explicit user request, after trying `secondary` and then
   `tertiary`). It is a CONTENT role being used as a container, which is unusual for MD3 — but it's
   also the one role this palette keeps on the NEUTRAL seed while surfaces and borders go warm (see
   Arquitectura.md §8), so it's what makes these bands read grey rather than as more brand colour.
   Paired with `surface` for the content, since a content role has no "on-" of its own; measured
   9.36:1 in light (white on #42474E) and 8.47:1 in dark (#282A2D on #C2C7CF).

   Whatever role is chosen here, it must NOT be the current-month overlay's (`secondary-container`):
   the overlay crosses these bands, and the same token on both would make the column vanish exactly
   where it met one. Checked for this pair — the overlay composites to rgb(142,130,132) over the
   light band and rgb(154,145,148) over the dark one, distinct against both.
   A solid fill replaces the earlier transparent-plus-border-on-top version: the border existed to
   make the boundary register at all, and a filled band says it outright. */
.horus-gantt-person {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 12px;
    margin-left: -24px;
    margin-right: -24px;
    padding: 2px 24px;
    background: var(--md-sys-color-on-surface-variant);
    color: var(--md-sys-color-surface);
}

.horus-gantt-person:first-of-type {
    margin-top: 0;
}

/* The whole name+chevron+count area is the fold control. A bare <button> reset to inherit the
   band's own colour — a MudButton here would bring its own padding, ripple and text colour, all
   of which would have to be undone. */
.horus-gantt-person-toggle {
    display: flex;
    align-items: center;
    /* Explicit: the app's global `button` reset sets `justify-content: center`, and since this rule
       only declared `display: flex`, that centred the content in a full-width band (reported). A
       `text-align: left` cannot fix it — the children are flex items, not text. */
    justify-content: flex-start;
    gap: 6px;
    flex: 1 1 auto;
    min-width: 0;
    padding: 8px 4px;
    border: none;
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.horus-gantt-person-name {
    font-size: .875rem;
    font-weight: 700;
    color: inherit;
}

/* How many rows hang under this name — the number that makes a FOLDED band still informative. */
.horus-gantt-person-count {
    font-size: .75rem;
    font-weight: 600;
    padding: 1px 8px;
    border-radius: 999px;
    /* A state layer of the band's own content colour, so it tints with the band in either theme
       instead of needing a colour of its own. */
    background: color-mix(in srgb, var(--md-sys-color-surface) 18%, transparent);
}

/* The "+" sits on the filled band, so it takes the band's content colour rather than the brand
   primary it used when the band was transparent. */
.horus-gantt-person-add.mud-icon-button {
    color: var(--md-sys-color-surface);
}

/* Client / Project, stacked. The two spans come from the shared task-path classes
   (.horus-ts-task-client/-project), which are inline by default — the column direction here is
   what stacks them, so the path reads the same as it does everywhere else without a second
   copy of its typography. */
.horus-gantt-rowlabel {
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* Row height lives here rather than on the track: the label is the taller of the two, so
       letting it set the row keeps both cells the same height with no fixed number to keep in
       sync. min-height, not height, so a long project name can wrap without being clipped. */
    min-height: 44px;
    padding: 4px 8px;
    overflow: hidden;
}

/* "No assignments in {year}" — a person picked in the selector who has nothing this year. Muted
   and italic: it's the absence of a row, not a row. */
.horus-gantt-rowlabel-empty {
    font-size: .8125rem;
    font-style: italic;
    color: var(--horus-disabled-content);
}

/* MudTooltip's own wrapper, which is what actually carries the bar's left/width (see the
   comment at the call site — the tooltip wraps its child, so positioning the button itself
   would position it relative to a wrapper that is in the flow). */
.horus-gantt-baranchor {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    /* Above the month cells, which are ordinary in-flow grid items. */
    z-index: 1;
}

.horus-gantt-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 28px;
    padding: 0 10px;
    border: none;
    /* Pill, like every other button in this app — and the shape is load-bearing here: a rounded
       end means "the assignment starts/ends inside this year", which the squared variants below
       are the counterpart of. */
    border-radius: 999px;
    /* NEUTRAL grey, mixed from on-surface over surface so it inverts with the theme — light
       grey bars on white, mid grey bars on the dark card. The first version used
       secondary-container, which is #FFDAD6 in light: every one of the 296 bars came out pink.
       That's the same correction the suggestion chips needed, and the same standing preference
       behind it — hierarchy in this app is carried by weight and shape, not by brand tone
       (see the timesheet views' own note). Colour here is reserved for the ONE thing on a bar
       that is a distinction: full time, below. */
    background: var(--horus-gantt-bar-bg);
    color: var(--md-sys-color-on-surface);
    cursor: pointer;
    overflow: hidden;
    font: inherit;
}

.horus-gantt-bar:hover:not(:disabled) {
    /* MD3 hover state layer: 8% of the content colour over the container's own fill. */
    background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, var(--horus-gantt-bar-bg));
}

.horus-gantt-bar:disabled {
    cursor: default;
}

/* Full time, distinguished by HUE rather than by weight or size: the tertiary family is this
   palette's one blue (see Arquitectura.md §8) and is the only tone that reads as clearly
   different from the bars beside it in BOTH themes. primary-container would have been the
   obvious pick and is the documented trap — it is #93000C in dark, a saturated red that would
   make every full-time bar the loudest thing on the page (same note as the Total column).
   Colour is never the only carrier: the tooltip says "Full time" in words. */
.horus-gantt-bar-fulltime {
    background: var(--md-sys-color-tertiary-container);
    color: var(--md-sys-color-on-tertiary-container);
}

.horus-gantt-bar-fulltime:hover:not(:disabled) {
    background: color-mix(in srgb, var(--md-sys-color-on-tertiary-container) 8%, var(--md-sys-color-tertiary-container));
}

/* An assignment that already ran before this year, or keeps running after it, gets that edge
   squared off — the bar is cut by the edge of the year rather than ending there. Reading the
   shape is how you tell "started in January" from "started some time before January". */
.horus-gantt-bar-openstart {
    border-top-left-radius: 2px;
    border-bottom-left-radius: 2px;
}

.horus-gantt-bar-openend {
    border-top-right-radius: 2px;
    border-bottom-right-radius: 2px;
}

.horus-gantt-bar-label {
    font-size: .75rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mud-input-number-control.mud-input-showspin .mud-input-adornment-end {
    margin-right: 32px;
}
/* ---------------------------------------------------------------------------
   Leave balances (/admin/pto-balances)

   An editable sheet, not a read-only grid: the width of each field IS the signal
   for how much is expected to be typed into it. Without this the numeric field
   stretches across its whole column, and a two-character input looks like it is
   asking for a paragraph.
   --------------------------------------------------------------------------- */
.horus-balances-typeselect {
    min-width: 220px;
}

/* Wide enough for the WORST case, not the typical one: the field has to hold a sign, two
   digits, a decimal separator and a decimal ("-25,5") next to a clear button and a pair of spin
   buttons, which between them eat ~70px of the box. At 140px the text area left over clipped
   anything longer than a plain integer. */
.horus-balances-days {
    max-width: 200px;
}

/* The name gets the room, the country gives it up: a country name plus its flag is short and
   bounded, while a full name is neither. The note keeps whatever is left — it is still the one
   field in the row that grows to any purpose. */
.horus-balances-table td:nth-child(1),
.horus-balances-table th:nth-child(1) {
    width: 290px;
}

.horus-balances-table td:nth-child(2),
.horus-balances-table th:nth-child(2) {
    width: 150px;
}

.horus-balances-table td:nth-child(3),
.horus-balances-table th:nth-child(3) {
    width: 220px;
}

/* ── Client time report (/reports/time) ───────────────────────────────────────
   Not a MudDataGrid: a report is read and exported, not paged and edited, and its blocks carry
   subtotals a data grid has nowhere to put. The table is plain markup styled here, the same call
   the weekly timesheet grid makes for the same reason. */

/* The filters that should give up width first when the toolbar gets tight — the month picker and
   the segmented button are fixed-size, and the segmented one can't shrink without clipping its
   segments (see the notes on .horus-toolbar-row). `flex: 1 1 0` with a real floor, never
   `1 1 auto`: line-breaking in flex uses the hypothetical size, so an unbounded basis wraps the
   row before it ever tries to shrink. */
.horus-report-filter-grow {
    flex: 1 1 0;
    min-width: 180px;
}

/* ...and the CONTROL has to grow with its group, or the group's share of the row just becomes
   empty space to the right of a content-width field (measured: 481px groups holding a 232px
   control, so ~188px of slack each). `.horus-filter-group` lays its caption out to the LEFT of
   the control, so the control is the last child. `min-width: 0` lets it shrink past its content
   when the row gets tight, instead of overflowing — a flex item's automatic minimum is its
   content, which is what would otherwise push the row wider than the card. */
.horus-report-filter-grow > :last-child {
    flex: 1 1 auto;
    min-width: 0;
}

/* Its own scrollport so a long description can't push the page sideways. */
.horus-report-tablewrap {
    overflow-x: auto;
}

.horus-report-table {
    width: 100%;
    border-collapse: collapse;
    font-size: .875rem;
}

.horus-report-table th {
    text-align: left;
    font-weight: 700;
    padding: 8px 16px;
    background: var(--md-sys-color-background);
    color: var(--horus-text-secondary);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    white-space: nowrap;
}

.horus-report-table td {
    padding: 8px 16px;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    vertical-align: top;
}

/* The columns that carry a known-width value stay at their content width, so the Notes column —
   the substance of this report — keeps everything that's left. */
.horus-report-col-date,
.horus-report-col-hours {
    width: 1px;
    white-space: nowrap;
}

.horus-report-col-hours {
    text-align: right;
    font-weight: 600;
}

.horus-report-col-person,
.horus-report-col-task {
    width: 1px;
    white-space: nowrap;
}

/* The path is a flex container (that's where its "›" separators come from), and flex wrapping
   doesn't answer to white-space — without this a two-part path breaks onto two lines while the
   cell around it is still nowrap. */
.horus-report-col-task .horus-ts-dayentry-path {
    flex-wrap: nowrap;
}

/* Air around the rules that separate period / client / grouping. The client field grows to fill
   its share of the row, so its right edge landed flush against the divider. Scoped to this page:
   the listings' toolbars are tighter rows and were not asked to change. */
/* The language picker inside the export menu. A MudMenu's popover has no padding of its own
   around non-item content, so the group provides it — matching the horizontal inset of the
   MudMenuItems below it, or the segmented button would sit proud of the list it heads. */
.horus-report-exportlang {
    padding: 8px 16px 4px;
    gap: 6px;
}

.horus-time-report .horus-toolbar-divider,
.horus-hs-filters .horus-toolbar-divider,
.horus-reconciliation-filters .horus-toolbar-divider,
.horus-invoice-filters .horus-toolbar-divider,
.horus-cash-filters .horus-toolbar-divider,
.horus-account-filters .horus-toolbar-divider {
    margin-inline: 8px;
}

/* A block heading is a band, not a row of data: it spans the table and carries its own subtotal.
   `background` (the canvas) rather than a tinted container — the same choice the timesheet grid
   makes for its non-content surfaces, so a report embedded in a card doesn't grow a second accent. */
.horus-report-grouprow td {
    background: var(--md-sys-color-background);
    padding: 0;
}

/* The band is one big toggle. `justify-content` has to be stated: the app's global button reset
   centres flex content, and a `text-align` can't fix that because these are flex items. The gap
   sits between the chevron and the name; the total is pushed to the far edge by `margin-left:auto`
   so it lands under the Hours column regardless of how long the heading is. */
.horus-report-grouptoggle {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    width: 100%;
    padding: 6px 16px;
    background: none;
    border: none;
    cursor: pointer;
    color: inherit;
    font: inherit;
    text-align: left;
}

.horus-report-grouptoggle:hover {
    background-color: var(--horus-ts-hover-bg);
}

.horus-report-grouptoggle .horus-report-grouptotal {
    margin-left: auto;
}

.horus-report-groupname {
    font-weight: 700;
}

.horus-report-grouptotal {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* Non-billable rows read as an aside: they only appear when the switch is on, and the point of
   turning it on is to see what ISN'T being charged. Muted text, not a colour — nothing is wrong
   with these hours. */
.horus-report-nonbillable td {
    color: var(--horus-text-secondary);
    font-style: italic;
}

.horus-report-summary {
    border-collapse: collapse;
    font-size: .875rem;
}

.horus-report-summary td {
    padding: 2px 16px 2px 0;
}

/* Two fixed options, so it only needs the width of the longer word — left to itself a MudSelect
   stretches to fill the toolbar row. */
.horus-report-langselect {
    width: 120px;
}

.horus-report-datepicker {
    width: 170px;
}

/* The reconciliation's status filter. A fixed width because a MudSelect left to itself stretches
   to fill the toolbar row, and 260px is what holds the two longest labels together ("Parcial,
   Facturado de más") before the control elides — the common case is one or two statuses, which is
   what somebody hunting a discrepancy selects. */
.horus-reconciliation-statusfilter {
    width: 260px;
}

/* ---- The file of a reconciliation block (expanded row) ---- */

/* The expanded content aligns with the table body, not with the card's edge. */
.horus-reconciliation-detail {
    padding: 12px 16px 16px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* `.horus-subtable` brings `margin: -12px 56px`, computed for a subtable that is the ONLY child
   of the expanded cell: the 56px separate it from the edges and the -12px cancel MudDataGrid's
   native padding. Here there are THREE tables with titles between them, so that inset added to
   this panel's (the titles ended up 40px to the left of their tables) and the negative margin pulled
   each table against its own title. It is reset for this container instead of touching the shared
   class, which is still correct for its other consumers. */
.horus-reconciliation-detail .horus-subtable {
    margin: 0;
}

/* The expander column needs no width: its header is empty and the button measures 40px. And it is
   reached BY POSITION — `HierarchyColumn` accepts `HeaderClass` but not `CellClass` (the MUD0002
   analyzer flags it), so the class alone only reached the header and the cell still measured 72px.
   It is always the first column of this grid, which is the same device the specificity note
   further up in this file uses.

   And it goes with the LONG selector because of trap #1 in that note: MudBlazor's cell rules —and
   this file's padding rule— are three classes plus a type, so the first version of this
   (one class and two types) loaded, matched the right ancestor and lost silently: the cell still
   measured 72px with its 16px of padding. Measured before believing it. */
.horus-section .horus-reconciliation-grid .mud-table-container thead th.mud-table-cell:first-child,
.horus-section .horus-reconciliation-grid .mud-table-container tbody td.mud-table-cell:first-child {
    width: 48px;
    padding-inline: 4px;
}

/* The "this difference is already explained" mark. Secondary tone and not a semantic one: it is not
   a warning or an error, it is a signal that there is NOTHING to investigate here. */
.horus-reconciliation-note-icon {
    color: var(--horus-text-secondary) !important;
    font-size: 1rem !important;
    margin-inline-start: 4px;
}

/* The unbilled hours broken down by reason, inside the KPI tile. Two columns and not a table:
   at most four rows of label + figure, with the figure aligned right so they can be compared at a
   glance. */
.horus-reconciliation-reasons {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 2px 12px;
    margin-top: 8px;
}

.horus-reconciliation-reason {
    display: contents;
    color: var(--horus-text-secondary);
    font-size: 0.8125rem;
}

/* The "why it does not tie" block, at the top of the file: the text grows and the button stays
   against the edge, so the action is in the same place no matter how much was written. */
.horus-reconciliation-blocknote {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
}

/* The note, inside the cell of the line's text. Dense and with no margin of its own: it shares the cell
   with the description, and MudBlazor's alert brings 16px of its own. */
.horus-reconciliation-note.mud-alert {
    padding: 4px 8px;
}

/* What the hours say: several lines of short text in one cell, each readable on its
   own. It is the evidence that resolved Midinero and Hot Design, so it is not truncated. */
.horus-reconciliation-entrytext {
    font-size: .8125rem;
    line-height: 1.35;
}

/* Collapsed, each text keeps ONE line — the beginning of every one stays readable, which is what
   makes four of them worth showing at all. Nothing is dropped: the link below opens them in full.

   A line clamp PER TEXT, never `white-space: nowrap`: in a table cell of automatic width, nowrap
   makes the cell's min-content the whole paragraph, so instead of an ellipsis the inner table grew
   past the card and pushed the outer grid's last columns off screen (measured, not guessed). The
   clamp wraps within whatever width the cell already had and cuts at the first line. */
.horus-reconciliation-texts-collapsed .horus-reconciliation-entrytext {
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* The count of what is not shown and the toggle, on one line: they answer the same question. */
.horus-reconciliation-texts-more {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: .8125rem;
}

/* A card whose body IS a stack of `.horus-toolbar-row`s: each row brings its own padding and its
   own bottom rule, so the card adds no padding and the LAST row drops its rule (otherwise it draws
   a line across the bottom of the card with nothing under it). A new filter card adds its class
   HERE rather than restating the rule — which is how this one broke: `.horus-settlement-filters`
   wrote its own copy further down, and when the quote cards were inserted right after this
   selector list the list swallowed `.horus-rate-cards`'s declaration block. For a while the last
   filter row of BOTH reports was `display: grid` with a 210px minimum instead of a flex toolbar,
   and neither of them dropped its bottom rule. Found while building /reports/reconciliation. */
.horus-time-report .horus-toolbar-row:last-child,
.horus-hs-filters .horus-toolbar-row:last-child,
.horus-settlement-filters .horus-toolbar-row:last-child,
.horus-reconciliation-filters .horus-toolbar-row:last-child,
.horus-invoice-filters .horus-toolbar-row:last-child,
.horus-cash-filters .horus-toolbar-row:last-child,
.horus-account-filters .horus-toolbar-row:last-child {
    border-bottom: none;
}

/* ---- Reference quotes, inside the month picker's own section (/settlements) ----
   Three cards of the SAME width (explicit request): BCU, Santander, BROU. `auto-fit` with a floor
   rather than `repeat(3, 1fr)` so they wrap instead of squeezing on a narrow window — at three
   across each one still holds "39,470" without wrapping, measured at the body size.

   They sit BELOW the toolbar row, which is what brings that row's bottom border back (it is styled
   away only while it is :last-child) — so the divider between the picker and the cards costs
   nothing extra. */
.horus-rate-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 12px;
    padding: 12px 20px;
}

/* Flat, not another .horus-section: this is a tile INSIDE a card, and a second shadow inside the
   first one reads as a card that came loose. The border alone separates it from the surface it
   shares with its parent. */
.horus-rate-card {
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: 8px;
    padding: 10px 12px;
}

.horus-rate-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 6px;
}

.horus-rate-card-name {
    font-weight: 600;
}

/* The quote's day (chip) and, if the month is open, the time of our request and the button
   to ask again. */
.horus-rate-card-stamp {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Genuinely tiny (explicit request): MudBlazor's Small is a 32px button for a 20px
   icon, which stretches an 18px strip. Two classes to beat `.mud-icon-button-size-
   small` without `!important`. */
.horus-rate-card-refresh.mud-icon-button {
    padding: 2px;
    min-width: 0;
}

.horus-rate-card-refresh .mud-icon-root {
    font-size: 1rem;
    height: 1rem;
    width: 1rem;
}

.horus-rate-card-date,
.horus-rate-card-rowdate {
    font-size: 0.75rem;
    color: var(--horus-text-secondary);
    font-variant-numeric: tabular-nums;
}

.horus-rate-card-rowdate {
    margin-left: 6px;
}

/* Label + the two sides. `tabular-nums` here and NOT on the KPI tiles: this is a column of figures
   meant to be compared down and across, which is exactly what tabular figures are for. */
.horus-rate-card-grid {
    display: grid;
    grid-template-columns: auto 1fr 1fr;
    gap: 2px 10px;
    align-items: baseline;
    font-variant-numeric: tabular-nums;
}

/* The BCU publishes one figure, so its grid has one value column and the second stays empty. */
.horus-rate-card-grid-single {
    grid-template-columns: auto 1fr;
}

.horus-rate-card-unit,
.horus-rate-card-colhead {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--horus-text-secondary);
}

/* The currency, said ONCE for the whole card instead of beside all four figures — the rule against
   a currency column is about repeating one value down every row, which is what this avoids. */
.horus-rate-card-unit {
    justify-self: start;
}

.horus-rate-card-colhead,
.horus-rate-card-value {
    text-align: right;
}

/* Flex for the GAP: the date chip ended up stuck to the quote type ("SupernetAgo 31, 2026")
   because the markup collapses the whitespace between the text and the MudChip. 4px, the minimum that
   separates them (explicit request), and along the way the chip ends up centred with the label. */
.horus-rate-card-rowlabel {
    display: flex;
    align-items: center;
    gap: 4px;
    color: var(--horus-text-secondary);
    white-space: nowrap;
}

.horus-rate-card-value {
    font-weight: 600;
}

/* The time report's client, shown as a FIXED LABEL instead of a picker when the acting account is
   a client's own (§24). Sized to sit on the same baseline as the dense inputs beside it in the
   toolbar, so the row doesn't jump when the same screen is opened by the studio and by a client. */
.horus-report-client-fixed {
    font-weight: 600;
    line-height: 32px; /* matches Margin.Dense input height, measured */
    white-space: nowrap;
}

/* A `.horus-subtable` with nothing to show says so in a ROW of its own, spanning the columns and
   centred — inside the table's frame, on its white body, rather than as a paragraph beside it.
   Centred because it describes the whole table and not its first column; left-aligned under the
   first header it read as a value that failed to load. */
/* Qualified by the table, not a bare class: MudSimpleTable sets `text-align: start` with
   `.mud-simple-table table * tr > td` (one class, three types), which beats a single class — a
   bare `.horus-subtable-empty` changed nothing and the message stayed left-aligned under the first
   column, reading like a value that failed to load. Same specificity trap as the `.me-6` override
   documented for MudNumericField, and the reason the rest of this file qualifies its selectors. */
.horus-subtable td.horus-subtable-empty {
    text-align: center;
    color: var(--horus-text-secondary);
}

/* The button that sits BESIDE a `.horus-subtable` — "add pay" on /admin/people.

   It has to clear the same two insets the table's own columns clear, or it reads as a separate
   block floating to the left of the table it belongs to (reported): `.horus-subtable` is inset
   56px by its own margin, and each of its cells adds 16px of padding. Expressed as the same two
   numbers rather than one magic 72px, so that changing the table's inset moves both together.
   The 10px vertical matches the cell padding too, which keeps the rhythm of the block. A MudBlazor text button carries 16px of horizontal padding of its own
   (measured, not assumed — an earlier guess of 8px left it 6px out), so the button is pulled back
   by exactly that much: its CONTENT then starts where the column text does, while the button keeps
   its full padding and therefore its full click target. Compensating its own padding rather than
   trimming the inset above is what keeps this correct if either number changes. */
/* margin-top exists because `.horus-subtable` closes with `margin-bottom: -12px` (it pulls itself
   into the row's padding): without compensating that, this block's own 10px of padding nets out to
   -2px and the button sits flush against the table's last row — reported. 12px cancels the pull so
   the padding becomes real air again. */
.horus-subtable-aside-button {
    margin: 12px 56px 0;
    padding: 10px 16px;
}

.horus-subtable-aside-button .mud-button-root {
    margin-left: -16px;
}

/* A row of form fields that must share the width EQUALLY — the three dates on PersonFormPanel
   (birth, hire, termination), one third each by explicit request.

   `flex: 1 1 0`, not MudBlazor's `flex-grow-1`: that one is `flex-grow: 1` alone, which distributes
   only the LEFTOVER space, so fields whose intrinsic widths differ (a label of "Egreso" against one
   of "Fecha de nacimiento") end up different sizes. A zero basis makes the shared-out space the
   whole width, which is what "a third each" means. Same distinction already documented for the
   Gantt toolbar, where the hypothetical size — not the shrunk one — is what decides the outcome.

   `min-width` keeps that from collapsing into three unusable slivers on a narrow viewport: below it
   the row wraps (the container keeps `flex-wrap`) and the fields go full width instead, which is
   the right answer on a phone even though it stops being thirds. */
.horus-field-row-equal > * {
    flex: 1 1 0;
    min-width: 180px;
}

/* ============================================================================
   Hours dashboard — /reports/statistics (Arquitectura.md §25)

   Built from divs and MD3 tokens rather than a chart library, the same call the Gantt (§17) and the
   PTO calendar (§20.11) already make: MudBlazor has no stacked column with a per-column reference
   line, and a palette handed to a library as hex strings cannot follow the light/dark tokens.

   THE RAMP. The three buckets are an ORDERED composition (billable → client non-billable →
   internal overhead), so they take an ordinal ramp — one hue, light to dark — and not three
   competing hues. The hue is `tertiary`, this palette's only blue: the brand red would read as
   alarm on a bar chart, and green/amber are reserved for status.

   Each step is that hue mixed over `surface` at 100% / 75% / 50%, which is why there is ONE
   definition here and no dark override — a custom property is substituted where it is USED, so both
   `tertiary` and `surface` are already the active theme's when the mix is evaluated. Verified with
   the data-viz validator in both themes (ordinal checks: monotone lightness, adjacent ΔL ≥ 0.06,
   single hue, light end clearing the surface):
   (measured in the browser in both themes, not computed by hand — the values below are what the
   mixes actually render as):
     light, over #FFFFFF: #355DA4 / #6886BB / #9AAED2 — light end 2.24:1
     dark,  over #282A2D: #ADC6FF / #8C9FCB / #6B7896 — light end 3.26:1
   The lightest step is the one that constrains this: `tertiary-container` (#D8E2FF) was the obvious
   first choice for it and FAILED at 1.29:1 against white — a mark nobody can see.

   The mixes are over `surface` because that is what is actually behind them: the plot area sits on a
   card, not on the tinted page. If a chart ever moves onto the canvas, these three have to be
   re-measured, not assumed — the same warning the timesheet grid carries about its weekend tint.
   ============================================================================ */
.horus-hs {
    --horus-hs-billable: var(--md-sys-color-tertiary);
    --horus-hs-clientnonbillable: color-mix(in srgb, var(--md-sys-color-tertiary) 75%, var(--md-sys-color-surface));
    --horus-hs-internal: color-mix(in srgb, var(--md-sys-color-tertiary) 50%, var(--md-sys-color-surface));

    /* The meter's unfilled track: a lighter step of the SAME ramp, so state reads across the whole
       bar instead of the fill floating on a neutral gray. */
    --horus-hs-track: color-mix(in srgb, var(--md-sys-color-tertiary) 16%, var(--md-sys-color-surface));

    /* ABSENCES are not a step of the ramp, and that is a measured conclusion rather than a
       preference. A 3-step single-hue ordinal ramp already spends the whole lightness band, so a
       4th fill lands on top of one of them: the validator put a neutral against the lightest step
       at ΔE 6.2 in light and 7.4 in dark under NORMAL vision, well under the hard floor of 15
       ("hard to tell apart even with full colour vision"). Darkening the neutral only moved the
       collision to another step (12.0 against the darkest blue).

       So absence stopped being a segment of the bar. It is the reason capacity SHRINKS, not a kind
       of work, and it now appears as its own stat tile, its own table column, and the capacity
       marker sitting lower. This neutral is only ever a swatch beside its own label — never a fill
       touching a ramp fill inside one mark — which is what makes a gray legitimate here. */
    --horus-hs-absence: color-mix(in srgb, var(--md-sys-color-on-surface) 45%, var(--md-sys-color-surface));

    /* White doing the separating: 2px of surface between touching fills, one consistent width
       across every stack — never a border drawn around a mark. */
    --horus-hs-gap: 2px;

    --horus-hs-plot-height: 220px;
    --horus-hs-col-gap: 4px;
}

/* Held at reduced opacity while refetching instead of being swapped for a skeleton: no layout jump,
   and the numbers being replaced stay readable until the new ones arrive.

   Both were `.horus-hs-*` while the hours dashboard was the only report that refetched in place;
   promoted when the reconciliation report did the same thing. Neither says anything about a
   dashboard — one is "this is stale", the other is the gap above a loading bar. */
.horus-refetching {
    opacity: 0.55;
    transition: opacity 0.15s ease;
}

.horus-loading {
    margin-top: 8px;
}

/* ---- KPI row — SHARED (hours dashboard, monthly settlement) ----
   Named `horus-kpi-*` and not `horus-hs-*` since the settlement became its second consumer:
   the same move `.horus-filter-group` made when it stopped being `.horus-status-filter`. The
   hero tile beside them stays private to the dashboard — one big figure with a meter is that
   screen's own shape, not a tile. */
.horus-kpi-row {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
}

.horus-hs-hero {
    flex: 1 1 300px;
}

/* The three pieces stay stacked at the top — label, value, help — and the slack from the tiles
   being stretched to the hero's height sits at the bottom. Pinning the help line to the bottom of
   the card instead was tried and reverted: explicit preference for the help line reading as part of
   the value it qualifies rather than as a footnote. */
.horus-kpi-tile {
    flex: 1 1 180px;
}

.horus-kpi-tile-head {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* The small small-caps label of a piece of data. `.horus-field-label` is added here instead of copying
   the three declarations: it is the same treatment the KPI tile and the filter bar already used,
   and a third copy is how the three start to diverge. No margins on purpose —
   `.horus-filter-label` brings them because it lies flat in a toolbar; this one is stacked. */
.horus-kpi-tile-label,
.horus-field-label {
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--horus-text-secondary);
}

.horus-field-label {
    font-size: 11px;
    font-weight: 600;
    line-height: 1.4;
}

/* The one hero figure of the view. Same sans as everything else (never a display face), and
   PROPORTIONAL figures — `tabular-nums` gives every digit the width of a `0`, which makes a number
   like 12% look loose at this size. Tabular is for columns, further down. */
.horus-hs-hero-value {
    font-size: 3rem;
    font-weight: 700;
    line-height: 1.1;
    margin: 4px 0 12px;
    color: var(--md-sys-color-on-surface);
}

/* 12px above the label, not 4 (explicit request): at 4px the caption and the figure read as one
   crowded line, and there was plenty of card to spend on it. */
.horus-kpi-tile-value {
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1.2;
    margin: 12px 0 2px;
    color: var(--md-sys-color-on-surface);
}

.horus-hs-meter {
    height: 8px;
    border-radius: 4px;
    background: var(--horus-hs-track);
    overflow: hidden;
    margin-bottom: 8px;
}

.horus-hs-meter-fill {
    height: 100%;
    border-radius: 4px;
    background: var(--horus-hs-billable);
}

/* ---- Legend and swatches ---- */

/* Identity never rests on colour alone: the swatch sits BESIDE a name in a text token. Text never
   wears the data colour — a light step of this ramp is illegible as type on the surface. */
.horus-hs-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    font-size: 0.75rem;
    color: var(--horus-text-secondary);
}

.horus-hs-legenditem {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.horus-hs-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 2px;
    flex: none;
}

.horus-hs-swatch-billable {
    background: var(--horus-hs-billable);
}

.horus-hs-swatch-clientnonbillable {
    background: var(--horus-hs-clientnonbillable);
}

.horus-hs-swatch-internal {
    background: var(--horus-hs-internal);
}

.horus-hs-swatch-absence {
    background: var(--horus-hs-absence);
}

/* The capacity marker's key: a line, because that is the mark it stands for. */
.horus-hs-legendline {
    display: inline-block;
    width: 14px;
    height: 2px;
    border-radius: 1px;
    background: var(--md-sys-color-on-surface);
    flex: none;
}

.horus-hs-cardhead {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 16px;
}

.horus-hs-cardhead-tools {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 16px;
}

/* ---- The monthly mix ---- */
/* `margin-top` on top of the body's own 16px of padding, because the padding is not what the eye
   measures here: the topmost axis tick is centred ON the plot's top edge (`translateY(50%)`), so it
   sticks half its height ABOVE the chart box and the real gap to the card header was 8.1px — half
   of what the padding suggests. 12px here puts it at ~20px. */
.horus-hs-chart {
    display: flex;
    gap: 8px;
    margin-top: 12px;
}

.horus-hs-gutter {
    position: relative;
    flex: none;
    width: 46px;
    height: var(--horus-hs-plot-height);
}

/* Axis ticks carry every value the chart doesn't label directly, which is what lets the marks stay
   clean. Tabular figures here: they DO align vertically. */
.horus-hs-tick {
    position: absolute;
    right: 0;
    transform: translateY(50%);
    font-size: 0.6875rem;
    font-variant-numeric: tabular-nums;
    color: var(--horus-text-secondary);
}

.horus-hs-chartbody {
    flex: 1 1 0;
    min-width: 0;
}

.horus-hs-plot {
    position: relative;
    height: var(--horus-hs-plot-height);
}

/* Hairline, solid, one step off the surface. Never dashed — dashing reads as "projection" or
   "threshold" when it is only a grid. */
.horus-hs-gridline {
    position: absolute;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--md-sys-color-outline-variant);
}

.horus-hs-cols {
    position: absolute;
    inset: 0;
    display: flex;
    gap: var(--horus-hs-col-gap);
    z-index: 1;
}

.horus-hs-col {
    position: relative;
    flex: 1 1 0;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    min-width: 0;
}

/* `column-reverse` so the segments stack UP from the baseline, which is also what makes each one's
   percentage height resolve against the plot. Capped at 24px: a bar never fills its slot, the
   leftover is air. */
.horus-hs-bar {
    display: flex;
    flex-direction: column-reverse;
    justify-content: flex-start;
    width: 100%;
    max-width: 24px;
    height: 100%;
}

/* No `min-height` floor and no flex `gap`: BOTH of those distort the bar, and the distortion was
   measured in the live chart, not theorised. `gap` ADDS space, so three segments drew the bar 4px
   taller than its value — 36 hours at this scale, the same order as the very difference the
   capacity marker exists to show (October read as sitting ABOVE its marker when its hours are 32h
   BELOW it). Subtracting the gap from each segment with `calc()` fixed the normal case and left
   2.6px, because a 0.6px sliver cannot pay 2px back and its `min-height` floor made it worse.

   So the gap is PAINT, not box: every segment after the first fills its first 2px with the surface
   colour (below). Its box keeps exactly the height its value earns, the total is exact by
   construction, and a sliver thinner than the gap simply renders as separation — the honest
   outcome, since its value is in the tooltip and the table either way. */
.horus-hs-seg {
    flex: 0 0 auto;
    min-height: 0;
    background: var(--horus-hs-fill);
}

.horus-hs-seg-billable {
    --horus-hs-fill: var(--horus-hs-billable);
}

.horus-hs-seg-clientnonbillable {
    --horus-hs-fill: var(--horus-hs-clientnonbillable);
}

.horus-hs-seg-internal {
    --horus-hs-fill: var(--horus-hs-internal);
}

/* The 2px of surface that separates touching fills, painted inside the segment that follows —
   one consistent width across every stack, and never a stroke drawn around a mark. */
.horus-hs-bar .horus-hs-seg-inset {
    background: linear-gradient(
        to top,
        var(--md-sys-color-surface) 0 var(--horus-hs-gap),
        var(--horus-hs-fill) var(--horus-hs-gap));
}

.horus-hs-rankbar .horus-hs-seg-inset {
    background: linear-gradient(
        to right,
        var(--md-sys-color-surface) 0 var(--horus-hs-gap),
        var(--horus-hs-fill) var(--horus-hs-gap));
}

/* 4px rounded data-end, square at the baseline — and it lands on whichever segment is actually
   last, because the empty buckets are never rendered. The horizontal ranking bars want the same
   radius on their own end, hence two rules for one class. */
.horus-hs-bar .horus-hs-seg-end {
    border-radius: 4px 4px 0 0;
}

.horus-hs-rankbar .horus-hs-seg-end {
    border-radius: 0 4px 4px 0;
}

/* Capacity, per column. Wider than the bar so it reads as a target laid over it, and carrying a
   1px surface halo above and below so it stays legible where it crosses the darkest fill — the
   surface ring the marks spec asks for on overlapping marks. `pointer-events: none` keeps the
   column's own tooltip reachable through it. */
.horus-hs-capacity {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: min(100%, 32px);
    height: 2px;
    border-radius: 1px;
    background: var(--md-sys-color-on-surface);
    box-shadow: 0 -1px 0 0 var(--md-sys-color-surface), 0 1px 0 0 var(--md-sys-color-surface);
    pointer-events: none;
    z-index: 2;
}

/* Same flex geometry and the same gap as the columns, which is what keeps a label under its bar.
   In its own row rather than inside the plot: a fixed-height plot that had to hold the axis band
   too would end up with a tiny nested scrollbar. */
.horus-hs-labels {
    display: flex;
    gap: var(--horus-hs-col-gap);
    margin-top: 8px;
}

.horus-hs-label {
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
    font-size: 0.6875rem;
    color: var(--horus-text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* A tooltip that carries several lines instead of a sentence — one fact per line, left aligned
   against MudBlazor's centred default. Was `.horus-hs-tip` while /reports/statistics was its only
   consumer; renamed rather than copied when the settlement's aguinaldo offer became the second one
   (the rule: promote the class, don't duplicate it). */
.horus-tooltip-lines {
    display: flex;
    flex-direction: column;
    gap: 2px;
    text-align: left;
}

.horus-hs-partial {
    margin-left: 6px;
    font-size: 0.6875rem;
    color: var(--horus-text-secondary);
}

/* ---- Rankings ----
   ONE grid for the whole list, with the rows made transparent to it (`display: contents`), so the
   three columns are sized ACROSS every row.

   It was a grid per row, and that misaligns the bars — reported, then measured: the third column is
   `auto`, so it took the width of its OWN row's value ("3.492 h 30 %" against "18 h 0 %", 82.8px
   against 55.4px), and whatever it left over got redistributed by the two `fr` columns. The bars
   started at anywhere between 458px and 468px. Ten pixels of drift with no cause a reader could
   see.

   `auto` on the value column is still the right sizing — it just has to be resolved once, against
   the widest value in the list, instead of nine times. Which also means a five-figure total or a
   longer client name widens the column for everyone at once, with no hardcoded width to maintain. */
.horus-hs-rank {
    display: grid;
    /* The name column is as wide as the longest NAME, capped at a third — not a fraction of the
       card. As `1.2fr` it scaled with the width, so on a full-width card the names sat ~380px wide
       and a short name was followed by a gap of 250px before its bar (reported on the home
       dashboard). `fit-content` takes the content's width and wraps only past the cap. */
    grid-template-columns: fit-content(33%) minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 16px;
    /* Row spacing that used to be the row's own vertical padding — with `display: contents` the row
       box is gone, so there is nothing left to pad. */
    row-gap: 10px;

    /* EVERY row exactly this tall (explicit request), instead of each one taking the height of its
       tallest cell. Without it the rows carrying the "Interno" chip stood 32px against the other
       rows' 20px — measured — because a MudChip is 24px plus its own 4px of vertical margin. A list
       whose line spacing changes on the rows that happen to have a badge reads as a rendering
       glitch, and it also breaks the eye's ability to scan the bars as a column. */
    grid-auto-rows: 24px;
}

.horus-hs-rankrow {
    display: contents;
}

.horus-hs-rankname {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
}

.horus-hs-rankname-main {
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The owning client on a project row: a project called "Mantenimiento" says nothing alone, and two
   clients can legitimately have one. */
.horus-hs-rankname-sub {
    font-size: 0.75rem;
    color: var(--horus-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.horus-hs-rankbarwrap {
    width: 100%;
    min-width: 0;
}

.horus-hs-rankbar {
    display: flex;
    align-items: stretch;
    height: 14px;
}

.horus-hs-rankvalue {
    display: flex;
    align-items: baseline;
    justify-content: flex-end;
    gap: 8px;
    white-space: nowrap;
}

.horus-hs-rankvalue-main {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--md-sys-color-on-surface);
}

.horus-hs-rankvalue-sub {
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--horus-text-secondary);
}

/* Sized to sit INSIDE the fixed row height rather than to set it: no vertical margin (MudBlazor
   ships 4px on every side) and short enough to clear 24px with room to spare. Three classes so it
   beats `.mud-chip`'s own single-class rule without reaching for `!important`. */
.horus-hs-rankname .horus-hs-internal-chip.mud-chip {
    flex: none;
    height: 18px;
    margin: 0 0 0 2px;
}

/* ---- Table twin of the chart ----
   It is a MudDataGrid with `Items`, the same component and props as every CRUD listing, so it needs
   no styling to look like them — this is only the numeric alignment, which no listing had needed
   before. Three classes deep because MudDataGrid's own cell rules have to be beaten (the same
   specificity floor the import screen's column padding documents).

   (It used to be a MudSimpleTable styled to approximate the look. That was reported as not matching
   the rest of the app's tables, and it wasn't: the fix was the component, not more CSS.) */

/* ---- Monthly settlement (/settlements) --------------------------------------------------
   Layout only, and only what the shared pieces don't already cover: the KPI strip above the
   partner cards is `.horus-kpi-*` (promoted out of the hours dashboard), and the filter strip
   is the shared `.horus-toolbar-row`.

   No colour system of its own — the cards sit on `surface` like every other card in the app,
   and the ONE colour that appears is `success` on a partner who is owed money. That restraint
   is the same rule the timesheet grids and the Gantt bars already follow: hierarchy is carried
   by weight and size, not by the brand tone. Painting the partner who pays in the brand red
   (which is what the spreadsheet does) would make three of four cards read as alarms in a
   month where nothing is wrong. */
.horus-settlement-partners {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 16px;
}

.horus-settlement-partner {
    padding: 16px;
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--mud-default-borderradius);
    background-color: var(--md-sys-color-surface);
}

.horus-settlement-partner-name {
    font-weight: 700;
    color: var(--md-sys-color-on-surface);
}

/* The two derived figures under the name, as a label/value pair per line. A grid rather than a
   flex row with a spacer, so the amounts line up down the card whatever the labels measure. */
.horus-settlement-partner-rows {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 2px 12px;
    margin-top: 10px;
    font-size: 0.8125rem;
    color: var(--horus-text-secondary);
}

.horus-settlement-partner-rows > :nth-child(even) {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.horus-settlement-partner-balance {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--md-sys-color-outline-variant);
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* Owed TO this partner — the only coloured figure on the screen, because it is the only one
   that is exceptional. Whoever pays stays in the ordinary text colour. */
.horus-settlement-owed {
    color: var(--mud-palette-success);
}

.horus-settlement-transfer {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0;
}

.horus-settlement-transfer + .horus-settlement-transfer {
    border-top: 1px solid var(--md-sys-color-outline-variant);
}

.horus-settlement-transfer-amount {
    margin-left: auto;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* Amount columns of the two cost grids. Tabular figures so the decimal points stack, which is
   what makes a column of money scannable at all. */
.horus-settlement-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The aguinaldo offer: the sentence and the amounts on the left, the one button that accepts them
   on the right. Wraps instead of squeezing the button — the sentence is two lines of prose and it
   is what has to keep its width. */
.horus-aguinaldo-offer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

/* Takes the room and leaves the button its own column; below ~420px of text it wraps and the
   button lands underneath instead of being squeezed to two words per line. */
.horus-aguinaldo-offer-body {
    flex: 1 1 420px;
    min-width: 0;
}

.horus-aguinaldo-people {
    display: flex;
    flex-wrap: wrap;
    column-gap: 20px;
    row-gap: 4px;
    margin-top: 8px;
}

/* Name and figure as one unit, so a wrap never leaves an amount under somebody else's name.
   tabular-nums because these are four amounts read against each other. */
.horus-aguinaldo-person {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* What a closed month's sources would now say: the figure, what it was closed with, what it would
   be today. A grid and not a table because it lives inside an alert, where a MudTable's chrome
   would be a second surface on top of the first; the two figures right-aligned and tabular so they
   read against each other down the column.

   The label column is `auto`, never `minmax(0, …)`: with a zero floor it collapsed on a phone and
   the names ran under the figures beside them. `auto` stops at the longest word and wraps. */
.horus-settlement-drift {
    display: grid;
    grid-template-columns: auto max-content max-content;
    justify-content: start;
    column-gap: 24px;
    row-gap: 2px;
    margin-top: 8px;
    font-size: 0.875rem;
}

/* On a phone the two figures alone take the alert's width, so the label gets a line of its own
   above them and the empty header cell over it goes away. */
@media (max-width: 599.98px) {
    .horus-settlement-drift {
        grid-template-columns: max-content max-content;
    }

    .horus-settlement-drift-what {
        grid-column: 1 / -1;
        margin-top: 6px;
    }

    .horus-settlement-drift-head:first-child {
        display: none;
    }
}

.horus-settlement-drift-head {
    font-weight: 500;
}

.horus-settlement-drift-value {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Three dots that say "still coming", where a figure is about to go. Used by the market-rate cards
   while the three external sources answer (a couple of seconds).

   Two things this needs to get right, and the first one was wrong at first (reported: the dots
   looked odd):

   · VERTICAL ALIGNMENT. An inline-flex box collapses to the height of its content — 4px here — and
     then sits on the TEXT BASELINE, so the dots hung at the bottom of the line instead of where the
     digits would be. Giving it the height of a line and `vertical-align: middle` puts them through
     the middle of the figure they stand in for. Same trap the icon-beside-text rule describes.
   · ROOM. Sized in em with a min-width close to a quote's width, so the card does not re-lay out
     when the number lands, and right-aligned to end where the digits end.

   The stagger is what reads as motion: one animation, three delays. `prefers-reduced-motion` leaves
   the dots visible and still — the information is "a value is pending", and that survives without
   movement. */
.horus-loading-dots {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    /* Dot and gap are the SAME size, so the rhythm is even. Sized in px and not em: at em the dot
       came out 4.5px against a 3.1px gap — a ratio nobody chose, and it read as uneven (reported).
       The BOX still measures in em, which is what keeps the card from re-laying out. */
    gap: 4px;
    height: 1.2em;
    min-width: 2.2em;
    vertical-align: middle;
}

.horus-loading-dots i {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background-color: currentColor;
    opacity: 0.35;
    animation: horus-loading-dots 1.05s ease-in-out infinite;
}

.horus-loading-dots i:nth-child(2) { animation-delay: 0.15s; }
.horus-loading-dots i:nth-child(3) { animation-delay: 0.3s; }

@keyframes horus-loading-dots {
    0%, 70%, 100% { opacity: 0.35; }
    35% { opacity: 0.9; }
}

@media (prefers-reduced-motion: reduce) {
    .horus-loading-dots i {
        animation: none;
        opacity: 0.55;
    }
}

/* A salary correction: the amount and the currency it was paid in, as one control. Right-aligned
   with the column, and the currency sized to its three letters so the amount keeps the room. */
.horus-settlement-salary-amount {
    display: flex;
    gap: 6px;
    justify-content: flex-end;
    align-items: center;
}

/* Only ever three letters (USD or the month's local code), so it takes the room those need and
   nothing more — the amount beside it is what deserves the width. The input's own horizontal
   padding is trimmed too: MudBlazor's Outlined padding is sized for a sentence, and around a
   three-letter code it is more inset than content. */
.horus-settlement-salary-currency {
    flex: 0 0 auto;
    width: 84px;
}

.horus-settlement-salary-currency .mud-input-slot {
    padding-left: 8px;
}

/* What the correction comes to in USD, under the field, and ONLY when it was entered in another
   currency. A note about the figure above it, not a second figure: quieter, and no box. */
.horus-settlement-salary-converted {
    margin-top: 4px;
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
}

/* A salary line taken out of the month. Struck through and dimmed rather than hidden — a line
   nobody can put back is worse than one that is visibly out. */
.horus-settlement-excluded {
    text-decoration: line-through;
    color: var(--horus-disabled-content);
}

/* The billable / non-billable column pairs, told apart by a background tone (explicit request).
   Neutral steps of the canvas, never a hue: which side of the line an hour falls on is a
   CLASSIFICATION, not good news and bad news — the same restraint the rest of this screen keeps.

   Only the non-billable pair is tinted. One tone is enough to group both pairs — the billable
   columns read as a pair by being the ones left on the plain surface — and it is half the noise of
   giving each side its own colour. Measured live: rgb(32,34,36) against surface's rgb(40,42,45),
   which reads as a column without competing with the row borders.

   THE HEADER IS DELIBERATELY NOT TINTED. The whole header band is already one flat canvas tone
   (`.horus-section .mud-table-container thead th.mud-table-cell` above), so tinting two of its
   cells would split that band rather than extend the column — and the band is a decision the
   design system already took. The first version of this rule did try, with a selector that lost on
   specificity to that very rule (3 classes + 1 type against 3 + 2), so it never applied and the
   header looked right by accident.

   Three selectors deep because MudDataGrid paints its own cell backgrounds; a single class loses. */
.horus-section .mud-table-container td.mud-table-cell.horus-settlement-nonbill {
    background-color: color-mix(in srgb, var(--md-sys-color-background) 60%, var(--md-sys-color-surface));
}

/* The billable pair in bold (explicit request): it is the half of the table that turns into an
   invoice, so it carries the weight while the non-billable pair keeps its background tone. Only
   the `td` — the header row is already 600 for every column. */
.horus-section .mud-table-container td.mud-table-cell.horus-settlement-bill {
    font-weight: 600;
}

/* The total column: bold, ordinary text colour, and a REDDISH background tone (explicit request,
   after red TEXT was tried and rejected). Same 6% of `primary` the row hover uses, so the screen
   holds one reddish tint and not two.

   Mixed over `surface` rather than over `transparent` like the hover is: a translucent fill
   composes against whatever sits behind it, and this cell has to read the same whether or not the
   row is being hovered. Note this rule outweighs the hover (4 classes to 3), so the total keeps
   its tone instead of doubling up under the cursor — same as the non-billable pair. */
.horus-section .mud-table-container td.mud-table-cell.horus-settlement-total,
.horus-subtable td.horus-settlement-total {
    font-weight: 700;
    background-color: color-mix(in srgb, var(--md-sys-color-primary) 6%, var(--md-sys-color-surface));
}

/* ============================================================================================
   /invoices — the book of issued comprobantes
   ============================================================================================ */

/* THE FILE OF THE COMPROBANTE. Same skeleton as `.horus-reconciliation-detail`, and for the
   same reason: MudDataGrid's expanded cell brings 12/16 of native padding and the content
   needs its own.

   And it cancels the margin of `.horus-subtable`, which is `-12px 56px` — computed for a subtable
   that is the ONLY child of the cell. Here there are titles and a panel above, so that inset left the
   table 56px to the RIGHT of its own title and the negative margin pulled it 4px up over it
   (measured: title at x=281, table at x=337, and their rectangles overlapping). It is exactly what
   had already happened to the reconciliation screen, documented next to that class. */
.horus-invoice-detail {
    padding: 12px 16px 16px;
    display: flex;
    flex-direction: column;
}

.horus-invoice-detail .horus-subtable {
    margin: 0;
}

/* The facts off the paper. A bordered tile with no shadow: it is a tile INSIDE a card, and a second
   shadow in there reads as a card that came loose (Lineamientos-UI.md §5, reference
   `.horus-rate-card`). `auto-fit` with a floor instead of a fixed number of columns, so they wrap in
   a narrow window instead of squeezing. */
.horus-invoice-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 12px 24px;
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: 8px;
    padding: 12px 16px;
}

/* Label above, value below — the hierarchy the first version lacked: run together inline and
   all the same size, six facts read as one grey paragraph. */
.horus-invoice-fact {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.horus-invoice-fact-value {
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}

/* Air above each section title, and none above the first: the facts panel already
   ends with its own border, and adding both leaves a gap that looks like a mistake. */
.horus-invoice-detail-title {
    margin: 20px 0 8px;
}

/* The line-number column: it is a one-digit ordinal, not a piece of data that needs room. */
.horus-invoice-linenum {
    width: 1%;
    white-space: nowrap;
    color: var(--horus-text-secondary);
}

.horus-invoice-line-text {
    font-weight: 500;
}

/* The fiscal legend ("Servicios exonerados literal S…") is the same sentence on almost every
   line: it is context, not the line, so it drops a step in size as well as in colour. */
.horus-invoice-line-legend {
    font-size: .75rem;
    line-height: 1.35;
    margin-top: 2px;
}

/* One imputation of a line. Several can stack in the cell (a line that pays for two months),
   so the spacing goes between them and not around the group. */
.horus-invoice-allocation + .horus-invoice-allocation {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--md-sys-color-outline-variant);
}

.horus-invoice-allocation-period {
    color: var(--horus-text-secondary);
    font-size: .75rem;
}

/* What the payer is called on the PAPER, below the catalog's client. Muted and small: it is an
   alternative spelling of the same name, not a second piece of data. It does not use `.horus-ts-task-client` — that is
   the PARENT line of a task path and goes in the brand red, so it put the paper's spelling in
   brand colour below the name it qualifies. */
.horus-invoice-payer {
    font-size: .75rem;
    line-height: 1.35;
    color: var(--horus-text-secondary);
}

.horus-invoice-credit-links {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* Label, date, amount and maybe a chip on one line. `align-items: center` because the chip is
   taller than the text next to it, and a baseline alignment would leave it low. */
.horus-invoice-credit-link {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

/* The amounts of a credit note, shown as the subtraction they are. In grey and not in red: it is
   not an error or a warning, it is the other direction — and red is already taken by the
   destructive and by a document DGI rejected, which can land on the same row. */
.horus-invoice-negative {
    color: var(--horus-text-secondary);
}

/* ---- Entering a comprobante by hand (the /invoices panel) ---- */

/* The form's lines table. `.horus-subtable` does not work here: its `margin: -12px 56px` is
   computed for a subtable that is the only child of an expanded cell, and this lives inside a
   MudForm with its own padding. All it needs is not to overflow and for the cells to make room for a
   Dense input. */
.horus-invoice-form-lines {
    border-radius: 8px;
}

/* Its own scrollport, like `.horus-report-tablewrap`: in a narrow window the table takes its
   horizontal scroll with it and does not push the form sideways. */
.horus-invoice-form-lines .mud-table-container {
    overflow-x: auto;
}

.horus-invoice-form-lines td {
    padding: 6px 8px;
    vertical-align: top;
}

/* THE TEXT TAKES ALL THE SPARE WIDTH. Without this the table split by eye and the most
   important field of the line —what it bills— ended up at a quarter of the width of the three
   figure columns, showing "Qué fact" (measured on screen). The number ones take what they need and nothing more:
   `width: 1%` on a table cell is the idiom for "the least you need". */
.horus-invoice-form-lines th:first-child,
.horus-invoice-form-lines td:first-child {
    width: 100%;
    /* And a floor, because `width: 100%` in an auto table is a preference that loses against the
       min-content of the other cells: at a 1024 viewport the three figure ones kept 486px
       of 684 and the text collapsed to 94px (measured). With the floor, what gives is the table, which
       scrolls inside its own container instead of crushing the most important field. */
    min-width: 220px;
}

.horus-invoice-form-lines th:not(:first-child),
.horus-invoice-form-lines td:not(:first-child) {
    width: 1%;
    white-space: nowrap;
}

/* The three numeric ones measure the same and align right with their column. Fixed width and not
   automatic: if they followed the content, the table would shift with every digit somebody types. */
.horus-invoice-form-num {
    width: 130px;
    margin-left: auto;
}

.horus-invoice-form-num input {
    text-align: right;
}

/* The totals, derived from the lines. On the right, below the table and the columns whose
   figures they add up. */
.horus-invoice-form-totals {
    display: flex;
    justify-content: flex-end;
    gap: 32px;
    padding: 8px 8px 0;
}

.horus-invoice-form-totals > div {
    display: flex;
    flex-direction: column;
    gap: 2px;
    align-items: flex-end;
}

.horus-invoice-form-grandtotal {
    font-weight: 600;
}

/* ---- The monthly load of comprobantes (/invoices/import) ---- */

/* A row that is ALREADY loaded: it is shown (with the switch) but cannot be imported, so it
   reads inert. Muted and with no colour tint: it is not an error or a warning — it is the expected
   state of almost the whole file, because the billing provider's report is cumulative. None of the
   mechanism of `.horus-import-duprow`, which reserves room for an absolute strip and is not this. */
.horus-section .mud-table-container tbody tr.horus-import-loadedrow > td.mud-table-cell {
    color: var(--horus-text-secondary);
}

/* ---- The cash book's two directions (/cash) ----
   Money in and money out, coded three ways at once: an arrow in the column header as a legend, and
   a sign and a colour on every figure. A ledger is SCANNED rather than read cell by cell, so the
   redundancy is the point — the question is "where did money leave" over forty rows.

   `success`/`error` from the MD3 palette and NOT a hand-picked green and red, so both survive the
   light/dark switch with the contrast the palette already guarantees. `font-weight` because a
   coloured figure at regular weight loses to the neutral text around it at body size, and
   `font-variant-numeric: tabular-nums` so a column of them lines up on the decimal — the amounts
   carry three decimals in a few rows (13.025,025) and proportional digits make those wander.

   This is DATA red, not the destructive red that a row's delete button owns: that rule is about
   action icons competing inside one row, and this screen has no row actions. */
.horus-cash-in,
.horus-cash-out {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.horus-cash-in {
    color: var(--mud-palette-success);
}

.horus-cash-out {
    color: var(--mud-palette-error);
}

/* The running balance: same tabular figures so it lines up on the decimal, but NEUTRAL and at
   regular weight. A balance has no direction — the green and the red belong to the two movement
   columns beside it, and colouring a balance would invite reading it as an inflow. It is also the
   quietest thing in the row on purpose: it is there to be checked against a statement, not to
   compete with what actually moved. */
.horus-cash-balance {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* ---- The cash book's balance card (/cash) ----
   The sum written the way anyone writes one by hand: the three terms, a rule, the total. The rule is
   what makes it read as an arithmetic claim rather than four unrelated figures, and the total is the
   only thing in the card at display size — it is the figure the reader came for. */
.horus-cash-balance-card {
    flex: 1 1 260px;
}

.horus-cash-sum {
    width: 100%;
    margin-top: 10px;
    border-collapse: collapse;
}

.horus-cash-sum th {
    padding: 2px 12px 2px 0;
    font-weight: 400;
    text-align: left;
    white-space: nowrap;
    color: var(--mud-palette-text-secondary);
}

/* THE THREE TERMS ARE BOLD, not regular. Gilroy has no Regular — Medium, Bold and ExtraBold — so
   this is one step and there is no half step to take. The opening figure takes the SECONDARY tone
   instead of the surface text: bold and black it competed with the total, and it is the term nobody
   is looking for. The movements keep their green and red. */
.horus-cash-sum td {
    padding: 2px 0;
    text-align: right;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.horus-cash-sum-muted {
    color: var(--mud-palette-text-secondary);
}

/* The legend sits where the total's label goes, so it HAS to wrap. Written as `th.` and not as the
   bare class on purpose: `.horus-cash-sum th` sets `nowrap` and beats a lone class on specificity,
   so the first version never wrapped — the sentence claimed its full width, the table laid out 506px
   inside a 343px card and every FIGURE ended up past the card's clipped edge. The card looked like
   it had labels and no numbers.

   It also drops to the size the other cards' help lines use, because it is help and not a term. */
.horus-cash-sum th.horus-cash-sum-legend {
    white-space: normal;
    font-size: 0.875rem;
    line-height: 1.35;
    padding-right: 16px;
}

/* The last term gives the rule room above it, so the line sits BETWEEN the sum and its total instead
   of hugging the figure above. */
.horus-cash-sum tr:nth-last-child(2) th,
.horus-cash-sum tr:nth-last-child(2) td {
    padding-bottom: 12px;
}

/* The rule goes on the TOTAL's cells and not on the row above, so it spans exactly the two columns
   the sum occupies — a border on a <tr> is not painted by every engine with border-collapse. */
.horus-cash-sum-total th,
.horus-cash-sum-total td {
    padding-top: 12px;
    border-top: 1px solid var(--mud-palette-divider);
}

/* Sized down from 20px so it sits with a 12px caption instead of riding above it, and painted in the
   SURFACE TEXT tone. MudBlazor's `Color.Default` is its action grey (measured rgb(106,107,109)),
   which is dimmer than the text beside it — asked for the dark default the rest of the app uses. */
.horus-cash-balance-icon {
    font-size: 1.125rem;
    color: var(--md-sys-color-on-surface);
}

.horus-cash-sum-total td {
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1.2;
    color: var(--md-sys-color-on-surface);
}

/* One currency at each end instead of one per line, which is what makes these two cards short. They
   wrap to a line each when the card cannot hold both, so nothing is ever clipped — at that point the
   layout is back to what it was. The margins come off the values and onto the wrapper, or the flex
   row inherits the value's 12px top and sits crooked. */
.horus-cash-totals {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    column-gap: 20px;
    margin: 12px 0 2px;
}

.horus-cash-totals > .horus-kpi-tile-value {
    margin: 0;
}

/* The movements of a cash row being typed. Full width of the form, and the amount column as narrow
   as its content so the three selects get the room: a movement is an account, a currency and a
   direction, and those are what need reading. */
.horus-cash-movements {
    width: 100%;
    border: none;
}

.horus-cash-movements th,
.horus-cash-movements td {
    padding: 4px 8px 4px 0;
}

.horus-cash-movements td:last-child,
.horus-cash-movements th:last-child {
    width: 1%;
    padding-right: 0;
}

/* The concept column takes the slack. It is the widest thing a movement says -- "Gasto -
   Marketing/Comercial" is 28 characters against "Banco" and "USD" -- and the only one whose values
   are not a short closed list, so giving it a share of a table that is otherwise four narrow
   controls is what stops it truncating. 34% and not `auto`: with auto the autocomplete's intrinsic
   width won and the three selects next to it collapsed. */
.horus-cash-movements .horus-cash-movement-concept {
    width: 30%;
}

/* The counterparty, for the same reason as the concept: it is an autocomplete over long names next to
   short-list selects, so it needs a share of the slack or its neighbours collapse. Less
   than the concept because a proper name is shorter than `Gasto - Marketing/Comercial`. */
.horus-cash-movements .horus-cash-movement-person {
    width: 22%;
}

/* Row actions of the cash book: as narrow as the icon, like the other three icon columns here. */
.horus-cash-actions-col {
    width: 1%;
    white-space: nowrap;
}

/* ---- The reorder handle of a cash row ----
   As narrow as the glyph, like the other two icon columns, and `grab`/`grabbing` because that is the
   only thing on the screen that says a row can be MOVED. */
.horus-cash-drag-col {
    width: 1%;
    white-space: nowrap;
    padding-right: 4px !important;
}

.horus-cash-drag {
    display: flex;
    align-items: center;
    cursor: grab;
    color: var(--mud-palette-text-disabled);
}

.horus-cash-drag:active {
    cursor: grabbing;
}

/* The handle is quiet until the row is hovered: it is an affordance for when somebody wants it, not
   a mark on every row of a book that is mostly read. */
.horus-section tbody tr:hover .horus-cash-drag {
    color: var(--mud-palette-text-secondary);
}

/* WHERE IT WOULD LAND. A line and not a filled row: the drop puts the dragged row in this one's
   place, and a border on the cell reads as a position while a tint reads as a selection. */
.horus-cash-drag-over {
    box-shadow: inset 0 2px 0 0 var(--mud-palette-primary);
}

/* The account column is a GLYPH, so it takes the glyph and nothing more. `width: 1%` alone does not
   do it — the cell's own padding is not content and the column never shrinks below it, which is the
   same 30px the arrow column was wasting before it was measured. */
.horus-cash-acct-col {
    width: 1%;
    white-space: nowrap;
    padding-right: 4px !important;
}

.horus-cash-acct-icon {
    color: var(--mud-palette-text-secondary);
}

/* ---- The direction arrows and the reference, in a cash row ----
   The arrow column is as narrow as its content: it is a glyph, and any slack it takes comes off the
   description, which is the column that actually needs room. */
.horus-cash-dir-col {
    width: 1%;
    white-space: nowrap;
    /* Measured: with the cell's default padding this column took 70px to draw two small glyphs, and
       every pixel it wastes comes off the description. `width: 1%` cannot help here -- padding is
       not content, so the column never shrinks below it. */
    padding-right: 4px !important;
}

.horus-cash-dir {
    display: flex;
    align-items: center;
}

/* The bank's own name for the transaction, under the description the person wrote. Quiet and small:
   it is what identifies a movement against a statement, and it is looked for rather than read. */
.horus-cash-reference {
    font-size: 0.75rem;
    line-height: 1.3;
    color: var(--mud-palette-text-secondary);
    font-variant-numeric: tabular-nums;
}

/* ---- The current account's figure columns (/current-account) ----
   `width: 1%` is the table idiom for "the least you need", and it is a LAYOUT question local to one
   table rather than a property of `.horus-cell-num`, which only ever meant alignment. Without it
   MudBlazor spread eight columns evenly and the table wanted 1.530px inside a 1.139px container, so
   ABIERTO -- the one figure the screen exists for -- sat off the right edge. The payer column keeps
   the slack, which is the same split the comprobante form uses for its line rows. */
.horus-account-grid td.horus-cell-num.horus-account-num,
.horus-account-grid th.horus-cell-num.horus-account-num {
    width: 1%;
    white-space: nowrap;
}

/* What a cash movement SETTLES: the comprobantes it collects, or the settlement lines it
   pays. A second row of the same movement, not a separate table. Indented so it reads as
   dependent on the line above and not as another section — the same device a subtable uses
   inside an expanded row, but without its negative margin, which is a grid-cell indent and
   here clips the panel (§40.6).

   It used to be called `horus-cash-links*` and was PROMOTED when the second consumer arrived: the name
   stated the action of a single case. Copying it would have left two identical blocks that drift apart on the
   first adjustment (Lineamientos-UI, rule 2). */
.horus-cash-links > td {
    padding: 0 0 8px 24px;
    border-bottom: none;
}

.horus-cash-links-body {
    border-left: 2px solid var(--mud-palette-lines-default);
    padding-left: 12px;
}

.horus-cash-links-label {
    display: block;
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
    margin-bottom: 4px;
}

/* The comprobante takes the slack and the amount stays as wide as its content: an `A 447 ·
   USD 7.075,00` is much longer than the figure applied to it. */
.horus-cash-links-line {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 4px;
}

.horus-cash-links-line > :first-child {
    flex: 1 1 auto;
    min-width: 0;
}

.horus-cash-links-amount {
    flex: 0 0 140px;
}

/* One option of the account selector: the name and, below it, the roles it carries. Two lines in a
   MudMenuItem, which by default lays its content out in a single row — without this the roles end up
   stuck to the name and the list stops reading at a glance. It is development scaffolding and goes away
   with DevUserSwitcher. */
.horus-acting-option {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
}

/* The two account menus of the app bar (AccountMenu, DevUserSwitcher). Their items are set at
   label-large, 14px — MD3's size for a menu item — instead of MudBlazor's body 16px, which read
   larger than the account name on the button that opens them (a button label, also 14px). */
.horus-account-menu .mud-menu-item-text,
.horus-menu-summary {
    font-size: 0.875rem;
}

/* The header of an account menu: who you are, above the actions. Padded like a MudMenuItem
   (8px 12px) so its text starts where the items' text would without an icon column. */
.horus-menu-summary {
    padding: 8px 12px;
}

/* ============================================================================
   Home dashboard — / (Arquitectura.md §72)

   Cards of figures on white, so the frame is the hours dashboard's card (section header band on
   surface) and the charts are its charts: the page carries `.horus-hs`, which brings the ordinal
   ramp, the stacked bars and the legend into scope instead of a second copy of them.
   ============================================================================ */

/* List cards fill the row and wrap rather than squeeze. `auto-fit`, so two cards share the row
   between them instead of leaving a third of it empty. Stretched to the row's height, with the body
   absorbing the slack, so every footer link sits on the same line. */
.horus-dashboard-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: 16px;
    align-items: stretch;
}

/* Cards with a period: full width, one under the other. Their header carries the picker, its
   arrows and the Mes / Año switch, which does not fit a third of the row — and a header that wraps
   puts the controls on a line of their own that reads as outside it (reported). */
.horus-dashboard-wide {
    display: grid;
    gap: 16px;
}

.horus-dashboard-grid + .horus-dashboard-wide {
    margin-top: 16px;
}

/* ---- Accent: one colour per SUBJECT (hours, time off, money), so a subject reads the same in
   every section. On a plain element, so the MD3 roles; success has no MD3 role and is the theme's
   overridden one (Lineamientos §6). Never the brand red. */
.horus-dashboard-accent-hours {
    --horus-dashboard-accent: var(--md-sys-color-tertiary);
}

.horus-dashboard-accent-timeoff {
    --horus-dashboard-accent: var(--md-sys-color-secondary);
}

.horus-dashboard-accent-money {
    --horus-dashboard-accent: var(--mud-palette-success);
}

.horus-dashboard-card-title {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

/* The colour lives on a mark, never on the title: a glyph in its accent over a disc of the same
   accent washed into the surface. Mixed over SURFACE, not transparent, so it is one tone on the
   white header band whatever sits behind. */
.horus-dashboard-card-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    flex: none;
    color: var(--horus-dashboard-accent);
    background: color-mix(in srgb, var(--horus-dashboard-accent) 16%, var(--md-sys-color-surface));
}

/* 12px instead of the section header's 8px: the 32px icon disc is taller than a bare title, and at
   8px it sat tight against the band's edges (reported). Only here — the disc is the dashboard's. */
.horus-dashboard-card > .horus-section-header {
    padding-top: 12px;
    padding-bottom: 12px;
}

.horus-dashboard-period {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 16px;
}

/* ---- List tiles ----
   Every item of a widget's list is a tile, so consecutive items read as separate things (reported:
   the holidays and the leave ran together). What has passed is neutral; what is AHEAD — or under
   way — is washed with the widget's accent, which is what tells "última" from "próxima" before the
   label is read. Both mixed over surface, so the step points the same way in both themes. */
.horus-dashboard-tile {
    padding: 10px 12px;
    border-radius: 8px;
    background: color-mix(in srgb, var(--md-sys-color-background) 70%, var(--md-sys-color-surface));
}

/* Ahead: a wash of the accent AND a stripe of it on the leading edge, so "what comes next" does not
   rest on a hue difference alone. The stripe is an inset shadow, not a border: a border would push
   the content 3px right of the neutral tiles beside it. The wash is mixed over a per-theme base
   (md3-tokens.css) — over the surface it lightened dark's tile until secondary text fell to 3.98:1. */
.horus-dashboard-tile.is-ahead {
    background: color-mix(in srgb, var(--horus-dashboard-accent) 8%, var(--horus-dashboard-tile-ahead-base));
    box-shadow: inset 3px 0 0 var(--horus-dashboard-accent);
}

/* Money still owed is ahead in RED, not in the card's green (reported: a pending invoice washed
   green read as settled). The same recipe with the accent swapped for the theme's error role, so
   the wash stays as faint as every other tile's and its text keeps the measured contrast. */
.horus-dashboard-tile.is-ahead.is-due {
    --horus-dashboard-accent: var(--md-sys-color-error);
}

.horus-dashboard-card {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* 24px horizontal, the same inset as the header band above it, so the title and the content
   start on one line. */
.horus-dashboard-card-body {
    flex: 1 1 auto;
    padding: 16px 24px;
    min-width: 0;
}

.horus-dashboard-card-body .horus-dashboard-label {
    display: block;
    margin-bottom: 6px;
}

.horus-dashboard-empty {
    font-size: 0.875rem;
    color: var(--horus-text-secondary);
}

.horus-dashboard-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* One fact per line: what (bold) with its detail underneath, and anything about WHEN or its
   state on the right. */
.horus-dashboard-line {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 12px;
}

.horus-dashboard-line-main {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.horus-dashboard-line-aside {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: none;
}

.horus-dashboard-line-title {
    font-weight: 700;
    color: var(--md-sys-color-on-surface);
}

.horus-dashboard-line-value {
    color: var(--md-sys-color-on-surface);
}

.horus-dashboard-line-sub {
    font-size: 0.8125rem;
    color: var(--horus-text-secondary);
}

/* A tooltip root that has to stack like a block: two classes, or MudBlazor's
   `.mud-tooltip-root.mud-tooltip-inline { display: inline-block }` wins (Lineamientos §3). */
.mud-tooltip-root.horus-dashboard-holiday-country {
    display: block;
}

/* The countdown is what the "to send" half of the hours card exists for, so it carries the weight.
   Late keeps the figure in on-surface and puts the amber on the icon beside it — the legible
   amber for a MARK (Lineamientos §6); a text needs 4.5:1, which that amber doesn't give. */
.horus-dashboard-deadline {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    column-gap: 8px;
    margin-top: 8px;
}

.horus-dashboard-deadline-figure,
.horus-dashboard-balance-figure {
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--md-sys-color-on-surface);
}

.horus-dashboard-deadline-icon {
    align-self: center;
    color: var(--horus-warning-mark);
}

.horus-dashboard-balance-figure {
    margin-bottom: 6px;
}

.horus-dashboard-balance-figure.is-over {
    color: var(--mud-palette-error);
}

/* ---- Cash: one block per currency ----
   A tile inside a card is a border and a radius, not another card (Lineamientos §5). */
.horus-dashboard-cash {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 16px;
}

.horus-dashboard-cash-currency {
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: 8px;
    padding: 12px 16px;
}

/* A tooltip root that has to stack like a block: two classes against MudBlazor's inline-block. */
.mud-tooltip-root.horus-dashboard-cash-balance {
    display: block;
    margin-bottom: 8px;
}

.horus-dashboard-cash-flow {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
}

.horus-dashboard-cash-flow > :last-child {
    margin-left: auto;
}

/* ---- Billing donut ----
   The three slices are an ordered pipeline — collected → billed → not billed yet — so they take
   the hours ramp's three steps, darkest first, already measured over `surface` (see `.horus-hs`).
   Named for what they mean here, pointing at the ramp, so the ramp has ONE definition. */
.horus-dashboard {
    --horus-billing-collected: var(--horus-hs-billable);
    --horus-billing-open: var(--horus-hs-clientnonbillable);
    --horus-billing-unbilled: var(--horus-hs-internal);
}

.horus-billing {
    display: grid;
    /* The legend is capped: on a full-width card its amounts would otherwise drift a screen away
       from their labels. */
    grid-template-columns: 120px minmax(0, 480px);
    align-items: center;
    gap: 24px;
}

.horus-billing-donut {
    width: 120px;
    height: 120px;
}

.horus-billing-arc {
    fill: none;
    stroke-width: 6;
}

.horus-billing-arc-collected,
.horus-billing-swatch-collected {
    stroke: var(--horus-billing-collected);
    background: var(--horus-billing-collected);
}

.horus-billing-arc-open,
.horus-billing-swatch-open {
    stroke: var(--horus-billing-open);
    background: var(--horus-billing-open);
}

.horus-billing-arc-unbilled,
.horus-billing-swatch-unbilled {
    stroke: var(--horus-billing-unbilled);
    background: var(--horus-billing-unbilled);
}

/* The pie and its legend on the left, the per-client bars filling the rest — on a full-width card
   the pie alone left two thirds of it empty (reported). Stacked when the card is narrow. */
.horus-billing-layout {
    display: grid;
    grid-template-columns: minmax(0, 620px) minmax(0, 1fr);
    align-items: center;
    gap: 40px;
}

@container (max-width: 960px) {
    .horus-billing-layout {
        grid-template-columns: minmax(0, 1fr);
        gap: 24px;
    }
}

/* The per-client bars are segments of the hours ranking, filled with the pie's three steps. */
.horus-billing-seg-collected {
    --horus-hs-fill: var(--horus-billing-collected);
}

.horus-billing-seg-open {
    --horus-hs-fill: var(--horus-billing-open);
}

.horus-billing-seg-unbilled {
    --horus-hs-fill: var(--horus-billing-unbilled);
}

/* What is still to collect, under a rule: a different scope from everything above it. */
.horus-billing-receivables {
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid var(--md-sys-color-outline-variant);
}

/* `min(…, 100%)`: on a phone the card body is under 320px, and a bare 320px minimum pushed the
   tiles past its edge (measured at 375px: 43px over). */
.horus-dashboard-receivables {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
    gap: 8px;
}

.horus-dashboard-receivable-aside {
    flex-direction: column;
    align-items: flex-end;
    gap: 0;
}

/* ONE grid for every row, rows in `display: contents`, so the amounts line up in a column whatever
   the length of the label — a grid per row aligns nothing (Lineamientos §3). */
.horus-billing-legend {
    display: grid;
    grid-template-columns: 10px minmax(0, 1fr) auto auto;
    column-gap: 8px;
    row-gap: 8px;
    align-items: baseline;
    font-size: 0.875rem;
}

.horus-billing-row {
    display: contents;
}

.horus-billing-row > .horus-hs-swatch {
    align-self: center;
}

.horus-billing-amount {
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--md-sys-color-on-surface);
}

.horus-billing-share {
    min-width: 3ch;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--horus-text-secondary);
}

/* The rule of a hand-written sum, as ONE element spanning the grid. It was a border on each cell
   of the total row, and in a baseline-aligned grid every cell starts at a different height, so the
   rule came out staggered and read as nothing (reported). */
.horus-billing-rule {
    grid-column: 1 / -1;
    border-top: 1px solid var(--md-sys-color-outline-variant);
}

.horus-billing-total > * {
    font-weight: 700;
}

/* On a card too narrow for the donut beside its legend, the legend goes under it. */
@container (max-width: 380px) {
    .horus-billing {
        grid-template-columns: minmax(0, 1fr);
        justify-items: center;
    }
}

.horus-dashboard-card-body {
    container-type: inline-size;
}

/* ---- A client's hours: twelve months stacked by project ----
   The projects take the hours ramp's three steps by size, most hours darkest and at the baseline —
   the ramp is ordinal, and size is the order the legend can be checked against. Never a fourth
   step: it could not be told apart (see ClientHoursChart). The current month is drawn like the
   rest and said in its tooltip and the note under the chart, as on /reports/statistics: a lighter
   bar would read as the next step of the ramp, which here means another project. */
.horus-client-hours-seg-0 {
    --horus-hs-fill: var(--horus-hs-billable);
}

.horus-client-hours-seg-1 {
    --horus-hs-fill: var(--horus-hs-clientnonbillable);
}

.horus-client-hours-seg-2 {
    --horus-hs-fill: var(--horus-hs-internal);
}

.horus-hs-swatch.horus-client-hours-seg-0,
.horus-hs-swatch.horus-client-hours-seg-1,
.horus-hs-swatch.horus-client-hours-seg-2 {
    background: var(--horus-hs-fill);
}

/* On a phone a column is ~20px and every month name came out as an ellipsis (measured at 375px).
   Every other label is hidden instead — kept in its slot so the rest stay under their bars — and the
   ones left spill into the empty slots on both sides. The first label carries the year, so it
   is never lost. */
@container (max-width: 520px) {
    /* Flex-centred: a block centres its text only while it fits, and spilt text ran right of
       its bar. */
    .horus-client-hours-labels .horus-hs-label {
        display: flex;
        justify-content: center;
        overflow: visible;
        text-overflow: clip;
    }

    .horus-client-hours-labels .horus-hs-label:nth-child(even) {
        visibility: hidden;
    }
}

/* The window's total on the left, the key with each project's share of it on the right. */
.horus-client-hours-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: 8px 24px;
}

/* ---- Salaries: a range of months and one person ----
   The two ends of the range sit together with a dash between them: each is a PeriodPicker without
   arrows (two pairs side by side would not say which end they move). */
.horus-dashboard-range {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Wide enough for the longest name in the team, narrow enough to share the header band. */
.horus-dashboard-person {
    width: 240px;
    flex: none;
}

/* ---- Salaries ----
   Nobody chosen: one row per person with a sparkline. One person chosen: their line alone, on the
   hours chart's gutter, gridlines and labels with an SVG on top. (A single chart with every line on
   one axis came first and was hard to read — the lines crossed and overlapped.) */
.horus-salary-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}

/* Strokes that keep their pixel width while the 1000×1000 box stretches to the plot. */
.horus-salary-line {
    fill: none;
    vector-effect: non-scaling-stroke;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.horus-salary-line-focus {
    stroke: var(--horus-hs-billable);
    stroke-width: 2.5;
}

/* 8px markers with a 2px surface ring, so a marker on a crossing line still reads as a point. */
.horus-salary-dot {
    position: absolute;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    transform: translate(-50%, 50%);
    background: var(--horus-hs-billable);
    box-shadow: 0 0 0 2px var(--md-sys-color-surface);
    pointer-events: none;
    z-index: 1;
}

.horus-salary-crosshair {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 1px;
    transform: translateX(-50%);
    background: var(--md-sys-color-outline);
    pointer-events: none;
}

/* One hit target per month, over everything, so the hover does not depend on touching a 2px line. */
.horus-salary-hits {
    position: absolute;
    inset: 0;
    display: flex;
    z-index: 2;
}

.horus-salary-hit {
    flex: 1 1 0;
}

.horus-salary-tooltip {
    position: absolute;
    top: 4px;
    z-index: 3;
    min-width: 200px;
    padding: 8px 12px;
    border-radius: var(--mud-default-borderradius);
    border: 1px solid var(--md-sys-color-outline-variant);
    background: var(--md-sys-color-surface);
    box-shadow: var(--mud-elevation-4);
    color: var(--md-sys-color-on-surface);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    pointer-events: none;
}

.horus-salary-tooltip-focus {
    font-weight: 700;
}

/* The rows: ONE grid, each row a subgrid of it spanning the four columns, so every sparkline starts
   and ends at the same x and the figures line up — a grid per row aligns nothing (Lineamientos §3).
   A row is a <button>: it opens that person's line. */
.horus-salary-rows {
    display: grid;
    grid-template-columns: fit-content(33%) minmax(120px, 1fr) auto auto;
    column-gap: 16px;
    row-gap: 4px;
    align-items: center;
}

.horus-salary-row {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
    align-items: center;
    padding: 6px 8px;
    margin: 0 -8px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.horus-salary-row:hover,
.horus-salary-row:focus-visible {
    background: color-mix(in srgb, var(--md-sys-color-on-surface) 6%, var(--md-sys-color-surface));
}

.horus-salary-col-figure {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* The sparkline: a stretched box with non-scaling strokes, and its last point as an HTML marker. */
.horus-salary-spark {
    position: relative;
    display: block;
    height: 28px;
}

.horus-salary-spark svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}

.horus-salary-line-spark {
    stroke: var(--horus-hs-billable);
    stroke-width: 1.5;
}

.horus-salary-dot-small {
    width: 6px;
    height: 6px;
}

