/* CSS custom properties ("CSS variables") live here. `--name: value;` inside a selector declares
   one; `var(--name)` anywhere else reads it. Declared on :root (the <html> element), every rule
   in this file can read them - that's the entire reason a color only needs to change in ONE
   place here to affect every button/cell/badge that uses it.
   The admin-editable colors below (--color-status-*) go one step further: StatusColorState.cs
   overrides them at RUNTIME via JavaScript - `document.documentElement.style.setProperty(...)`
   sets an INLINE style on <html>, and an inline style always wins over a plain stylesheet rule
   like this one (higher specificity), so the browser instantly repaints with the new value with
   no page reload and no Blazor re-render. See StatusColorState.cs's class comment for the full
   walkthrough of that mechanism. */
:root {
    /* Shared/TopBar.razor's height - also read by MainLayout.razor.css (sidebar/page offset) and
       the .notification-banner-wrap sticky rule below, so it only needs to change in one place. Only
       meaningful at the >=641px breakpoint where TopBar actually renders (display:none below
       that - see TopBar.razor.css) - the consumers gate their own use of it the same way. */
    --topbar-height: 56px;

    --color-primary: #2563eb;
    --color-primary-dark: #1d4ed8;
    --color-primary-light: #dbeafe;

    --color-sidebar-bg: #0f172a;
    --color-sidebar-bg-alt: #16213f;
    --color-sidebar-text: #cbd5e1;
    --color-sidebar-text-active: #ffffff;

    --color-bg: #f8fafc;
    --color-surface: #ffffff;
    --color-border: #e2e8f0;

    --color-text: #0f172a;
    --color-text-muted: #64748b;
    --color-danger: #dc2626;
    --color-danger-bg: #fef2f2;
    --color-danger-border: #fecaca;
    --color-success: #16a34a;
    --color-success-bg: #f0fdf4;
    --color-success-border: #bbf7d0;
    --color-warning: #b45309;
    --color-warning-bg: #fffbeb;
    --color-warning-border: #fde68a;

    /* Dedicated variables for the 4 admin-editable calendar/dashboard status categories -
       deliberately NOT reusing --color-danger-bg/border or --color-warning above, since those
       are shared with generic alert/warning UI app-wide (see .alert-danger, .alert-warning) and
       changing a status color here must not silently recolor unrelated error/warning banners.
       Seeded with today's literal values (StatusColorSeeder) - zero visual change by default. */
    --color-status-holiday-bg: #fef2f2;
    --color-status-holiday-border: #fecaca;
    --color-status-weekend-bg: #e9edf2;
    --color-status-weekend-border: #e2e8f0;
    --color-status-under-bg: #fed7aa;
    --color-status-under-border: #f97316;
    /* --color-status-today-bg deliberately has NO static default here (unlike the other 3) -
       .calendar-cell-today's box-shadow relies on var(--color-status-today-bg, transparent)
       genuinely resolving to transparent when "Bez tła" is configured (StatusColorState.ApplyAsync
       removes the inline override entirely in that case). A static fallback here would defeat
       that: removing an inline override only reveals a lower-priority declaration for the SAME
       custom property, it doesn't make the property undefined - a :root default would always
       win over "nothing set", permanently painting over Holiday/Weekend/UnderTarget colors
       underneath regardless of the checkbox. Holiday/Weekend/UnderTarget don't have this problem
       since their rules use background-color directly, not var() with a fallback. */
    --color-status-today-border: #2563eb;

    /* Dashboard widgets (donut ring / hours progress bar) - only *-border is read, no background
       (see StatusColorSeeder's WidgetNormal/WidgetWarning/WidgetDanger comment). Static defaults
       here match today's literal --color-primary/--color-status-under-border/--color-danger, so
       this becomes admin-configurable with zero visual change until the site owner edits it. */
    --color-status-widget-normal-border: #2563eb;
    --color-status-widget-warning-border: #f97316;
    --color-status-widget-danger-border: #dc2626;
    /* Przysługuje (outer ring/track border) and Zostało (unfilled track/progress-bar track) - the
       other two legend labels next to Wykorzystano above, also admin-configurable. */
    --color-status-widget-total-border: #94a3b8;
    --color-status-widget-remaining-border: #e2e8f0;

    /* leave-and-absence-management, Faza 3: own-calendar day coloring on Absences.razor - a
       plain static pair, NOT wired into StatusColorState's admin-editable runtime override
       system (that would need its own migration/seeder entry per color, out of scope here). */
    --color-absence-pending-bg: #fef3c7;
    --color-absence-pending-border: #d97706;
    --color-absence-approved-bg: #dcfce7;
    --color-absence-approved-border: #16a34a;

    --radius-sm: 0;
    --radius-md: 0;
    --radius-lg: 0;

    --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
    --shadow-md: 0 4px 16px rgba(15, 23, 42, 0.08);
    --shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.12);

    --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

html {
    /* Every rem-based size in the app (Bootstrap's --bs-*-font-size variables included) scales
       off this - a modest, uniform reduction from the 16px browser default so dense tables fit
       more without needing per-component font-size overrides. */
    font-size: 93.75%;
}

html, body {
    font-family: var(--font-sans);
    color: var(--color-text);
    background-color: var(--color-bg);
}

h1, h2, h3, h4 {
    font-weight: 700;
    letter-spacing: -0.01em;
}

h1:focus {
    outline: none;
}

a, .btn-link {
    color: var(--color-primary);
}

.btn {
    border-radius: var(--radius-sm);
    font-weight: 600;
    /* Every button in the app (.btn-sm, .btn-outline-*, .btn-link, .btn-danger... all carry
       this base class) gets the same height regardless of size/text-length variant - only
       padding-top/bottom is zeroed here, horizontal padding is left to Bootstrap's own
       .btn/.btn-sm rules so width still grows naturally with the label. */
    height: 2.25rem;
    padding-top: 0;
    padding-bottom: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1.2;
}

.btn-primary {
    color: #fff;
    background-color: var(--color-primary);
    border-color: var(--color-primary);
}

    .btn-primary:hover, .btn-primary:focus {
        background-color: var(--color-primary-dark);
        border-color: var(--color-primary-dark);
    }

.btn-outline-secondary {
    color: var(--color-text);
    border-color: var(--color-border);
}

    .btn-outline-secondary:hover {
        background-color: var(--color-bg);
        border-color: var(--color-text-muted);
        color: var(--color-text);
    }

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.2rem var(--color-primary-light);
}

.form-control {
    border-radius: var(--radius-sm);
    border-color: var(--color-border);
}

    .form-control:focus {
        border-color: var(--color-primary);
    }

.form-label {
    font-weight: 600;
    font-size: 0.9rem;
}

.content {
    flex: 1 0 auto;
    padding-top: 1.75rem;
    padding-bottom: 2rem;
    max-width: none;
}

/* --- Landing / login split screen --- */

.landing {
    position: relative;
    overflow: hidden;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem 1rem;
    background:
        radial-gradient(circle at 15% 20%, rgba(59, 130, 246, 0.12) 0%, rgba(59, 130, 246, 0) 55%),
        radial-gradient(circle at 85% 80%, rgba(29, 78, 216, 0.10) 0%, rgba(29, 78, 216, 0) 55%),
        var(--color-bg);
}

/* Big, soft clock illustration sitting BEHIND the login card - decoration, not content. */
.landing-backdrop {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 0;
    pointer-events: none;
}

.clock-illustration {
    width: min(85vw, 620px);
    opacity: 0.4;
}

@media (min-width: 900px) {
    .clock-illustration {
        width: min(70vw, 880px);
    }
}

@media (min-width: 1400px) {
    .clock-illustration {
        width: min(60vw, 1040px);
    }
}

.landing-content {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 26rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.landing-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 700;
    font-size: 1.1rem;
    color: var(--color-text);
    margin-bottom: 1.25rem;
}

.landing-brand-icon {
    width: 1.5rem;
    height: 1.5rem;
    color: var(--color-primary);
}

.landing-tagline-sub {
    color: var(--color-text-muted);
    margin: 1.5rem 0 0;
}

/* --- Auth pages (Login / Register) --- */

.auth-page {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    padding: 2.5rem 1rem;
}

.auth-card {
    width: 100%;
    max-width: 26rem;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    padding: 2rem;
}

/* Floating over the clock backdrop on the landing page - needs to read clearly
   against the illustration behind it, so it's translucent + blurred rather than
   fully opaque, with a stronger shadow to lift it off the background. */
.auth-card-glass {
    background: rgba(255, 255, 255, 0.85);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    box-shadow: var(--shadow-lg);
}

/* --- Shared form-field styling for AuthShell's five pages (Login/Register/ForgotPassword/
   ResetPassword/AcceptInvitation) - global, not scoped to AuthShell.razor.css, because each
   page's form fields are its own ChildContent markup and keep that page's own CSS-isolation
   scope, never AuthShell's (see AuthShell.razor's comment). --- */

/* Used both by AuthShell's own Subtitle parameter (AuthShell's scope) and inline by pages whose
   subtitle needs embedded markup a plain string parameter can't carry, e.g. AcceptInvitation's
   "Dołączasz do zespołu <strong>@Name</strong>" (that page's own scope) - global is the only
   stylesheet both can reach. */
.login-subtitle {
    color: var(--color-text-muted);
    margin-bottom: 2rem;
}

.login-input-wrap {
    position: relative;
}

.login-input-icon {
    position: absolute;
    left: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    color: var(--color-text-muted);
    pointer-events: none;
}

.login-input {
    padding-left: 2.5rem;
}

.login-input-with-toggle {
    padding-right: 3.5rem;
}

.login-toggle-password {
    position: absolute;
    right: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    padding: 0;
    color: var(--color-primary);
    font-size: 0.85rem;
    font-weight: 600;
}

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

