:root {
    --tv-default-border-radius: 0.5rem;

    /* Width of the docked AI chat panel. Shared by the panel itself and by the
       margin-right that shifts .main-content out from under it — they must match. */
    --ai-chat-docked-width: min(400px, 35vw);

    /* ── Tier 1: typography ──
       The app used to inherit the stock `dotnet new blazor` stack
       ('Helvetica Neue', Helvetica, Arial). Neither Helvetica face exists on Windows, so
       app-authored text fell through to Arial while every Syncfusion control rendered in the
       theme's own stack — two typefaces with different x-heights and metrics on every screen.
       This mirrors Bootstrap 5.3's native stack so app text and component text match.

       Font *family* is set globally; the size/weight tokens below are applied deliberately by
       components. Setting a global font-size would reflow every existing screen, and TimeView is
       a dense power-user tool where that density is a feature. */
    --tv-font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
                    "Noto Sans", "Liberation Sans", Arial, sans-serif,
                    "Apple Color Emoji", "Segoe UI Emoji";

    /* Machine output — trace ids, request ids, stack traces. Mirrors Bootstrap 5.3's own
       monospace stack for the same reason as the sans stack above. */
    --tv-font-mono: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
                    "Courier New", monospace;

    /* Type scale. Five steps, because five is what the screens actually need:
       page title, section heading, body, supporting metadata, and the large numerals
       on KPI/summary cards. Resist adding a sixth without a screen that needs it. */
    --tv-text-page-title: 1.375rem;
    --tv-text-section:    1rem;
    --tv-text-body:       0.875rem;
    --tv-text-meta:       0.75rem;
    --tv-text-metric:     1.75rem;

    --tv-weight-regular: 400;
    --tv-weight-medium:  500;
    --tv-weight-strong:  600;

    --tv-leading-tight:  1.25;
    --tv-leading-normal: 1.5;

    /* ── Tier 1: spacing ──
       A 4px base step. Kept short on purpose: page gutter and section gap are the two values
       that were missing everywhere (grids ran edge-to-edge with no gutter), the numbered steps
       cover component-internal spacing. */
    --tv-space-1: 0.25rem;
    --tv-space-2: 0.5rem;
    --tv-space-3: 0.75rem;
    --tv-space-4: 1rem;
    --tv-space-5: 1.5rem;
    --tv-space-6: 2rem;

    --tv-page-gutter: var(--tv-space-4);
    --tv-section-gap: var(--tv-space-5);

    /* ── Tier 2: focus ──
       Two-tone ring: a thin separating ring against the element, then the accent.
       The separator uses the theme's own background so it reads on light and dark
       alike (the stock template hardcoded `white`, which vanished on dark panels). */
    --tv-focus-ring: var(--tv-accent);
    --tv-focus-ring-inner: var(--color-sf-content-bg-color, #ffffff);

    /* ── Tier 1: primitive palette ─────────────────────────────────────────
       Raw brand colors, named by hue. Every semantic token below points here,
       so a hue is changed in exactly one place. Do not reference these directly
       from component styles — use a semantic token instead.

       Where a hue has a natural match in Syncfusion's semantic theme variables,
       it inherits from the active theme (var(--color-sf-*, <brand fallback>)) so
       status colors track theme switches (light/dark/high-contrast/Fluent/etc.).
       The brand hex is the fallback for themes that don't define the variable
       (e.g. bootstrap5, highcontrast). Hues with no semantic Syncfusion match
       (grey/orange/black) stay explicit.
       Caveat: some Syncfusion themes define --color-sf-* as raw RGB triplets
       ("r, g, b") rather than full colors, so on those the token resolves to an
       invalid value and the element falls back to its inherited color. The
       supported family (bootstrap5.3) defines them normally. */
    --palette-grey:   #757575;
    --palette-blue:   var(--color-sf-info,    #4dadff);
    --palette-green:  var(--color-sf-success, #2e7d32);
    --palette-amber:  var(--color-sf-warning, #f9a825);
    --palette-red:    var(--color-sf-danger,  #c62828);
    --palette-accent: var(--color-sf-primary, #0d6efd);
    --palette-orange: #ff814c;
    --palette-black:  #000000;

    /* Deep variants, for use as a solid *fill* behind white text. The theme-derived hues above are
       tuned for glyphs and borders on the page background; as a background they leave white text at
       roughly 2:1, so they cannot simply be reused. Explicit hex on purpose — a theme-inherited
       value gives no guarantee of the contrast a filled chip needs. */
    --palette-green-deep: #1b5e20;
    --palette-amber-deep: #a35200;

    /* ── Tier 2: neutral chrome ──
       Derived from the element's own foreground rather than a fixed light grey, so
       surfaces and separators stay legible on light, dark and high-contrast themes.
       (currentColor is resolved where the token is used, not here.) */
    --tv-border-color: color-mix(in srgb, currentColor 25%, transparent);
    --tv-surface-alt: color-mix(in srgb, currentColor 8%, transparent);

    /* Same idea, under the names components already reference. Both were used across the UI with
       hardcoded light-theme hex fallbacks (#555 / #d0d0d0 / #595959) and defined nowhere, so every
       use silently fell back — including on dark themes, where a mid-grey on a dark panel fails
       contrast. Deriving from currentColor makes them track the active theme. */
    --tv-color-text-secondary: color-mix(in srgb, currentColor 70%, transparent);
    --tv-color-border: var(--tv-border-color);

    /* ── Tier 2: accent fill and its foreground ──
       Syncfusion themes expose --color-sf-primary but no matching "on primary"
       variable, so a component that fills itself with the accent has nothing to
       read for its foreground. Declared once here rather than hardcoded per
       component. White holds up against the accent on every bundled theme; a
       theme with a light primary would override this token, not the components. */
    --tv-accent: var(--palette-accent);
    --tv-on-accent: #ffffff;

    /* ── Tier 2: in-place search ──
       Marks the rows a client-side search matched (e.g. the batch-edit field list).
       Amber rather than the primary colour so a hit never reads as a selection. */
    --tv-search-match: var(--palette-amber);

    /* ── Tier 2: global search ──
       The app-bar search box and its results panel. The panel floats above page content, so it
       needs a surface of its own rather than inheriting whatever it happens to overlap. Values
       come from the Syncfusion theme so the panel follows a theme switch; the hexes are only
       fallbacks for themes that define neither. */
    --tv-search-surface: var(--color-sf-flyout-bg-color, var(--color-sf-content-bg-color, #ffffff));
    --tv-search-border: var(--color-sf-border, rgba(128, 128, 128, .35));
    --tv-search-hover: var(--color-sf-hover-bg-color, rgba(128, 128, 128, .12));
    /* Demoted rows: ended employees, inactive/blocked customers, archived tasks. Dimmed rather
       than hidden — they are still results, just not the ones you probably meant. */
    --tv-search-muted: var(--palette-grey);

    /* ── Tier 2: generic indicators (value-vs-target cards) ── */
    --indicator-neutral: currentColor;
    --indicator-good: var(--palette-green);
    --indicator-warning: var(--palette-amber);
    --indicator-bad: var(--palette-red);

    /* ── Tier 2: time-registration approval domain ──
       Distinct from task/invoice status on purpose — the approved green (#4CA44C)
       is intentionally lighter than the generic --palette-green. */
    --approval-draft: var(--palette-amber);
    --approval-employee-closed: var(--palette-blue);
    --approval-management-approved: #4CA44C;
    --approval-management-denied: var(--palette-black);

    /* ── Tier 2: day norm status (fill-remaining-hours day strip) ──
       Solid chips with white text rather than coloured glyphs, so these point at the deep palette
       variants. This is the one place a status colour is used as a fill; everywhere else it is text. */
    --day-norm-met: var(--palette-green-deep);
    --day-norm-short: var(--palette-amber-deep);

    /* ── Tier 2: task status domain (TvTask.Status) ── */
    --task-status-notstarted:  var(--palette-grey);
    --task-status-inprogress:  var(--palette-blue);
    --task-status-completed:   var(--palette-green);
    --task-status-approved:    var(--palette-green);
    --task-status-disapproved: var(--palette-red);

    /* ── Tier 2: deadline status domain (TvTaskDeadline.Status) ── */
    --deadline-status-created:  var(--palette-grey);
    --deadline-status-done:     var(--palette-green);
    --deadline-status-declined: var(--palette-red);

    /* ── Tier 2: email delivery status domain (EmailLog.Status) ──
       Amber (not red) for a spam complaint: the message was delivered, so it is a reputation
       warning rather than a failure to deliver. Grey covers both "not yet known" (Queued) and
       "will never be known" — a Flowmailer row, which no webhook ever updates. */
    --email-status-queued:        var(--palette-grey);
    --email-status-sent:          var(--palette-blue);
    --email-status-delivered:     var(--palette-green);
    --email-status-bounced:       var(--palette-red);
    --email-status-spamcomplaint: var(--palette-amber);
    --email-status-failed:        var(--palette-red);
    --email-status-unknown:       var(--palette-grey);

    /* ── Tier 2: invoice status domain (Invoice.Status) ──
       Draft is orange (work in progress) rather than grey, matching the legacy client's
       orange draft overlay; the rest follow the blue → green progression. */
    --invoice-status-draft:      var(--palette-orange);
    --invoice-status-done:       var(--palette-blue);
    --invoice-status-approved:   var(--palette-green);
    --invoice-status-posted:     var(--palette-blue);
    --invoice-status-dispatched: var(--palette-blue);
    --invoice-status-paid:       var(--palette-green);
    --invoice-status-cancelled:  var(--palette-red);

    /* ── Tier 2: scheduled job results (SchedulableExecutionResultEnum) ──
       "Nothing to do" is grey rather than green: it is a non-event, and colouring it as success
       makes an idle job look as reassuring as one that actually did work. */
    --job-result-ok:            var(--palette-green);
    --job-result-witherrors:    var(--palette-amber);
    --job-result-failed:        var(--palette-red);
    --job-result-stopped:       var(--palette-red);
    --job-result-nowork:        var(--palette-grey);
    --job-result-unknown:       var(--palette-grey);
    --job-result-info:          var(--palette-blue);

    /* ── Tier 2: receivables aging bands (invoicing dashboard) ──
       A severity ramp, not a status: green while nothing is due, then progressively hotter the
       longer a receivable has been outstanding. Amber is reused for the first two overdue bands
       because the palette has no distinct "mild warning" hue. */
    --invoice-aging-not-due:    var(--palette-green);
    --invoice-aging-1-30:       var(--palette-amber);
    --invoice-aging-31-60:      var(--palette-orange);
    --invoice-aging-61-90:      var(--palette-red);
    --invoice-aging-over-90:    var(--palette-black);

    /* ── Tier 2: invoice line shortfall/profit (InvoiceDraftLine.Shortfall) ──
       Billed below the calculated price is a loss, above it a profit; zero stays unstyled. */
    --invoice-shortfall-negative: var(--palette-red);
    --invoice-shortfall-positive: var(--palette-green);

    /* ── Tier 2: invoice key figure trend (InvoiceDetail trend chart) ──
       Series identity, not status: the same metric keeps one colour across every invoice on the
       chart. The margin line takes the theme accent so it reads as a different *kind* of quantity
       (a percentage, on its own axis) rather than as a fourth amount. The current invoice reuses
       the draft colour because that is exactly what it is - a figure still being edited. */
    --invoice-trend-price:        var(--palette-blue);
    --invoice-trend-contribution: var(--palette-green);
    --invoice-trend-shortfall:    var(--palette-amber);
    --invoice-trend-margin:       var(--palette-accent);
    --invoice-trend-current:      var(--palette-orange);

    /* ── Tier 2: operation queue domain (QueuedOperation.Status) ──
       Terminal states reuse the indicator tokens; the two non-terminal states get their own
       so "waiting for a worker" reads differently from "neutral". */
    --operation-queue-waiting: var(--palette-grey);
    --operation-queue-running: var(--palette-blue);

    /* ── Tier 2: activity centre badge ──
       A solid accent fill behind white text. It must not use --indicator-neutral: that token is
       currentColor, which in the app bar is the white icon colour, giving a white pill with white
       text. The failed variant is handled by --indicator-bad in the component's own stylesheet. */
    --activity-center-badge-bg: var(--palette-accent);
    --activity-center-badge-fg: var(--tv-on-accent);

    /* ── Tier 2: DMS document status domain (CorrespondenceListItem.ApprovalStatus) ── */    --doc-status-draft:    var(--palette-orange);
    --doc-status-finished: var(--palette-blue);
    --doc-status-denied:   var(--palette-black);
    --doc-status-approved: var(--palette-green);

    /* ── Tier 2: time-reg schema pin affordances (TimeRegSchema) ──
       Not a status domain: these are chrome. --pin-toggle stays a neutral, because it also draws the
       ring on the topbar's *primary-blue* quick-add toggle, where a coloured ring would be
       accent-on-accent and vanish.
       The row glyphs get their own pair. Unpinned is the info blue on the outline glyph: coloured
       enough to read at 18px, but the outline keeps its visual weight below the pinned state.
       Pinned is orange — a warm hue no other schema affordance uses, so a pinned row is spotted
       without reading the icon and never confused with today's blue column or a primary button.
       Orange is an explicit hex rather than --palette-amber because amber inherits the theme's
       warning colour, which on light themes is a pale yellow that all but disappears on white. */
    --pin-toggle: var(--color-sf-content-text-color, #e0e0e0);
    --pin-toggle-pinned: var(--palette-accent);
    --pin-glyph: var(--palette-blue);
    --pin-glyph-pinned: var(--palette-orange);

    /* ── Tier 2: planning grid domain (PlanningGrid) ──
       Restricted-row label/glyph is a muted neutral, not a status color - it signals "hidden by
       design", not good/bad/pending. Rejected/pending/receipt cell accents reuse the generic
       indicator tokens above rather than inventing a parallel red/blue/green. */
    --planning-restricted: var(--palette-grey);
    --planning-inactive-customer: var(--indicator-warning);
    --planning-cell-pending: var(--indicator-neutral);
    --planning-cell-rejected: var(--indicator-bad);
    --planning-cell-receipt: var(--indicator-good);

    /* -- Tier 2: planning capacity scale (PlanningCapacityCell, #575/#576/#577) --
       Five bands diverging around 100 %: below is spare capacity, around 100 % is balance, above is
       overbooked. Both ends are problems and the middle is the healthy one, which is why this is a
       diverging scale and not the sequential --indicator-* good-to-bad set.

       The outer bands reuse the primitives so they track the theme, matching the mockup's own blue
       and red. The two inboard tints are explicit rgba rather than a color-mix() of those primitives,
       for the reason documented on --timereg-today-tint below: color-mix() over var(--color-sf-*)
       resolves to an invalid colour on themes that expose those as bare RGB triplets (Material 3),
       which drops the fill entirely instead of degrading. The channels are the mockup's #d03b3b and
       #256abf at the alpha its light tints imply.

       The balance band is deliberately NOT one of the mockup's near-white/near-black pair. Freezing
       #f0efec here would paint a near-white fill on the dark theme - the exact failure the
       --tv-color-text-secondary comment above records having fixed. Deriving it from currentColor
       tracks every theme, and currentColor-based color-mix() is safe on Material 3 because it reads
       no --color-sf-* variable. */
    --planning-load-over: var(--palette-red, #c62828);
    --planning-load-over-soft: rgba(208, 59, 59, 0.28);
    --planning-load-mid: var(--tv-surface-alt);
    --planning-load-spare-soft: rgba(38, 106, 191, 0.28);
    --planning-load-spare: var(--palette-blue, #4dadff);

    /* ── Tier 2: time registration schema — today's column ──
       Today is a navigational cue, not a status, so it must not collide with the approval
       colours the schema already paints onto day headers.
       The tint is written as an explicit rgba rather than derived from --palette-blue with
       color-mix()/relative-colour syntax: those resolve to an invalid colour on themes that
       define --color-sf-* as bare RGB triplets (Material 3), which would silently drop the
       highlight entirely rather than degrade. */
    --timereg-today-accent: var(--palette-blue, #4dadff);
    --timereg-today-tint: rgba(77, 173, 255, 0.12);

    /* Drag & drop of hours between days in the schema. The "over" tint is written as an explicit
       rgba rather than a colour-mix of the primitive: it sits behind the cell's textbox, so it has
       to stay translucent no matter which theme resolves --palette-blue. */
    --timereg-drop-valid: var(--palette-blue, #4dadff);
    --timereg-drop-over: rgba(77, 173, 255, 0.22);

    /* ── Tier 2: notifications (toasts + error dialog) ──
       Toasts sit in the bottom-left corner, flush to the edge: they deliberately overlay the
       navigation when it is open, which the stacking scale below permits (toast 1300 > sidebar 1001).
       Colours point at the Tier-1 primitives, which inherit from the active Syncfusion theme, so the
       accent of a toast matches the rest of the app instead of a second hardcoded palette. */
    --tv-toast-offset-block: 1.25rem;
    --tv-toast-offset-inline: 0;

    /* Toast surfaces are mixed from the accent into the *theme's* own toast background, so light,
       dark and high contrast all work from one declaration — no parallel [data-theme] block.
       The mix stays opaque: a translucent toast would show the page through it and wreck contrast. */
    --notification-info-accent: var(--palette-blue, #4dadff);
    --notification-info-surface: color-mix(in srgb, var(--notification-info-accent) 14%, var(--color-sf-content-bg-color, #fff));
    --notification-success-accent: var(--palette-green, #2e7d32);
    --notification-success-surface: color-mix(in srgb, var(--notification-success-accent) 14%, var(--color-sf-content-bg-color, #fff));
    --notification-warning-accent: var(--palette-amber, #f9a825);
    --notification-warning-surface: color-mix(in srgb, var(--notification-warning-accent) 16%, var(--color-sf-content-bg-color, #fff));
    --notification-error-accent: var(--palette-red, #c62828);
    --notification-error-surface: color-mix(in srgb, var(--notification-error-accent) 14%, var(--color-sf-content-bg-color, #fff));

    /* Fremdriftsringen on "Mangler at blive planlagt" (#573). Both are set explicitly because
       Syncfusion writes the circular ring's colours as SVG *presentation attributes*
       (stroke="#E9ECEF" / "#007bff") rather than taking them from the theme stylesheet - so an
       unstyled ring renders a near-white track on the dark theme's #1e1e1e panel, where the empty
       portion is the loudest thing on the row and a 0% ring reads as a full one. */
    --planning-progress-ring: var(--palette-accent);
    --planning-progress-track: var(--color-sf-border, #495057);

    /* ── Tier 2: file-type glyphs (DMS document list) ──
       Vendor brand hues (Word/Excel/PDF) plus generic image/archive accents;
       no Tier-1 semantic match exists for these, so they stay explicit hex. */
    --filetype-word:    #2b579a;
    --filetype-excel:   #217346;
    --filetype-powerpoint: #d24726;
    --filetype-pdf:     #d93025;
    --filetype-email:   #0072c6;
    --filetype-image:   #8e44ad;
    --filetype-archive: #f39c12;

    /* Form validation / error accents (Blazor template default #e50000). */
    --color-danger: #e50000;

    /* ── Stacking order for app chrome ──
       The sticky TopBar sits at 1000 and the main-layout sidebar is lifted to
       1001 (see theme-overrides.css) so it covers the TopBar on mobile. The AI
       chat slide-over must clear the sidebar — expanded, it spans the full
       viewport width. A modal dialog must clear everything, so it is
       deliberately defined one band above both.
       Toasts sit *above* ordinary dialogs, not below: several screens (the mobile time-tracking
       timer, for one) are themselves modal dialogs, and a toast they raise would otherwise render
       behind their own overlay. The error dialog then has to clear the toasts too, so an exception
       is never hidden behind the notification reporting it. */
    --tv-z-chat-panel: 1050;
    --tv-z-dialog: 1100;
    /* Full-screen app overlays (mobile global search, planning grid busy shade). */
    --tv-z-overlay: 1200;
    --tv-z-toast: 1300;
    --tv-z-error-dialog: 1400;
}

html, body {
    font-family: var(--tv-font-sans);
}

/* Bootstrap's own stylesheet is not loaded (see App.razor), so these few element
   defaults have to be stated here or links fall back to the browser's default blue.
   They previously carried the stock template's hardcoded light-theme hex values,
   which stayed light-blue on the dark and high-contrast themes; they now track the
   accent token instead. */
a, .btn-link {
    color: var(--tv-accent);
}

.btn-primary {
    color: var(--tv-on-accent);
    background-color: var(--tv-accent);
    border-color: var(--tv-accent);
}

/* The inner white ring was invisible against a dark panel, leaving a single-colour
   halo instead of the intended two-tone focus indicator. currentColor's inverse is
   not available in CSS, so the surface token supplies the separating ring. */
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.1rem var(--tv-focus-ring-inner), 0 0 0 0.25rem var(--tv-focus-ring);
}

/* The layout markup carried Bootstrap's `px-2` for its horizontal gutter, but the Bootstrap
   stylesheet is not loaded (its <link> is commented out in App.razor), so that class resolved to
   nothing and every page — the full-bleed entity grids most visibly — ran edge-to-edge. The
   gutter is owned here instead, via the shared token. */
.content {
    padding-top: 0.2rem;
    padding-inline: var(--tv-page-gutter);
}

/* State borders for summary/KPI cards. These four class names are used across the dashboard, the
   landing page and the organisation hierarchy page, and were documented as an existing mechanism,
   but no stylesheet ever defined them — so every "state border" in the app silently rendered as
   no border at all. Defined here against the Tier-2 indicator tokens, next to the other globals
   the components assume exist.

   Good and Neutral are deliberately borderless: they are the absence of a problem, and marking
   them is what made every card on a page shout equally loudly. The tokens are kept for callers
   that want the colour, but the border is reserved for states that need attention. */
.card-border-warning,
.card-border-bad {
    border-left: 4px solid;
}

.card-border-warning {
    border-left-color: var(--indicator-warning);
}

.card-border-bad {
    border-left-color: var(--indicator-bad);
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid var(--color-danger);
}

.validation-message {
    color: var(--color-danger);
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* Gør knappen til referencepunkt for placering */
.e-btn.timeregistrationSchemaLinkToTimereg {
    position: relative !important;
    overflow: visible !important; /* Vigtigt: så badget ikke bliver klippet af */
}

/* Placer badget helt oppe i højre hjørne */
.custom-inline-dot {
    position: absolute !important;
    top: -5px; /* Dette virker allerede hos dig */
    /* NYE RETTELSER: */
    right: -7px !important; /* Prøv en høj værdi for at se effekten */
    left: auto !important; /* Vigtigt: fjerner eventuel låsning til venstre side */
    width: 13px !important;
    height: 16px !important;
    background-color: lightgray !important;
    display: flex !important;
    justify-content: center;
    align-items: center;
    margin: 0 !important;
    padding: 0 !important; /* Fjern din padding-bottom: 14px hvis muligt */
    color: black !important;
}

.employeeDraftColor {
    background-color: var(--approval-draft) !important;
    color: white !important;
}

.timeregDoneNotApprovedColor {
    background-color: var(--approval-employee-closed) !important;
    color: white !important;
}

.timeregManagementApprovedColor {
    background-color: var(--approval-management-approved) !important;
    color: white !important;
}

.timeregManagementDeniedColor {
    background-color: var(--approval-management-denied) !important;
    color: white !important;
}

.customTextAlignCenter {
    text-align: center !important;
}

.text-red {
    color: red !important;
}

.text-green {
    color: green !important;
}

/* Reset margin added by Syncfusion e-icon-left on icon-only buttons using MDI */
.e-icon-btn.e-btn .e-btn-icon.mdi {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Outline style for TvButton instances only: transparent bg, theme-aware text/icon/border, primary color on hover */
.e-btn.tv-btn {
    background-color: transparent !important;
    border: 1px solid var(--color-sf-content-text-color) !important;
    color: var(--color-sf-content-text-color) !important;
}

.e-btn.tv-btn .e-btn-icon {
    color: inherit !important;
}

.e-btn.tv-btn:hover,
.e-btn.tv-btn:focus {
    background-color: transparent !important;
    border-color: var(--color-sf-primary) !important;
    color: var(--color-sf-primary) !important;
}

.e-btn.tv-btn:active {
    background-color: color-mix(in srgb, var(--color-sf-primary) 10%, transparent) !important;
    border-color: var(--color-sf-primary) !important;
    color: var(--color-sf-primary) !important;
}

/* Borderless variant for TvButton: apply via Borderless="true" */
.e-btn.tv-btn.e-btn-borderless {
    border: none !important;
}

.e-btn.tv-btn.e-btn-borderless:hover,
.e-btn.tv-btn.e-btn-borderless:focus {
    border: none !important;
}

.e-btn.tv-btn.e-btn-borderless:active {
    border: none !important;
}

/* Planning row markers -------------------------------------------------------------------------
   Global rather than component-scoped, because the same markers are rendered by more than one
   component. Blazor CSS isolation rewrites a .razor.css selector to match only its own component, so
   a shared marker needs a copy per component - and PlanRowsGrid never got one for .planning-restricted,
   which left the restricted label in "Min plan" (the view that actually ships) with no styling at all,
   while MyPlanGrid.razor.css carried a copy that matched nothing. One definition here removes both the
   duplication and that class of bug.

   Both markers put their glyph and label beside the name rather than behind it: no single text colour
   clears WCAG AA against every --indicator-* value the themes resolve to, so a coloured fill with a
   label on top cannot be made accessible in both themes at once. */
.planning-restricted {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--planning-restricted, #757575);
    font-style: italic;
}

.planning-restricted-icon {
    font-size: 1rem;
}

.planning-inactive-customer {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--planning-inactive-customer, var(--indicator-warning, #f9a825));
}

.planning-inactive-customer-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.planning-inactive-customer-icon {
    font-size: 1rem;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Global search (app bar + results panel + /search page)

   Lives here rather than in scoped component CSS because the box and the results
   panel are two components that share one visual surface: Blazor scoped styles do
   not cross a component boundary, so keeping them together avoids a ::deep chain
   whose specificity has to be maintained by hand.
   ───────────────────────────────────────────────────────────────────────────── */

/* Syncfusion's app bar sets `overflow: hidden` on `.e-appbar`, which clips anything taller than
   the 48px bar. That silently swallows the search results panel whole — the box accepts input,
   queries the API and renders the panel, but none of it is ever painted, so the feature reads as
   completely dead. Two class names beat Syncfusion's single-class rule, so no `!important`.
   Applied via an opt-in class rather than to `.e-appbar` globally, so only the bar that hosts an
   overflowing popup gives up its clipping. */
.e-appbar.appbar-allows-overflow {
    overflow: visible;
}

.global-search {
    position: relative;
    display: flex;
    align-items: center;
    /* Takes what it can up to a readable width, then stops: a search box as wide as
       the window makes the app bar look empty and the results panel unreadably wide. */
    flex: 0 1 26rem;
    min-width: 0;
    margin-inline: .5rem;
}

.global-search__field {
    display: flex;
    align-items: center;
    gap: .25rem;
    width: 100%;
    padding: .125rem .5rem;
    border: 1px solid var(--tv-search-border);
    border-radius: 1rem;
    background: var(--tv-search-hover);
}

.global-search__field:focus-within {
    border-color: var(--tv-accent);
}

.global-search__icon,
.global-search__shortcut {
    flex: none;
    opacity: .7;
}

.global-search__shortcut {
    font-size: .75rem;
    padding: .0625rem .25rem;
    border: 1px solid var(--tv-search-border);
    border-radius: .25rem;
    white-space: nowrap;
}

.global-search__input {
    flex: 1 1 auto;
    min-width: 0;
    border: none;
    background: transparent;
    color: inherit;
    font: inherit;
    padding: .25rem 0;
}

.global-search__input:focus {
    outline: none;
}

/* The browser's own clear affordance would sit beside ours and do something subtly
   different (it does not tell Blazor the value changed). */
.global-search__input::-webkit-search-cancel-button {
    display: none;
}

.global-search__clear,
.global-search__close,
.global-search-trigger {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2rem;
    min-height: 2rem;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    cursor: pointer;
}

.global-search__clear:hover,
.global-search__close:hover,
.global-search-trigger:hover {
    background: var(--tv-search-hover);
}

/* ── Mobile overlay ── */

.global-search--overlay {
    position: fixed;
    inset: 0 0 auto 0;
    z-index: 1200;
    flex-direction: column;
    align-items: stretch;
    flex: none;
    margin: 0;
    padding: .5rem;
    background: var(--tv-search-surface);
}

.global-search--overlay .global-search__panel {
    position: static;
    width: auto;
    max-height: calc(100vh - 4rem);
    border: none;
    box-shadow: none;
}

/* ── Results panel ── */

.global-search__panel {
    position: absolute;
    top: calc(100% + .25rem);
    left: 0;
    width: min(30rem, 90vw);
    max-height: min(28rem, 70vh);
    overflow-y: auto;
    z-index: 1100;
    padding: .25rem 0;
    border: 1px solid var(--tv-search-border);
    border-radius: .5rem;
    background: var(--tv-search-surface);
    box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .25);
}

.global-search-group + .global-search-group {
    border-top: 1px solid var(--tv-search-border);
}

.global-search-group__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
    padding: .375rem .75rem .125rem;
}

.global-search-group__heading {
    font-size: .75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    opacity: .7;
}

.global-search-group__more,
.global-search__all {
    border: none;
    background: transparent;
    color: var(--tv-accent);
    font: inherit;
    font-size: .8125rem;
    cursor: pointer;
    padding: .125rem .25rem;
}

.global-search-group__more:hover,
.global-search__all:hover {
    text-decoration: underline;
}

.global-search__all {
    display: block;
    width: 100%;
    text-align: center;
    padding: .5rem;
    border-top: 1px solid var(--tv-search-border);
}

.global-search-group__items {
    list-style: none;
    margin: 0;
    padding: 0;
}

.global-search-item {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .375rem .75rem;
    cursor: pointer;
}

.global-search-item:hover,
.global-search-item--active {
    background: var(--tv-search-hover);
}

.global-search-item--active {
    /* An inset marker rather than an outline: the row is inside a scrolling panel, and an
       outline would be clipped at the top and bottom edges. */
    box-shadow: inset .1875rem 0 0 0 var(--tv-accent);
}

.global-search-item__icon {
    flex: none;
    opacity: .8;
}

.global-search-item__text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
}

.global-search-item__title,
.global-search-item__subtitle {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.global-search-item__subtitle {
    font-size: .8125rem;
    opacity: .7;
}

.global-search-item__status,
.global-search-item__badge {
    flex: none;
    font-size: .75rem;
    opacity: .8;
}

/* Demoted hits are dimmed rather than removed. The status label beside them says why, so a
   low-ranked result reads as a decision instead of a bug — colour alone is never the signal. */
.global-search-item--demoted .global-search-item__title,
.global-search-item--demoted .global-search-item__icon {
    color: var(--tv-search-muted);
}

/* ── States and notices ── */

.global-search-state,
.global-search-notice {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    padding: .75rem;
    font-size: .875rem;
}

.global-search-state--error {
    color: var(--indicator-bad);
}

.global-search-state__hint {
    font-size: .8125rem;
    opacity: .7;
}

.global-search-notice {
    border-top: 1px solid var(--tv-search-border);
    opacity: .8;
}

.global-search__sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ── Full-screen page ── */

.global-search-page__heading {
    font-size: 1.25rem;
    margin: 0 0 .75rem;
}

.global-search-page__filters {
    display: flex;
    flex-wrap: wrap;
    gap: .375rem;
    margin-bottom: .75rem;
}

.global-search-chip {
    border: 1px solid var(--tv-search-border);
    border-radius: 1rem;
    background: transparent;
    color: inherit;
    font: inherit;
    font-size: .8125rem;
    padding: .1875rem .75rem;
    cursor: pointer;
}

.global-search-chip--active {
    border-color: var(--tv-accent);
    background: var(--tv-accent);
    color: var(--tv-on-accent);
}

.global-search-results--page .global-search-group {
    border: 1px solid var(--tv-search-border);
    border-radius: .5rem;
    margin-bottom: .75rem;
}

/* The spinner glyph only spins if something tells it to; MDI ships the class, not the motion. */
@keyframes global-search-spin {
    to { transform: rotate(360deg); }
}

.global-search-state .mdi-spin {
    animation: global-search-spin 1s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
    .global-search-state .mdi-spin {
        animation: none;
    }
}