.login-forgot {
    font-size: 0.9rem;
}

.login-submit {
    margin-top: 0.25rem;
}

.login-switch {
    margin: 1.5rem 0 0;
    text-align: center;
    color: var(--color-text-muted);
    font-size: 0.9rem;
}

.auth-title {
    font-size: 1.5rem;
    margin-bottom: 0.25rem;
}

.auth-subtitle {
    color: var(--color-text-muted);
    margin-bottom: 1.5rem;
}

.auth-switch {
    margin: 1.25rem 0 0;
    text-align: center;
    color: var(--color-text-muted);
    font-size: 0.9rem;
}

.field-error {
    display: block;
    color: var(--color-danger);
    font-size: 0.825rem;
    margin-top: 0.25rem;
}

/* Marks a required field's <label> - appended after the label text, e.g.
   <label class="form-label">Nazwa<span class="required-marker">*</span></label>. */
.required-marker {
    color: var(--color-danger);
    margin-left: 0.2rem;
}

.alert-danger {
    background-color: var(--color-danger-bg);
    border: 1px solid var(--color-danger-border);
    color: var(--color-danger);
    border-radius: var(--radius-sm);
    padding: 0.65rem 0.9rem;
    font-size: 0.9rem;
}

.alert-success {
    background-color: var(--color-success-bg);
    border: 1px solid var(--color-success-border);
    color: var(--color-success);
    border-radius: var(--radius-sm);
    padding: 0.65rem 0.9rem;
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

/* Zmiany (Changelog.razor) - an item's type pill, sized as an inline table badge, not a full-width
   box. The set of types is admin-defined and open-ended (ChangelogItemType), so this is a single
   neutral style rather than a per-type color pairing. */
.changelog-type-badge {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    background-color: var(--color-bg);
    border: 1px solid var(--color-border);
    color: var(--color-text-muted);
}

.changelog-item-row {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin-bottom: 0.35rem;
}

.changelog-item-row:last-child {
    margin-bottom: 0;
}

.settings-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    padding: 1.75rem;
    max-width: 40rem;
}

.settings-card-wide {
    max-width: none;
}

.settings-card-narrow {
    max-width: 22rem;
}

/* Same grid as .dashboard-stats but with a smaller column floor, for stat rows that must fit
   3+ tiles across a single settings-card (40rem cap) instead of the full page width. */
.dashboard-stats-compact {
    grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
    gap: 0.75rem;
}

    .dashboard-stats-compact .stat-card {
        padding: 1rem 1.1rem;
    }

    .dashboard-stats-compact .stat-card-value {
        font-size: 1.3rem;
    }

/* Shared by both the year and month cards on the Calendar page - wide enough for 4
   month-tiles per row in the year grid, which also happens to fit the day grid's 7
   columns + total column without a horizontal scrollbar. */
.calendar-page-card {
    max-width: 52rem;
}

.settings-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    align-items: start;
    gap: 1.5rem;
}

    .settings-row > .settings-card,
    .settings-row > div > .settings-card {
        max-width: none;
    }

/* Equal-height variant - unlike the default top-aligned .settings-row (used where card heights
   genuinely differ and forcing them equal would just add odd blank space), Integracje's two
   Jira cards read as one connected unit and look better matched in height. */
.settings-row-stretch {
    align-items: stretch;
}

/* FeedbackSettings.razor (poprawki-p1 korekta): Statusy+Typy sit in one .settings-row, Priorytety
   in a second one below it, so the two rows need their own vertical gap - a single .settings-row
   never needed this since nothing followed it directly. */
.settings-row + .settings-row {
    margin-top: 1.5rem;
}

/* Tighter vertical gap when cards wrap to a stack - for a .settings-row whose item count can grow
   arbitrarily long (Dictionaries.razor's list of słowniki), not just the fixed 2-card layouts
   elsewhere. Grid's two-value gap shorthand is row-gap column-gap - only the stacked (row) gap
   shrinks, the side-by-side (column) gap is unchanged. Poprawka na wyraźną prośbę: capped at max
   3 cards per row - minmax()'s lower bound is whichever is bigger, 320px or "the width one card
   would have if there were exactly 3", so auto-fit can never squeeze in a 4th no matter how wide
   the screen (it can still show fewer than 3 on a narrower one). Later in the file than
   .settings-row, so this wins the grid-template-columns tie (same specificity, same class combo). */
.settings-row-compact {
    gap: 7px 1rem;
    grid-template-columns: repeat(auto-fit, minmax(max(320px, calc((100% - 2rem) / 3)), 1fr));
}

/* Feedback.razor's grid - a subtle full-row tint plus a small dot next to the ticket number for
   any report with unread activity (new report, edit, or comment) for the current viewer, same
   "unread" definition as FeedbackUnreadBadge's count. Opening the report's details (which also
   marks it viewed) removes both, no reload needed - see OpenDetailAsync's optimistic update. */
.feedback-row-unread {
    background-color: var(--color-primary-light);
}

.feedback-unread-dot {
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background-color: var(--color-primary);
    margin-right: 0.35rem;
    vertical-align: middle;
}

/* Feedback.razor's private comment thread - a left/right-aligned bubble list distinguishing the
   submitter's messages from the admin's, same idea as a typical two-party chat log. */
.feedback-comment-thread {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    max-height: 18rem;
    overflow-y: auto;
}

.feedback-comment {
    max-width: 85%;
    padding: 0.5rem 0.75rem;
    border-radius: 10px;
    background: var(--color-surface-alt, var(--color-surface));
    border: 1px solid var(--color-border);
}

.feedback-comment-admin {
    align-self: flex-end;
    background: var(--color-primary);
    color: #fff;
    border-color: var(--color-primary);
}

.feedback-comment-meta {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    font-size: 0.75rem;
    opacity: 0.8;
    margin-bottom: 0.2rem;
}

.feedback-comment-text {
    margin: 0;
    white-space: pre-wrap;
}

.settings-card-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1rem;
}

    .settings-card-header h2 {
        margin: 0;
    }

.settings-card-action {
    background: none;
    border: none;
    padding: 0;
    color: var(--color-primary);
    font-weight: 600;
    font-size: 0.9rem;
    cursor: pointer;
    white-space: nowrap;
    /* Same height as .btn (see above) even though this is a bare icon button with no
       border/background of its own - keeps every clickable action the same height app-wide. */
    height: 2.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

    .settings-card-action:hover {
        color: var(--color-primary-dark);
        text-decoration: underline;
    }

.settings-summary {
    margin: 0;
}

.settings-summary-row {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--color-border);
}

    .settings-summary-row:last-child {
        border-bottom: none;
        padding-bottom: 0;
    }

    .settings-summary-row dt {
        color: var(--color-text);
        font-weight: 700;
        font-size: 0.9rem;
    }

    .settings-summary-row dd {
        margin: 0;
        font-weight: 400;
        text-align: left;
    }

.form-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
}

.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    z-index: 1050;
}

.modal-panel {
    background: var(--color-surface);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    width: 100%;
    max-width: 48rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
}

.modal-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.25rem 1.5rem;
    border-bottom: 1px solid var(--color-border);
}

    .modal-panel-header h2 {
        margin: 0;
    }

.modal-panel-header-warning {
    padding: 0.6rem 1.5rem;
    background: var(--color-warning-bg);
    border-bottom-color: var(--color-warning-border);
}

    .modal-panel-header-warning h2 {
        font-size: 1rem;
        color: var(--color-warning);
    }

    .modal-panel-header-warning .modal-close {
        color: var(--color-warning);
    }

        .modal-panel-header-warning .modal-close:hover {
            color: var(--color-text);
        }

.modal-close {
    background: none;
    border: none;
    padding: 0.25rem;
    font-size: 1.5rem;
    line-height: 1;
    color: var(--color-text-muted);
    cursor: pointer;
}

    .modal-close:hover {
        color: var(--color-text);
    }

.modal-panel-body {
    padding: 1.5rem;
}

.modal-panel-confirm {
    max-width: 52rem;
}

/* Wider variant (2026-08-29) for editors that pair a form with a live preview side-by-side
   (email template/footer HTML editors) - the default 48rem is comfortable for a single-column
   form but cramped once a preview pane sits next to it. */
.modal-panel-wide {
    max-width: 64rem;
}

.modal-split {
    display: flex;
    gap: 1.5rem;
    align-items: flex-start;
}

    .modal-split-form,
    .modal-split-preview {
        flex: 1 1 50%;
        min-width: 0;
    }

    /* Sticks to the top of the modal's own scroll area while the (usually longer) form scrolls
       past it - same position:sticky idiom as .table-scroll's header, just vertical instead of
       within a table. */
    .modal-split-preview {
        position: sticky;
        top: 0;
    }

.modal-split-preview-frame {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 1rem;
    background: var(--color-bg);
    max-height: 32rem;
    overflow-y: auto;
    font-size: 0.9rem;
}

    /* The previewed HTML is arbitrary admin-authored content (email body/footer) - reset its
       own margins so it doesn't inherit spacing meant for this app's own UI text. */
    .modal-split-preview-frame img {
        max-width: 100%;
    }

@media (max-width: 900px) {
    .modal-split {
        flex-direction: column;
    }

    .modal-split-preview {
        position: static;
        width: 100%;
    }
}

.day-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}

/* poprawki-p1, Faza 2 (korekta, generalized in the second korekta for per-type inline absence
   colors): when a holiday/weekend class OR an inline absence background tints an entire
   settings-card (Registration.razor's day header) rather than a small calendar cell, the day-nav
   buttons and their icons must keep their normal look - Bootstrap's outline buttons are
   background:transparent by default, so the card's tint would otherwise bleed through and make
   them look colored too. Scoped to .day-nav (not every .btn in the card, e.g. table row actions
   further down) since only this row sits directly on the tinted card background.
   :not(:hover):not(:focus) matters: without it this always-on background sits later in the
   cascade than .btn-outline-primary/.btn-outline-secondary's own :hover fill (same specificity,
   later source order wins) and silently cancels it - "Dodaj wpis" stopped changing color on
   hover/focus while the same button elsewhere (outside .day-nav) still did. */
.day-nav .btn:not(:hover):not(:focus) {
    background-color: var(--color-surface);
}

.day-nav-current {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/* Poprawki P2, Faza 3: holiday/weekend/absence used to tint the WHOLE day card's background,
   which read as "tandetne" (cheap-looking) per explicit feedback. Only the day-nav bar itself is
   colored, with the day-off message centered in it and the absence label pinned to its right
   edge. 2026-09-30 (explicit request): the bar's background now matches the day's colour in the
   Absences/Calendar grids for every day-kind - holiday/weekend through the same shared
   .calendar-cell-holiday/.calendar-cell-weekend classes, absence via an inline per-type colour
   (see Registration.razor's DayStatusBarStyle), all at 50% opacity (explicit request) - mixed
   into transparent rather than `opacity`, so only the fill fades, not the text or border.
   Poprawka na wyraźną prośbę: the border stays a constant 1px, matching every other section's own
   border, on every day alike - only its COLOR changes, never its width. */
.registration-day-status-bar {
    position: relative;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 0.4rem 0.75rem;
}

.registration-day-status-bar.calendar-cell-holiday {
    background: color-mix(in srgb, var(--color-status-holiday-bg) 50%, transparent);
    border-color: var(--color-status-holiday-border);
}

.registration-day-status-bar.calendar-cell-weekend {
    background: color-mix(in srgb, var(--color-status-weekend-bg) 50%, transparent);
    border-color: var(--color-status-weekend-border);
}

.registration-day-off-label {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    font-size: 0.75rem;
    font-style: italic;
    color: var(--color-text);
    white-space: nowrap;
    pointer-events: none;
    padding: 0 0.5rem;
}

.registration-absence-label {
    position: absolute;
    right: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.75rem;
    font-weight: 700;
    padding: 0.15rem 0.55rem;
    border: 1.5px solid currentColor;
    border-radius: 999px;
    white-space: nowrap;
    background: var(--color-surface);
    pointer-events: none;
}

.calendar-scroll {
    overflow-x: auto;
}

.calendar-month-grid {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 46rem;
}

.calendar-month-header {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr)) 4rem;
    gap: 0.5rem;
    font-size: 0.8rem;
    color: var(--color-text-muted);
    text-align: center;
    font-weight: 600;
}

.calendar-month-row {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr)) 4rem;
    gap: 0.5rem;
}

/* Poprawki P2, Faza 4: back to auto-fit/wrap (the layout this had before the fixed-12-column
   change above was written) - on a narrower viewport the fixed 12 columns squeezed every month
   tile down to an unreadable sliver instead of wrapping to a second/third row. minmax(5.5rem, 1fr)
   keeps all 12 in one row whenever the container is wide enough (unchanged from before), and only
   wraps once tiles would otherwise get narrower than that. */
.calendar-year-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(5.5rem, 1fr));
    gap: 0.5rem;
}

.calendar-cell {
    border: 1px solid var(--color-border);
    padding: 0.6rem 0.5rem;
    text-align: center;
    cursor: pointer;
    background: var(--color-surface);
    /* Anchors the Jira sync-status dot / checkbox / error mark (all absolutely positioned) -
       these can appear on any cell, not just "today". */
    position: relative;
}

    .calendar-cell:hover {
        border-color: var(--color-primary);
    }

.calendar-cell-sm {
    padding: 0.7rem 0.5rem;
    font-size: 0.95rem;
}

/* Punkt 1 (2026-08-29): ~30% shorter than the original 1rem vertical padding - the year overview
   above the month grid was taking up more vertical space than its content (month name + total
   hours) needed. */
.calendar-cell-month {
    padding: 0.5rem 0.75rem;
}

.calendar-cell-label {
    font-weight: 600;
    font-size: 0.75rem;
    color: var(--color-text-muted);
}

.calendar-cell-hours {
    font-weight: 700;
    margin-top: 0.1rem;
}

/* The absence short label as a WATERMARK: large text filling the cell, faint, behind everything
   else. This is the third attempt at placing it and the first that doesn't have to steal space
   from something - as a badge it collided with the Jira checkbox in the top-right, and moving it
   to the bottom-right forced the duration off-centre to fit beside it. Sitting behind the content
   it competes with nothing, so the cell keeps its natural height and the duration stays centred.
   z-index 0 against z-index 1 on the date/duration is what guarantees it never covers them (an
   absolutely positioned element otherwise paints over static siblings regardless of DOM order).
   Colour comes inline from the absence type; opacity is fixed here so an admin can't accidentally
   pick something opaque enough to bury the numbers. */
.calendar-cell-absence-watermark {
    position: absolute;
    inset: 0;
    z-index: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /* 2.9rem is the size asked for, but it only fits while the cells are wide: the month grid is
       repeat(auto-fit, minmax(5.5rem, 1fr)), so a narrow window squeezes a cell down towards 88px -
       and three letters at 2.9rem run about that wide on their own. clamp() scales the label down
       with the viewport instead of letting overflow:hidden clip it, and caps it at the requested
       2.9rem on any window wide enough to hold it (roughly 1290px up). */
    font-size: clamp(1.7rem, 3.6vw, 2.9rem);
    font-weight: 800;
    letter-spacing: 0.03em;
    line-height: 1;
    /* Deliberately very faint (0.05): earlier passes at 0.16 and 0.11 still tangled with the date
       and the duration, which are centred in the same box. At this size the letters read as a tint
       behind the numbers rather than as text competing with them. */
    opacity: 0.05;
    overflow: hidden;
    pointer-events: none;
    user-select: none;
}

    .calendar-cell-label,
    .calendar-cell-hours {
        position: relative;
        z-index: 1;
    }

/* A still-Pending absence request, on top of its type's own background color (set inline) -
   the color alone no longer distinguishes pending from approved, so this dashed outline is the
   one remaining "not decided yet" signal. */
.calendar-cell-absence-pending-outline {
    outline: 2px dashed var(--color-primary);
    outline-offset: -2px;
}

.calendar-cell-muted {
    opacity: 0.45;
}

/* Compound selector (.calendar-cell + .calendar-cell-today), not just .calendar-cell-today
   alone - gives this a higher specificity than .calendar-cell-holiday/-weekend/-under so "today"
   always wins the background even when a day is also a holiday/weekend, regardless of class
   order in the markup or declaration order here. A plain 1px border+ring used to be the only
   signal, easy to miss against an already-colored/bordered cell - background + thicker border +
   corner dot make it unambiguous at a glance. */
.calendar-cell.calendar-cell-today {
    border-color: var(--color-status-today-border);
    border-width: 2px;
    /* An inset box-shadow paints the fill instead of the background-color property - a
       DIFFERENT property than .calendar-cell-holiday/-weekend/-under use, so this never has to
       win a cascade fight to be visible over them (both apply, painted on top of each other).
       It also means "no background configured for Today" (admin unchecked it in Kolory, the
       CSS var gets removed entirely - see StatusColorState.ApplyAsync) needs no special-casing
       here: var(...) with no fallback and no value resolves to transparent, which is exactly
       "paint nothing, let the category color underneath show through". */
    box-shadow: inset 0 0 0 999px var(--color-status-today-bg, transparent);
}

/* The year grid's "which month am I currently drilled into" indicator - a DIFFERENT thing
   from calendar-cell-today (which means "this is the real, literal today"). The two used to
   share one class, which meant every month tile you clicked into displayed as "today" even in
   December while the real today was in August - kept distinct on purpose, and can both apply
   to the same cell (default view, anchor == today). */
.calendar-cell-selected {
    border-color: var(--color-primary);
    border-width: 2px;
}

/* Deliberately a stronger, more saturated orange than --color-warning-bg (used elsewhere
   for gentler inline form warnings) - this one flags a missed daily target and needs to
   read as an alert at a glance, not blend in as pale yellow. */
.calendar-cell-under {
    background: var(--color-status-under-bg);
    border-color: var(--color-status-under-border);
}

.calendar-cell-holiday {
    background: var(--color-status-holiday-bg);
    border-color: var(--color-status-holiday-border);
}

.calendar-cell-weekend {
    background: var(--color-status-weekend-bg);
    border-color: var(--color-status-weekend-border);
}

/* A future Approved day the user has clicked, staged for revocation on the next "Odwołaj
   zaznaczone" click - a dashed override ring so it stays legible against the per-type color
   applied inline underneath (Absences.razor's DayStyle/Calendar.razor's AbsenceCellStyle). */
.calendar-cell-absence-revoke-selected {
    outline: 2px dashed var(--color-danger);
    outline-offset: -2px;
}

/* A day already submitted for revocation and awaiting Owner/Admin's decision - a distinct
   (blue-ish) dashed ring from the red one above, which means "locally selected, not yet sent". */
.calendar-cell-absence-revoke-pending {
    outline: 2px dashed var(--color-primary);
    outline-offset: -2px;
}

/* Click-to-select date range for a new request (Absences.razor SelectRangeDay) - distinct blue
   from the pending/approved colors above, since this is a not-yet-submitted preview, not an
   actual request state. */
.absence-range-selected {
    background: var(--color-primary-light);
    border-color: var(--color-primary);
}

/* Jira sync status renders as a small corner marker, NOT the cell background - unlike
   Holiday/Weekend/UnderTarget, it must coexist with whichever of those already applies to the
   same day without a priority/z-order fight (see plan.md Krytyczne szczegóły #6). Top-left -
   .calendar-cell-today no longer paints a dot (border + background are enough), so this and the
   checkbox (top-right) are free to claim the top corners. */
.calendar-cell.calendar-cell-jira-not-synced::before,
.calendar-cell.calendar-cell-jira-synced::before,
.calendar-cell.calendar-cell-jira-changed::before,
.calendar-cell.calendar-cell-jira-none::before {
    content: "";
    position: absolute;
    top: 4px;
    left: 4px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}

.calendar-cell.calendar-cell-jira-not-synced::before {
    background: var(--color-status-jira-not-synced-border);
}

.calendar-cell.calendar-cell-jira-synced::before {
    background: var(--color-status-jira-synced-border);
}

.calendar-cell.calendar-cell-jira-changed::before {
    background: var(--color-status-jira-changed-border);
}

/* Neutral "nothing to sync today" state, same rationale as .calendar-legend-swatch-jira-none -
   not an admin-editable StatusColorCategory, just an absence of anything to sync. */
.calendar-cell.calendar-cell-jira-none::before {
    background: var(--color-text-muted);
}

/* The error mark is a real DOM element, not a pseudo-element (::before is already used above) -
   only rendered when the day's last sync attempt failed, independent of the 3-way color above
   (see JiraDaySyncResolver: a day can be "not yet synced" AND "last attempt failed" at once).
   Bottom-right - the sync-status dot (top-left) and checkbox (top-right) already claim both top
   corners, and the absence label is in the normal flow now, so both bottom corners are free. */
.calendar-cell-jira-error {
    position: absolute;
    bottom: 4px;
    right: 4px;
    color: var(--color-danger-border);
    line-height: 0;
}

/* Top-right - always shown for any day with at least one ticket-bearing entry, regardless of
   its current sync status (the user can re-sync or re-check an already-synced day too). */
.calendar-cell-jira-select {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 14px;
    height: 14px;
    margin: 0;
    cursor: pointer;
}

.calendar-week-total {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-text-muted);
    border: 1px dashed var(--color-border);
    padding: 0.45rem 0.3rem;
}

.calendar-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.5rem;
}

.calendar-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

.calendar-legend-swatch {
    display: inline-block;
    width: 1.1rem;
    height: 1.1rem;
    border-radius: 3px;
    background: var(--color-status-under-bg);
    border: 1px solid var(--color-status-under-border);
    flex-shrink: 0;
}

.calendar-legend-swatch-holiday {
    background: var(--color-status-holiday-bg);
    border-color: var(--color-status-holiday-border);
}

.calendar-legend-swatch-weekend {
    background: var(--color-status-weekend-bg);
    border-color: var(--color-status-weekend-border);
}

.calendar-legend-swatch-today {
    background: var(--color-status-today-bg);
    border-color: var(--color-status-today-border);
}

/* Jira statuses render as a dot on the calendar (not a filled cell background, see
   .calendar-cell-jira-*::before above) - the legend swatch matches that shape, a small filled
   circle in the marker's own color, rather than the square bg+border swatch used above. */
.calendar-legend-swatch-dot {
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 50%;
    border: none;
}

.calendar-legend-swatch-jira-not-synced {
    background: var(--color-status-jira-not-synced-border);
}

.calendar-legend-swatch-jira-synced {
    background: var(--color-status-jira-synced-border);
}

.calendar-legend-swatch-jira-changed {
    background: var(--color-status-jira-changed-border);
}

/* Neutral "nothing to sync today" state (Rejestracja, when the viewed day has no ticket-bearing
   entry) - deliberately NOT one of the admin-editable StatusColorCategory colors, since it isn't
   a sync outcome at all, just an absence of anything to sync. */
.calendar-legend-swatch-jira-none {
    background: var(--color-text-muted);
}

/* Absences.razor's year overview: 12 actual mini month-calendars (individual day cells), not
   Calendar.razor's 12 month-rollup tiles - the shaping session asked for "12 miniaturowych
   miesięcy z dniami" specifically, a different, more detailed layout. */
.absence-year-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 1rem;
}

.absence-mini-month {
    border: 1px solid var(--color-border);
    padding: 0.6rem;
}

.absence-mini-month-title {
    font-weight: 600;
    font-size: 0.9rem;
    margin-bottom: 0.4rem;
    text-align: center;
}

.absence-mini-month-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}

.absence-mini-day-header {
    font-size: 0.65rem;
    text-align: center;
    color: var(--color-text-muted);
    font-weight: 600;
}

.absence-mini-day {
    font-size: 0.7rem;
    text-align: center;
    padding: 0.25rem 0;
    border-radius: 2px;
    cursor: default;
}

.absence-mini-day-muted {
    opacity: 0.35;
}

.absence-mini-day-clickable {
    cursor: pointer;
}

.absence-mini-day.calendar-cell-today {
    border: 1px solid var(--color-status-today-border);
}

/* Historia wniosków row/status coloring - the app's own warning/danger palette (not Bootstrap's
   default .table-warning/.table-danger), so it matches .alert-danger's tone elsewhere. */
.absence-row-pending {
    background-color: var(--color-warning-bg);
}

.absence-row-rejected {
    background-color: var(--color-danger-bg);
}

.absence-status-pending {
    color: var(--color-warning);
    font-weight: 600;
}

.absence-status-rejected {
    color: var(--color-danger);
    font-weight: 600;
}

/* Zarządzanie > Wnioski (and any other filtered grid): the grid scrolls within its own box
   instead of stretching the whole page - both axes, since a filter-heavy table can get wide on
   narrow viewports too. Bordered box (flat corners, like every other surface in the app - see
   --radius-sm/md/lg all 0 in :root) so the grid reads as one self-contained unit instead of a
   bare table floating on the page. */
.table-scroll {
    overflow: auto;
    /* Capped against viewport height too (not just a flat rem value) - on shorter windows the
       table shrinks instead of pushing the app footer far below the fold, same rationale as
       Konfiguracja's .ticket-shortcut-list-page. */
    max-height: min(32rem, 45vh);
    border: 1px solid var(--color-border);
}

    .table-scroll table {
        margin-bottom: 0;
        /* Required for position:sticky <th> to render reliably across browsers (Safari in
           particular fails to stick header cells under the default border-collapse: collapse
           some resets apply to <table>) - punkt 1, 2026-08-29, fixes scrolled rows briefly
           painting over/through the header instead of disappearing behind it. */
        border-collapse: separate;
        border-spacing: 0;
    }

    /* Sticky header: only the body scrolls underneath, the header row stays put - position:sticky
       on each <th> (not on <thead>/<tr>) is the reliable cross-browser way to do this inside a
       scrolling ancestor. Needs its own opaque background (can't inherit transparency) so scrolled
       rows don't show through underneath it, and a visible accent so the header actually reads as
       "the header" instead of just another row - a bottom border in the brand color plus small
       bold uppercase text, the same idiom PolishDatePicker/settings-tab already use for emphasis.
       z-index bumped above the zebra/hover row backgrounds below, not just above plain rows. */
    .table-scroll thead th {
        position: sticky;
        top: 0;
        z-index: 2;
        background: var(--color-bg);
        border-bottom: 2px solid var(--color-primary);
        font-size: 0.75rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: var(--color-text-muted);
    }

    /* Bootstrap sizes .form-check-input in em, so a checkbox in the smaller header text above came
       out a different size than the ones in the rows - a checkbox-only header cell takes the
       table's own font size instead, whatever that is for this grid. */
    .table-scroll thead th.table-col-checkbox {
        font-size: inherit;
    }

/* Taller variant (punkt 1, 2026-08-29) for grids whose content is worth showing more of at once
   (Kolory's fixed 12-category list) - stacks with .table-scroll, doesn't replace it. */
.table-scroll-tall {
    max-height: min(48rem, 65vh);
}

/* Unsaved-change row highlight (punkt 1, 2026-08-29) - same idiom as .absence-row-pending
   (a whole-row background tint), subtle enough not to look like an error/warning state. */
.row-unsaved {
    background-color: var(--color-primary-light);
}

/* datagrid-component: DataGrid's scrollbar starts BELOW the sticky header row instead of running
   over it (explicit request) - the vertical track gets a top margin equal to the measured header
   height (--data-grid-header-height, set by DataGrid.razor.js). Custom thin styling is required
   for the track margin to apply at all (Chromium/Safari ::-webkit-scrollbar); Firefox ignores
   these and keeps its default full-height scrollbar. Never combine with scrollbar-width/-color on
   this element - in Chromium those standard properties switch the ::-webkit-scrollbar rules off. */
/* The strip beside the header where the scrollbar no longer runs would otherwise show the box's
   plain background - paint the header's own look there (its background + the 2px primary bottom
   border from `.table-scroll thead th`), so the header visually extends over the scrollbar's
   width. A container background doesn't scroll with the content (default attachment), so this
   strip stays put exactly like the sticky header; everywhere else it's transparent. With no
   sticky header the variable is 0px and the strip has no height. */
.data-grid-scroll {
    background-image: linear-gradient(to bottom,
        var(--color-bg) calc(var(--data-grid-header-height, 0px) - 2px),
        var(--color-primary) calc(var(--data-grid-header-height, 0px) - 2px),
        var(--color-primary) var(--data-grid-header-height, 0px),
        transparent var(--data-grid-header-height, 0px));
    background-repeat: no-repeat;
}

.data-grid-scroll::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

.data-grid-scroll::-webkit-scrollbar-track {
    background: transparent;
}

.data-grid-scroll::-webkit-scrollbar-track:vertical {
    margin-top: var(--data-grid-header-height, 0px);
}

.data-grid-scroll::-webkit-scrollbar-thumb {
    background-color: var(--color-border);
    border: 2px solid transparent;
    border-radius: 5px;
    background-clip: padding-box;
}

    .data-grid-scroll::-webkit-scrollbar-thumb:hover {
        background-color: var(--color-text-muted);
    }

.data-grid-scroll::-webkit-scrollbar-corner {
    background: transparent;
}

/* datagrid-component: DataGrid's footer - the "Wyświetlono X z Y" counter always in the grid's
   bottom-right corner, with optional footer content (e.g. AbsenceRequests' paging) on its left. */
.data-grid-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem 1rem;
    margin-top: 0.5rem;
    font-size: 0.8rem;
    color: var(--color-text-muted);
}

/* Deliberately smaller than the grid's own 0.85rem text and slightly faded - an at-a-glance hint,
   not something that should compete with the rows for attention (explicit request). */
.data-grid-count {
    margin-left: auto;
    white-space: nowrap;
    font-size: 0.7rem;
    opacity: 0.8;
}

.table-sortable {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}

    .table-sortable:hover {
        color: var(--color-primary);
    }

/* Row hover (any table, not just scrolling ones) - a faint highlight makes it easier to track a
   row across many columns without relying on the zebra stripe alone. Uses the same light-blue
   tint as .form-control:focus's box-shadow color, so it reads as "this app's accent", not a new
   ad-hoc color. */
.table tbody tr:hover {
    background-color: var(--color-primary-light);
}

/* --- Dashboard --- */

.widget-holidays {
    padding: 1.25rem 1.5rem;
}

.widget-holidays-scroll {
    max-height: 15rem;
    overflow-y: auto;
}

.dashboard-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.stat-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    padding: 1.25rem 1.5rem;
    /* Column layout so .stat-progress can pin itself to the card's bottom edge (margin-top:
       auto) instead of just trailing whatever content happens to sit above it. */
    display: flex;
    flex-direction: column;
}

.stat-card-label {
    font-size: 0.85rem;
    line-height: 1.3;
    color: var(--color-text-muted);
    font-weight: 600;
    /* Reserve room for 2 lines even when this card's own label happens to fit on 1 - siblings
       in the same row have labels of very different lengths, and without this, .stat-card-value
       started at a different height in each card depending on how many lines its label wrapped
       to at the current width. */
    min-height: calc(1.3em * 2);
}

.stat-card-value {
    font-size: 1.75rem;
    font-weight: 700;
    margin-top: 0.25rem;
}

.stat-card-sub {
    font-size: 0.8rem;
    color: var(--color-text-muted);
    margin-top: 0.25rem;
    margin-bottom: 0.5rem;
}

/* Subtle "how much is left" preview inline next to a .stat-card-value - deliberately smaller and
   more muted than .stat-card-sub, since this is a nice-to-glance-at extra, not the primary
   figure the widget already reports. */
.stat-card-remaining {
    font-size: 0.7rem;
    font-weight: 400;
    color: var(--color-text-muted);
    margin-left: 0.4rem;
}

/* Track (unfilled) uses the same Zostało/WidgetRemaining color as the donut's track, and the
   border uses the same Przysługuje/WidgetTotal color as the donut's outer ring - consistent
   admin-configurable palette between the hours widget's bar and the absence donuts. */
.stat-progress {
    height: 0.4rem;
    background: var(--color-status-widget-remaining-border);
    border: 1px solid var(--color-status-widget-total-border);
    /* auto pins the bar to the card's bottom edge (see .stat-card's flex-direction: column) so
       every progress bar in a row lines up regardless of how tall the content above it is -
       still at least 0.5rem below whatever precedes it, since margin-top: auto never collapses
       below the sibling's own margin-bottom. */
    margin-top: auto;
    overflow: hidden;
}

/* leave-and-absence-management, Faza 4 (feedback z ręcznej weryfikacji): a donut-chart widget
   for each configured absence type - conic-gradient ring (no chart library needed) showing
   used-vs-total proportion, with a legend spelling out przysługuje/wykorzystano/zostało instead
   of a wall of plain numbers. */
/* Shared by every stat-card that carries an InfoPopoverButton (Dashboard's absence-limit widgets
   and the overtime widget) - position:relative anchors the icon below, and the label gets out of
   its way. Generically named on purpose: this has nothing to do with absence specifically, it's
   just "a stat-card with an info icon in its corner". */
.stat-card-with-info-icon {
    position: relative;
}

.stat-card-with-info-icon .stat-card-label {
    padding-right: 1.8rem;
}

/* InfoPopoverButton.razor's icon - always a <button> (a click toggle, not a hover title=, since
   a native title= never responds to a click/tap at all - the bug this whole thing replaced).
   Resets the browser's native <button> chrome (background/border/padding/font) on top of the
   plain visual bits (color/position/size/cursor). */
.info-popover-icon {
    position: absolute;
    top: 0.9rem;
    right: 0.9rem;
    color: var(--color-primary);
    display: inline-flex;
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    cursor: pointer;
}

/* Sits right after a section heading/checkbox label in normal text flow instead of a card's
   top-right corner - used to collapse the explanatory paragraph that used to sit below a form
   field into a click-to-reveal bubble, so the form itself stays compact. */
.info-popover-icon-inline {
    position: static;
    margin-left: 0.35rem;
    vertical-align: middle;
}

/* Feedback z ręcznej weryfikacji: "2 dni zostało" alone doesn't say whether that resets weekly,
   monthly or yearly - this pill next to the widget title spells out the period. */
.absence-widget-period-badge {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.55rem;
    border-radius: 999px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    color: var(--color-text-muted);
    font-size: 0.7rem;
    font-weight: 600;
    vertical-align: middle;
}

.absence-widget-body {
    display: flex;
    align-items: center;
    gap: 1.1rem;
    margin-top: 0.85rem;
    flex-wrap: wrap;
}

/* Three colors, all admin-configurable and matching the legend 1:1: outer ring border =
   Przysługuje (WidgetTotal), filled arc = Wykorzystano (ringColor - Normal/Warning/Danger
   depending on state), unfilled track = Zostało (WidgetRemaining). */
.absence-donut {
    --pct: 0;
    --ring-color: var(--color-status-widget-normal-border);
    box-sizing: border-box;
    width: 5.5rem;
    height: 5.5rem;
    flex-shrink: 0;
    border-radius: 50%;
    border: 3px solid var(--color-status-widget-total-border);
    background: conic-gradient(var(--ring-color) calc(var(--pct) * 1%), var(--color-status-widget-remaining-border) 0);
    display: flex;
    align-items: center;
    justify-content: center;
}

.absence-donut-hole {
    width: 3.9rem;
    height: 3.9rem;
    border-radius: 50%;
    background: var(--color-surface);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0.2rem;
}

.absence-donut-value {
    font-size: 1.1rem;
    font-weight: 700;
    line-height: 1.15;
}

.absence-donut-unit {
    font-size: 0.62rem;
    color: var(--color-text-muted);
    line-height: 1.15;
}

.absence-widget-legend {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    font-size: 0.8rem;
    flex: 1;
    min-width: 8rem;
}

.absence-legend-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.absence-legend-swatch {
    width: 0.65rem;
    height: 0.65rem;
    border-radius: 50%;
    flex-shrink: 0;
}

.stat-progress-bar {
    height: 100%;
    transition: width 0.2s ease;
}

.bar-chart {
    display: flex;
    align-items: flex-end;
    gap: 0.75rem;
    height: 10rem;
    margin-top: 1rem;
}

.bar-chart-col {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
}

.bar-chart-bar-track {
    flex: 1;
    width: 100%;
    display: flex;
    align-items: flex-end;
    justify-content: center;
}

.bar-chart-bar {
    width: 60%;
    min-height: 2px;
    transition: height 0.2s ease;
}

.bar-chart-label {
    font-size: 0.75rem;
    color: var(--color-text-muted);
    margin-top: 0.4rem;
    text-align: center;
}

.bar-chart-value {
    font-size: 0.75rem;
    font-weight: 600;
    margin-top: 0.15rem;
}

/* Dashboard's yearly heatmap for an unlimited absence type (e.g. L4) - 12 month tiles (6x2, not
   day-level: 53 week-columns turned out too cramped to read in a narrow dashboard tile) shaded by
   that month's day count relative to this type's own busiest month, with the count printed on any
   non-zero tile. .absence-month-cell-fill is a separate layer under the number (not opacity on
   the whole tile) so the text itself never fades along with a light tint. */
.absence-month-heatmap {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    grid-template-rows: repeat(2, 1fr);
    gap: 0.2rem;
    height: 3rem;
    margin-top: 0.75rem;
}

.absence-month-cell {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-border);
    overflow: hidden;
}

.absence-month-cell-fill {
    position: absolute;
    inset: 0;
    background: var(--color-status-widget-normal-border);
}

.absence-month-cell-value {
    position: relative;
    z-index: 1;
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--color-text);
}

.ticket-breakdown-row {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    font-size: 0.85rem;
    padding: 0.25rem 0;
    border-bottom: 1px solid var(--color-border);
}

    .ticket-breakdown-row:last-child {
        border-bottom: none;
    }

.dashboard-link-card {
    display: block;
    text-decoration: none;
    color: inherit;
    transition: border-color 0.15s ease;
}

    .dashboard-link-card:hover {
        border-color: var(--color-primary);
    }

/* .pl-date-picker* styles moved to Shared/PolishDatePicker.razor.css (Blazor CSS isolation) -
   scoped to that component only, so it can be copied to another app as a self-contained pair
   of files instead of needing global CSS edits here. */

.settings-tabs {
    display: flex;
    gap: 0.5rem;
    border-bottom: 1px solid var(--color-border);
    margin-bottom: 1.5rem;
}

/* Registration.razor's Skróty/Notatki tabs only - a right-aligned actions area (font-size/expand,
   see .ticket-shortcut-toolbar-shared) on the SAME row as the tab buttons, not a plain flex-start
   row. Scoped to this modifier so Configuration.razor's own (non-actions) .settings-tabs is
   unaffected. */
.settings-tabs-with-actions {
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    row-gap: 0.5rem;
}

.settings-tabs-buttons {
    display: flex;
    gap: 0.5rem;
}

.settings-tab {
    background: none;
    border: none;
    padding: 0.6rem 0.25rem;
    margin-right: 1rem;
    font-weight: 600;
    color: var(--color-text-muted);
    border-bottom: 2px solid transparent;
    cursor: pointer;
}

    .settings-tab.active {
        color: var(--color-primary);
        border-bottom-color: var(--color-primary);
    }

.form-select {
    border-radius: var(--radius-sm);
    border-color: var(--color-border);
}

/* Section header + "Dodaj" button layout convention (2026-08-29, punkt 1): the button always
   sits on its own row below the header (and below the description, if the section has one) -
   never sharing a row with the header via space-between, which packed it too close to
   filters/grid below and made the header/button feel glued together. Right-aligned to match
   where these buttons already sat when they were still inline with the header. */
.section-toolbar {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 0.75rem;
    margin-bottom: 10px;
}

/* Shared filter convention (punkt 6, notes-ux-dashboard-consistency follow-up; poprawki-p1
   korekta: label-beside-input per user-provided reference screenshot, but responsive instead of
   the screenshot's fixed two columns): every list-filter bar in the app (Team.razor,
   AbsenceRequests.razor, EmailTemplates.razor, Permissions.razor, SystemAnnouncements.razor,
   Configuration.razor's Dodatkowe kwoty) uses this same trio - a visually bounded panel, label
   immediately beside its field, fields wrap onto their own additional row(s) if they don't fit,
   and the action buttons (Wyczyść filtr/Filtruj) always sit on their own row below the fields,
   right-aligned - never inline with the fields themselves. */
.filter-panel {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
}

/* poprawki-p1 (korekta): reverted from the earlier auto-fit-grid-of-column-pairs + subgrid
   approach - `repeat(auto-fit, minmax(0, max-content) ...)` is a known CSS Grid footgun: the
   spec computes the auto-fit REPEAT COUNT using each track's max sizing function if it's a
   definite length, else its min - and max-content isn't a definite length, so the label track
   counts as 0-width for that computation. The browser then decides on more label/input pairs
   than actually fit once real (non-zero) label widths are laid out, so the row overflows and
   fields get squeezed/overlap or are pushed out of view entirely. Plain flex-wrap has none of
   that: each .filter-field sizes itself to its own content and wraps as a whole unit, so nothing
   can silently miscalculate and disappear. The tradeoff is that labels in different wrapped rows
   no longer share an identical column width - acceptable; a working, simple layout beats a
   fragile, clever one. */
.filter-fields {
    display: flex;
    flex-wrap: wrap;
    column-gap: 1.25rem;
    row-gap: 0.6rem;
    align-items: center;
}

/* Label immediately beside its own input, both as a single inline unit that wraps as a whole -
   works the same whether the field is label-then-input (search/select fields) or the reversed
   input-then-label order used by checkbox fields, so no separate checkbox case is needed. */
.filter-field {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

    .filter-field .form-label {
        margin-bottom: 0;
        white-space: nowrap;
    }

.filter-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
}

.form-control-narrow {
    max-width: 10rem;
}

.input-group-narrow {
    max-width: 12.5rem;
}

.set-now-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 0.6rem;
    color: var(--color-text-muted);
}

.set-now-btn:hover {
    color: var(--color-primary);
}

.table {
    font-size: 0.85rem;
}

/* Shrink-to-fit column (2026-08-29, Rejestracja Godz. od/Godz. do/Czas) - width:1% makes a table
   column take only the space its content needs (the browser distributes the rest to columns
   without it), white-space:nowrap stops the short time/duration values from wrapping and
   defeating that. Frees up room for Typ/Opis, which actually need it. */
.table-col-fit {
    width: 1%;
    white-space: nowrap;
}

/* Poprawka na wyraźną prośbę: a table-col-fit column sitting right next to a normal (non-icon)
   column - e.g. Miesiąc/Kwota do FV, Data/Nazwa - read as "too close" with just Bootstrap's own
   cell padding (0.25rem on a .table-sm, 0.5rem otherwise). Ties Bootstrap's own padding rule on
   specificity (one class + one :not()), so this wins on source order (app.css loads after
   bootstrap.css) without needing !important. Icon-action columns never carry table-col-fit (they
   rely on the :last-child rule below instead), so this never affects them. */
td.table-col-fit,
th.table-col-fit {
    padding-right: 3rem;
}

/* Poprawki P2, Faza 0: global fix for "empty space after the action icons" - every grid in the
   app follows the same convention (trailing header-less column holding the row's action
   buttons), so a single last-child rule shrinks that column to its content and pushes it flush
   right instead of hand-adding .table-col-fit to every grid's action column individually. */
.table th:last-child,
.table td:last-child {
    width: 1%;
    white-space: nowrap;
    text-align: right;
}

/* Poprawki P2: opt-out for a grid whose last column is real data (e.g. Billing.razor's "Kwota do
   FV"/"Opis"), not action icons - the rule above wrongly shrank+right-aligned+nowrapped it too,
   throwing off every other column's width (a numeric column ended up abnormally wide because the
   real last column stole the "shrink to content" treatment meant for icons). Higher specificity
   (two classes + element vs. the rule above's one class + element) wins without needing !important. */
.table th.table-col-normal:last-child,
.table td.table-col-normal:last-child {
    width: auto;
    white-space: normal;
    text-align: left;
}

/* Poprawka na wyraźną prośbę: Rozliczenia - te tabele nie są duże, więc równe, proporcjonalne
   kolumny wyglądają lepiej niż dopasowane do treści. table-layout:fixed z każdą kolumną bez
   jawnej szerokości dzieli tabelę na równe części automatycznie. Neutralizuje też last-child'ową
   regułę powyżej (ta wymuszałaby węższą, przyklejoną-do-prawej ostatnią kolumnę - tu ma być tej
   samej szerokości i wyrównania co pozostałe, zwykłe dane, np. "Opis"). Kolumna "Dodatkowe koszty"
   (sama ikonka) dostaje wyśrodkowanie osobno, przez zwykłe Bootstrapowe .text-center na komórce -
   to jedyna kolumna, dla której środek ma sens. */
.table.table-cols-equal {
    table-layout: fixed;
}

    .table.table-cols-equal th:last-child,
    .table.table-cols-equal td:last-child {
        width: auto;
        white-space: normal;
        text-align: left;
    }

/* Zebra striping (shared <SortableHeader>/<FilterPanel> extraction, punkt 2; contrast bumped
   punkt 2, 2026-08-29 per explicit feedback that --color-bg read as too subtle) - every odd data
   row stays plain white (the card's own background), every even row gets a clearly-visible gray
   tint. Reuses --color-border (already the app's "visible but not loud" gray, used for card/
   input borders) instead of introducing a new color. Excludes rows that already carry their own
   status color (Absences.razor's .absence-row-pending/-rejected, CalendarColors.razor's
   .row-unsaved, Feedback.razor's .feedback-row-unread) - those must always win regardless of row
   position, never be washed out by the stripe. */
.table tbody tr:nth-child(even):not(.absence-row-pending):not(.absence-row-rejected):not(.row-unsaved):not(.feedback-row-unread) {
    background-color: var(--color-border);
}

@media (max-width: 480px) {
    .auth-card {
        padding: 1.5rem;
        border-radius: var(--radius-md);
    }
}

/* --- Home page --- */

.page-loading {
    color: var(--color-text-muted);
    padding: 2rem 0;
}

.hero {
    max-width: 42rem;
    padding: 2rem 0 1rem;
}

.hero-subtitle {
    color: var(--color-text-muted);
    font-size: 1.05rem;
    margin: 1rem 0 1.75rem;
}

.hero-actions {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.page-header {
    margin-bottom: 1.5rem;
}

.dashboard-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
}

.widget-menu-anchor {
    position: relative;
    flex-shrink: 0;
}

.widget-menu-popover {
    position: absolute;
    right: 0;
    top: calc(100% + 0.4rem);
    z-index: 10;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    padding: 0.75rem;
    min-width: 12rem;
}

.widget-menu-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0;
    font-size: 0.9rem;
    cursor: pointer;
}

.widget-menu-alert {
    margin: 0.5rem 0;
    padding: 0.4rem 0.6rem;
    font-size: 0.825rem;
}

.widget-menu-actions {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--color-border);
    display: flex;
    justify-content: flex-end;
}

.page-subtitle {
    color: var(--color-text-muted);
}

.empty-state {
    background: var(--color-surface);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-md);
    padding: 2rem;
    color: var(--color-text-muted);
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid var(--color-danger);
}

.validation-message {
    color: var(--color-danger);
}

#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/* Boot splash (index.html, before Blazor has booted) - same gradient/circles recipe as
   AuthShell.razor.css's .login-page/.login-bg-circle-*, duplicated here as plain unscoped rules
   because static index.html markup never gets Blazor's CSS-isolation attribute, so it can't match
   those scoped selectors. Keep the two visually in sync if the login backdrop ever changes. */
.boot-loading {
    position: relative;
    overflow: hidden;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
}

.boot-loading-backdrop {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.boot-loading-circle {
    position: absolute;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
}

.boot-loading-circle-1 {
    width: 32rem;
    height: 32rem;
    left: -14rem;
    top: -16rem;
}

.boot-loading-circle-2 {
    width: 16rem;
    height: 16rem;
    right: -4rem;
    top: 8%;
    background: rgba(255, 255, 255, 0.10);
}

.boot-loading-circle-3 {
    width: 22rem;
    height: 22rem;
    left: 8%;
    bottom: -12rem;
    background: rgba(255, 255, 255, 0.10);
}

.boot-loading-brand {
    position: relative;
    z-index: 1;
    color: #ffffff;
    font-weight: 700;
    font-size: 1.15rem;
    letter-spacing: -0.01em;
}

.loading-clock {
    position: relative;
    z-index: 1;
    display: block;
    width: 8rem;
    height: 8rem;
}

.loading-clock-ticks line {
    stroke: rgba(255, 255, 255, 0.55);
    stroke-width: 2;
    stroke-linecap: round;
}

.loading-clock-face {
    fill: none;
    stroke: rgba(255, 255, 255, 0.35);
    stroke-width: 4;
}

.loading-clock-hand {
    stroke: #ffffff;
    stroke-width: 5;
    stroke-linecap: round;
    transform-origin: 50px 50px;
    transform: rotate(var(--clock-hand-deg, 0deg));
    transition: transform 0.15s ease-out;
}

.loading-clock-pivot {
    fill: #ffffff;
}

.loading-progress-text {
    position: relative;
    z-index: 1;
    text-align: center;
    font-weight: 600;
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.85);
}

    .loading-progress-text:after {
        content: var(--blazor-load-percentage-text, "Ładowanie");
    }

code {
    color: #c02d76;
}

.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;
}

/* Legal pages (Polityka prywatności / Regulamin) */
.legal-page {
    max-width: 46rem;
    margin: 0 auto;
    padding: 1.5rem 1.25rem 4rem;
}

.legal-page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-bottom: 1.5rem;
    margin-bottom: 1.5rem;
    border-bottom: 1px solid var(--color-border);
}

.legal-brand {
    font-weight: 700;
    color: var(--color-text);
    text-decoration: none;
}

.legal-back {
    color: var(--color-text-muted);
    text-decoration: none;
    font-size: 0.9rem;
}

.legal-content h1 {
    margin-bottom: 0.25rem;
}

.legal-updated {
    color: var(--color-text-muted);
    font-size: 0.9rem;
    margin-bottom: 2rem;
}

.legal-content h2 {
    font-size: 1.15rem;
    margin-top: 2rem;
    margin-bottom: 0.75rem;
}

.legal-content p, .legal-content li {
    color: var(--color-text);
    line-height: 1.6;
}

.legal-content ul {
    padding-left: 1.25rem;
}

.legal-footer-link {
    margin-top: 2.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--color-border);
}

/* App footer with links to legal pages */
.announcement-banner {
    background: var(--color-warning-bg);
    border-bottom: 1px solid var(--color-warning-border);
    color: var(--color-warning);
    padding: 0.75rem 1.5rem;
    font-weight: 500;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 0.5rem;
}

    .announcement-banner svg {
        flex-shrink: 0;
    }

/* Same look as .notification-banner-close, but inherits the banner's text color - an admin can set a
   custom one per announcement, so a fixed color could clash with it. */
.announcement-banner-close {
    background: none;
    border: none;
    padding: 0.25rem;
    font-size: 1.25rem;
    line-height: 1;
    color: inherit;
    opacity: 0.8;
    cursor: pointer;
}

    .announcement-banner-close:hover {
        opacity: 1;
    }

/* Result banner for a finished Jira sync batch - deliberately a different look (info-blue, not
   warning-yellow) and behavior (has a close button + auto-dismiss) from .announcement-banner
   above, since it's a one-off completion notice rather than a standing system-wide notice. */
.registration-layout {
    display: grid;
    grid-template-columns: 2fr 1fr;
    align-items: start;
    gap: 1.5rem;
}

@media (max-width: 900px) {
    .registration-layout {
        grid-template-columns: 1fr;
    }
}

.registration-ticket-shortcuts {
    max-width: none;
}

.ticket-shortcut-toolbar {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin-bottom: 0.5rem;
}

    .ticket-shortcut-toolbar input[type="search"] {
        flex: 1 1 auto;
    }

    .ticket-shortcut-toolbar .ticket-shortcut-font-size {
        flex: 0 0 auto;
        width: auto;
    }

/* Shared Skróty+Notatki font-size/expand controls (poprawki-p2 follow-up) - sits inline in
   .settings-tabs-with-actions now (own row's flex/justify-content handles the right-alignment),
   so the toolbar's usual bottom margin (meant for separating it from the list below) is dropped. */
.ticket-shortcut-toolbar-shared {
    justify-content: flex-end;
    margin-bottom: 0;
}

.ticket-shortcut-list {
    max-height: 320px;
    overflow-y: auto;
    font-size: 0.85rem;
    transition: max-height 0.15s ease;
}

/* Side-panel empty states keep the panel's smaller text (they used an inline font-size before
   moving into DataGrid). */
.registration-ticket-shortcuts .data-grid-empty {
    font-size: 0.85rem;
}

    .ticket-shortcut-list.font-size-small {
        font-size: 0.75rem;
    }

    .ticket-shortcut-list.font-size-medium {
        font-size: 0.85rem;
    }

    .ticket-shortcut-list.font-size-large {
        font-size: 0.95rem;
    }

    /* Bug fix: Skróty/Notatki render a <table class="table">, and .table has its own fixed
       font-size (0.85rem) that always wins over an ANCESTOR's font-size (inheritance is weaker
       than any rule that targets the element directly, regardless of specificity) - so the
       selector above never actually changed anything visible. These target the table itself. */
    .ticket-shortcut-list.font-size-small .table {
        font-size: 0.75rem;
    }

    .ticket-shortcut-list.font-size-medium .table {
        font-size: 0.85rem;
    }

    .ticket-shortcut-list.font-size-large .table {
        font-size: 0.95rem;
    }

    .ticket-shortcut-list.expanded {
        max-height: 720px;
    }

    /* Konfiguracja's notes list is the page's main content (not a sidebar panel like
       Registration's), so it gets a taller default without needing the expand toggle. Capped
       against viewport height (not just a flat px), so on shorter windows the list shrinks
       instead of pushing the app footer (Regulamin/Polityka prywatności) far below the fold. */
    .ticket-shortcut-list.ticket-shortcut-list-page {
        max-height: min(480px, 45vh);
    }

.ticket-shortcut-expand-chevron {
    display: inline-flex;
    transition: transform 0.15s ease;
}

    .ticket-shortcut-expand-chevron.expanded {
        transform: rotate(90deg);
    }

/* "Zaraportowano dziś" summary card (Rejestracja, 2026-08-29) - one card, toggle chevron always
   at its top-left corner in both states. Collapsed = just the chevron + compact summary in one
   row. Expanded = the chevron row sits above three columns (Zaraportowano/Czas per numer/Czas per
   typ), separated by a vertical divider that floats clear of the top/bottom edges. */
.registration-stats-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 1.5rem;
    /* .settings-card's default 1.75rem is sized for a card whose content fills it out - this
       card's toggle row is much shorter, so the default padding read as an oversized gap above
       the arrow (and, when collapsed, above/below the single row too). */
    padding: 0.6rem 1.25rem;
}

    /* Collapsed = just one slim row, even less padding needed. */
    .registration-stats-card.registration-stats-card-collapsed {
        padding: 0.5rem 1.25rem;
    }

.registration-stats-toggle-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.registration-stats-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
}

/* Pushes the dot+status group to the right edge of the row - the sync icon right after it keeps
   its own small, fixed gap (not another auto-margin) so it hugs the status text instead of
   floating off toward the row's far edge. */
.registration-stats-jira-right {
    margin-left: auto;
}

.registration-stats-sync {
    margin-left: 10px;
}

/* Shared look for every "Synchronizuj z Jirą" button (Rejestracja czasu pracy + Kalendarz) -
   icon + label, spaced consistently regardless of the surrounding btn-* size modifier. */
.jira-sync-button {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
}

.registration-stats-bar-label {
    color: var(--color-text-muted);
}

.registration-stats-bar-jira {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.registration-stats-bar-jira-status {
    font-size: 0.8rem;
    color: var(--color-text-muted);
}

.registration-stats-columns {
    display: flex;
    flex-wrap: wrap;
}

.registration-stats-col {
    flex: 1 1 12rem;
    padding: 0 1.5rem;
    position: relative;
}

    .registration-stats-col:first-child {
        padding-left: 0;
    }

    .registration-stats-col:last-child {
        padding-right: 0;
    }

    /* The divider itself - a floating vertical line to the left of every column but the first,
       inset from the card's top/bottom edges (not a full-height border). */
    .registration-stats-col + .registration-stats-col::before {
        content: "";
        position: absolute;
        left: 0;
        top: 0.25rem;
        bottom: 0.25rem;
        width: 1px;
        background: var(--color-border);
    }

@media (max-width: 768px) {
    .registration-stats-columns {
        flex-direction: column;
    }

    .registration-stats-col {
        padding: 0.75rem 0;
    }

        .registration-stats-col:first-child {
            padding-top: 0;
        }

        .registration-stats-col:last-child {
            padding-bottom: 0;
        }

        /* Stacked layout - the divider flips to a horizontal line above each column but the
           first, inset from the left/right edges instead of top/bottom. */
        .registration-stats-col + .registration-stats-col::before {
            left: 0.25rem;
            right: 0.25rem;
            top: 0;
            bottom: auto;
            width: auto;
            height: 1px;
        }
}

/* Icon-only toggle button (2026-08-29) - no visible button chrome (border/background), just the
   chevron itself, since the whole bar is already clickable and a fully-styled button here would
   compete visually with the compact summary text next to it. */
.btn-icon-plain {
    display: inline-flex;
    align-items: center;
    background: none;
    border: none;
    padding: 0.15rem;
    color: var(--color-text-muted);
    cursor: pointer;
}

    .btn-icon-plain:hover {
        color: var(--color-primary);
    }

/* Explicit request: the grid "Pokaż opis" icon (Registration.razor/Configuration.razor) reads as
   grey inside .btn-icon-plain (color: var(--color-text-muted)) - overridden to the app's near-
   black body-text color on the icon itself, which wins over the inherited grey without touching
   .btn-icon-plain (still used unmodified by Registration.razor's unrelated stats-collapse
   toggle). Deliberately static, not swapping to --color-primary on hover like the button around
   it still does for OTHER icons - the whole point here was "always black", not "black until
   hovered". */
.document-icon-dark {
    color: var(--color-text);
}

/* Registration.razor's Opis "speech bubble" (poprawki-p2 follow-up, explicit request) - a comic-
   style callout anchored to the clicked note icon (see Registration.razor.js), not a centered
   Modal. Position is set to fixed with inline top/left by the JS (--tail-left is the only piece
   set from Razor, since only the .NET side knows which entry's icon was clicked). Invisible
   full-viewport backdrop + outside-click-to-close, same idiom as PolishDatePicker's own popup -
   deliberately NOT Modal.razor's "close only via the X" rule, since this reads as a lightweight
   tooltip-like popover, not a dialog. */
.description-bubble-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1055;
    background: transparent;
}

.description-bubble {
    position: fixed;
    z-index: 1060;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    padding: 0.75rem 1rem;
    padding-right: 1.75rem;
    max-width: 22rem;
    white-space: pre-line;
    font-size: 0.85rem;
    /* top/left start at 0/0 (Razor's default double fields) until Registration.razor.js computes
       the real coordinates - hidden until then so there's no one-frame flash at the wrong spot. */
    visibility: hidden;
}

    .description-bubble.positioned {
        visibility: visible;
    }

.description-bubble-close {
    position: absolute;
    top: 0.35rem;
    right: 0.5rem;
    background: none;
    border: none;
    padding: 0.15rem;
    font-size: 1.1rem;
    line-height: 1;
    color: var(--color-text-muted);
    cursor: pointer;
}

    .description-bubble-close:hover {
        color: var(--color-text);
    }

/* The tail: two stacked triangles (border color, then a slightly smaller surface-color one on
   top) - the standard CSS speech-bubble trick for faking a 1px border on the pointer itself.
   Points up by default (bubble sits below the icon); .flipped points down instead (bubble sits
   above the icon, per Registration.razor.js's viewport-edge flip). */
.description-bubble::before,
.description-bubble::after {
    content: "";
    position: absolute;
    left: var(--tail-left, 1rem);
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 8px solid transparent;
    border-right: 8px solid transparent;
}

.description-bubble::before {
    top: -9px;
    border-bottom: 9px solid var(--color-border);
}

.description-bubble::after {
    top: -8px;
    border-bottom: 8px solid var(--color-surface);
}

.description-bubble.flipped::before {
    top: auto;
    bottom: -9px;
    border-bottom: none;
    border-top: 9px solid var(--color-border);
}

.description-bubble.flipped::after {
    top: auto;
    bottom: -8px;
    border-bottom: none;
    border-top: 8px solid var(--color-surface);
}

.ticket-note-group-header {
    font-weight: 700;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-text-muted);
    margin-top: 0.75rem;
}

    .ticket-note-group-header:first-child {
        margin-top: 0;
    }

    .ticket-shortcut-list.font-size-small .ticket-note-group-header {
        font-size: 0.65rem;
        margin-top: 0.5rem;
    }

    .ticket-shortcut-list.font-size-large .ticket-note-group-header {
        font-size: 0.8rem;
        margin-top: 0.9rem;
    }

/* Poprawki-p2 follow-up (per explicit request): Skróty is now a normal <table> (nr zgłoszenia,
   tytuł, actions - headers, filters, same idiom as the Notatki table right next to it), not a
   bespoke flex row - only the ticket-number-as-link button needs its own rule. */
/* Code-review/explicit follow-up: font type and size must match the rest of the table (and
   Notatki's own table) exactly - only color+cursor signal "this is clickable", no bold override,
   so this reads as the same typeface as every other cell instead of visually standing out. */
.ticket-shortcut-link {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--color-primary);
    cursor: pointer;
    text-align: left;
}

    .ticket-shortcut-link:hover {
        color: var(--color-primary-dark);
        text-decoration: underline;
    }

.jira-json-preview-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    text-decoration: none;
    font-weight: 500;
    color: var(--color-text);
}

    .jira-json-preview-toggle:hover {
        color: var(--color-primary);
    }

.jira-json-preview-chevron {
    display: inline-flex;
    transition: transform 0.15s ease;
}

    .jira-json-preview-chevron.expanded {
        transform: rotate(90deg);
    }

.jira-json-preview {
    background: var(--color-surface-alt, #f5f5f7);
    border: 1px solid var(--color-border);
    border-radius: 6px;
    padding: 0.75rem 1rem;
    font-size: 0.8rem;
    max-height: 320px;
    overflow: auto;
    white-space: pre;
}

.notification-banner {
    background: var(--color-primary-light);
    border-bottom: 1px solid var(--color-primary);
    color: var(--color-primary-dark);
    padding: 0.75rem 1.5rem;
    font-weight: 500;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 0.75rem;
}

/* Pinned while the page scrolls, instead of scrolling away with the rest of the content - right
   under the fixed TopBar where it renders (see --topbar-height's own comment), at the very top of
   the viewport below that breakpoint (no TopBar there). Phones too (2026-10-07): a save error
   raised from a modal after scrolling down must not land above the viewport, behind the overlay.
   Sticky sits on the wrap, not the banner, since the wrap is the direct child of <main>. */
@media (min-width: 641px) {
    .notification-banner-wrap {
        position: sticky;
        top: var(--topbar-height);
    }
}

@media (max-width: 640px) {
    .notification-banner-wrap {
        position: sticky;
        top: 0;
    }
}

/* Slide-in/out from under the TopBar. The wrap's grid row animates 0fr <-> 1fr so the page
   content below moves smoothly instead of jumping when the banner's own height appears/
   disappears; the banner itself slides by its own height inside the clip, so it never overlaps
   the TopBar. z-index stays under the TopBar (1000) so its account menu opens over the banner -
   but goes above .modal-overlay (1050) while a modal is open: a save result (e.g. a server error
   while a popup stays open with the user's input) must be visible on top of that modal. */
.notification-banner-wrap {
    z-index: 900;
    display: grid;
    grid-template-rows: 1fr;
    animation: notification-banner-wrap-in 250ms ease-out;
}

body:has(.modal-overlay) .notification-banner-wrap {
    z-index: 1060;
}

.notification-banner-clip {
    min-height: 0;
    overflow: hidden;
}

.notification-banner-wrap .notification-banner {
    animation: notification-banner-slide-in 250ms ease-out;
}

/* Duration must match NotificationBanner.razor's LeaveAnimationDuration. */
.notification-banner-wrap.notification-banner-leaving {
    animation: notification-banner-wrap-out 300ms ease-in forwards;
}

    .notification-banner-wrap.notification-banner-leaving .notification-banner {
        animation: notification-banner-slide-out 300ms ease-in forwards;
    }

@keyframes notification-banner-wrap-in {
    from { grid-template-rows: 0fr; }
    to { grid-template-rows: 1fr; }
}

@keyframes notification-banner-wrap-out {
    from { grid-template-rows: 1fr; }
    to { grid-template-rows: 0fr; }
}

@keyframes notification-banner-slide-in {
    from { transform: translateY(-100%); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

@keyframes notification-banner-slide-out {
    from { transform: translateY(0); opacity: 1; }
    to { transform: translateY(-100%); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .notification-banner-wrap,
    .notification-banner-wrap .notification-banner,
    .notification-banner-wrap.notification-banner-leaving,
    .notification-banner-wrap.notification-banner-leaving .notification-banner {
        animation: none;
    }
}

.jira-ticket-link {
    margin-left: 0.35rem;
    color: var(--color-text-muted);
    display: inline-flex;
    vertical-align: middle;
}

    .jira-ticket-link:hover {
        color: var(--color-primary);
    }

.notification-banner-close {
    background: none;
    border: none;
    padding: 0.25rem;
    font-size: 1.25rem;
    line-height: 1;
    color: var(--color-primary-dark);
    cursor: pointer;
}

    .notification-banner-close:hover {
        color: var(--color-text);
    }

.notification-banner-success {
    background: var(--color-success-bg);
    border-bottom-color: var(--color-success-border);
    color: var(--color-success);
}

    .notification-banner-success .notification-banner-close {
        color: var(--color-success);
    }

.notification-banner-error {
    background: var(--color-danger-bg);
    border-bottom-color: var(--color-danger-border);
    color: var(--color-danger);
}

    .notification-banner-error .notification-banner-close {
        color: var(--color-danger);
    }

.app-footer {
    margin-top: 2rem;
    padding-top: 1rem;
    padding-bottom: 1.5rem;
    border-top: 1px solid var(--color-border);
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    font-size: 0.85rem;
    color: var(--color-text-muted);
}

.app-footer-links {
    display: flex;
    gap: 1rem;
}

.app-footer a {
    color: var(--color-text-muted);
}

.landing-footer {
    margin-top: 1.5rem;
    display: flex;
    gap: 1rem;
    font-size: 0.85rem;
}

.landing-footer a {
    color: inherit;
    opacity: 0.8;
    text-decoration: underline;
}

/* Cookie notice banner */
.cookie-banner {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1000;
    background: var(--color-sidebar-bg);
    color: var(--color-sidebar-text-active);
    padding: 1rem 1.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    box-shadow: var(--shadow-lg);
}

.cookie-banner-text {
    margin: 0;
    font-size: 0.9rem;
    max-width: 52rem;
}

.cookie-banner-text a {
    color: #93c5fd;
}

.cookie-banner-actions {
    display: flex;
    gap: 0.75rem;
    flex-shrink: 0;
}
