/* Blazor's FocusOnNavigate programmatically focuses h1 on each
   navigation for screen-reader announcements. Screen readers don't
   need a visible focus ring, and Chrome's :focus-visible heuristic
   still paints an outline on programmatic focus that follows a
   mouse click, which reads as a flash on every navigation. Suppress
   the outline — h1 isn't interactive so there's no keyboard
   "where am I" loss from removing it. */
h1:focus,
h1:focus-visible {
    outline: none;
}

/* Skip-to-content link. Visually hidden until keyboard focus lands
   on it, then slides into the top-left corner above everything. The
   target is the <main id="main-content"> in MainLayout. Positioned
   above the Fluent header's z-index so the link isn't hidden behind
   the header when it appears. */
.skip-link {
    position: absolute;
    top: 0;
    left: 0;
    transform: translateY(-110%);
    z-index: 1000;
    padding: 8px 16px;
    background: var(--accent-fill-rest);
    color: var(--foreground-on-accent-rest);
    text-decoration: none;
    border-radius: 0 0 4px 0;
    transition: transform 120ms ease-out;
}

.skip-link:focus {
    transform: translateY(0);
    outline: 2px solid var(--foreground-on-accent-rest);
    outline-offset: -4px;
}

/* Tune the default iOS/Android tap-highlight (which is a translucent
   grey that clashes with the blue accent palette) to a faint accent
   wash, keeping the "something was tapped" feedback on touch devices
   but matching the brand. Set at <html> so everything inherits. */
html {
    -webkit-tap-highlight-color: rgba(24, 95, 165, 0.15);
}

/* Mobile typography pass (M-24). Hunting-down and rewriting every
   Style="font-size: 0.85rem" (30+ occurrences) would churn a lot of
   code — instead, intercept the two explicit inline sizes that render
   too small on phones (0.85rem ≈ 13.6 px; 0.8rem ≈ 12.8 px) and bump
   them to a readable minimum below 768 px. !important on a rule in a
   stylesheet beats non-!important inline styles. Desktop viewports
   keep the dense sizing because the text is easier to read at arm's
   length on a large display.
   Out of scope: fluid-typography clamp() pass for headings (M-26,
   low priority). */
@media (max-width: 767px) {
    [style*="font-size: 0.85rem"],
    [style*="font-size:0.85rem"] {
        font-size: 0.95rem !important;
    }

    [style*="font-size: 0.8rem"],
    [style*="font-size:0.8rem"] {
        font-size: 0.9rem !important;
    }
}

/* WCAG 2.2 SC 2.5.8 — 24×24 minimum; Apple HIG / Material recommend
   44×44 for primary touch targets. We apply the 44 px floor only on
   coarse pointer devices so mouse-driven desktops keep their compact
   control heights. The ::part(control) selector reaches the interactive
   element inside Fluent's shadow DOM; native <button> and <a> get the
   same treatment. Icon-only stealth buttons are the worst offenders —
   Fluent's Size="Small" and Size16 icons render ~28–32 px without this
   rule, below the threshold on any touch device.
   Cautions: (1) we can't use min-height on the FluentBadge remove button
   inside TagInput because the badge host controls layout — those got
   an explicit padding bump via their scoped CSS instead. (2) the
   password-toggle button overrides this via its own scoped CSS because
   it needs absolute positioning inside the password field wrapper. */
@media (pointer: coarse) {
    fluent-button::part(control),
    fluent-anchor::part(control) {
        min-height: 44px;
        min-width: 44px;
    }

    /* Row-action buttons inside FluentDataGrid specifically — M-22.
       Buttons in rows are Size="Small" with Size16 icons, so they
       inherit the 44 px floor above. We also widen the row minimum so
       the buttons don't overflow a too-short row. */
    tr.fluent-data-grid-row > td {
        min-height: 48px;
    }

    /* Native <button>s (Login / Register password-toggle, avatar,
       etc.) — covered individually by their scoped CSS where
       positioning matters, but raise an untouched baseline for any
       others. */
    button:not(.password-toggle):not(.user-avatar):not(.skip-link) {
        min-height: 44px;
    }
}

/* Honour OS-level "Reduce Motion" — collapse every transition and
   animation to effectively instant. Covers scoped-CSS `::deep` rules
   and Fluent UI component internals since the selector is universal.
   Kept broad on purpose: animations we genuinely want under reduced
   motion (e.g. a progress spinner) should be re-opted in with their
   own `@media (prefers-reduced-motion: no-preference)` rule. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* "Add a job manually" modal — make form inputs fill their wrapper.
   FluentTextField / FluentTextArea / FluentNumberField render as
   <fluent-text-field> etc. with a shadow-DOM #control input. Setting
   `Style="width: 100%;"` on the Razor side reaches the OUTER custom
   element only — the inner #control input keeps Fluent's default
   ~200-280 px width and the form looks half-empty inside a 480 px
   modal. The `::part(control)` selector reaches into the shadow DOM
   to override the inner width.

   Scoped to a Class on FluentDialog rather than a `data-testid` —
   FluentDialog's unmatched-attributes splat does not always reach the
   rendered <fluent-dialog>, but Class is a typed parameter that
   reliably lands on the host element, so `.ck-add-manual-job-modal`
   is a more durable hook than `[data-testid="..."]`. */
.ck-add-manual-job-modal fluent-text-field,
.ck-add-manual-job-modal fluent-text-area {
    width: 100%;
}

/* Fluent's shadow DOM template puts the input inside a `.root` wrapper
   (`<div class="root" part="root">`). For fluent-text-field and
   fluent-text-area the root carries a fixed default width that caps
   the inner control even when ::part(control) is widened — setting
   both parts to width: 100% fills the shadow DOM all the way through.
   FluentNumberField is deliberately omitted: salary inputs hold short
   numeric values and a full-width box looks visually heavy beside its
   label. */
.ck-add-manual-job-modal fluent-text-field::part(root),
.ck-add-manual-job-modal fluent-text-field::part(control),
.ck-add-manual-job-modal fluent-text-area::part(root),
.ck-add-manual-job-modal fluent-text-area::part(control) {
    width: 100%;
    box-sizing: border-box;
}

/* Fluent UI Blazor base styles */
html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    /*
     * App shell scroll containment.
     * FluentMainLayout owns the scrollable region — the outer document
     * must not scroll independently. Do not remove without verifying
     * scroll behaviour after Fluent UI upgrades.
     */
    overflow: hidden;
    font-family: var(--body-font);
    font-size: var(--type-ramp-base-font-size);
    line-height: var(--type-ramp-base-line-height);
    font-weight: var(--font-weight);
    color: var(--neutral-foreground-rest);
    background: var(--neutral-fill-layer-rest);
}

/* ── Header bar: light background ──
   FluentMainLayout renders the header as <header class="header"> and sets
   its background via Fluent's design-token system (accent-fill-rest),
   which is applied dynamically and can't be beaten by normal selectors.
   Override with !important on the rendered element. */
header.header {
    background: var(--ck-neutral-50, #F8FAFC) !important;
    border-bottom: 1px solid var(--ck-neutral-200, #E2E8F0);
    color: var(--ck-neutral-900, #0F172A) !important;
}

/* Header icon buttons (theme toggle, suggestion, sign-in) need dark
   foreground on the light header instead of the default white. */
header.header fluent-button::part(control) {
    color: var(--ck-neutral-600, #475569);
}

header.header fluent-button:hover::part(control) {
    color: var(--ck-neutral-900, #0F172A);
}

/* Below 768 px the Fluent nav menu is lifted out of the flex row it
   normally shares with the page body, so it no longer eats 40 % of a
   360 px viewport when collapsed. Instead it becomes a fixed-position
   rail / drawer along the left edge (40 px collapsed, 270 px
   expanded) and the body gets a 40 px padding-left so the rail
   doesn't overlap content. `.layout` parent structure is Fluent
   framework HTML so selectors target .main-layout-navmenu and its
   flex sibling directly. z-index keeps the drawer above content but
   below toast/modal layers (which run at 1000+). */
@media (max-width: 767px) {
    .main-layout-navmenu {
        position: fixed !important;
        top: var(--header-height, 50px);
        left: 0;
        height: calc(100vh - var(--header-height, 50px));
        z-index: 500;
        background: var(--neutral-layer-1);
        box-shadow: 2px 0 8px rgba(0, 0, 0, 0.08);
        transition: width 160ms ease, min-width 160ms ease;
    }

    /* When the user expands the nav, Fluent sets inline width: 270px
       (or whatever NavMenuWidth is). Cap the overlay at the viewport
       so it doesn't spill off-screen on extreme narrow devices. */
    .main-layout-navmenu[style*="width: 270px"] {
        max-width: 92vw;
    }

    /* Give the body the rail's worth of left padding so the 40 px
       collapsed strip doesn't overlap headings and controls. The
       scroll container already has a 12 px gutter from the C1 rule —
       we add 40 on top. */
    .body-content,
    .body-scroll-container {
        padding-left: calc(40px + max(12px, env(safe-area-inset-left))) !important;
    }

}

/* Left-nav menu content needs its own scroll container on short
   viewports — FluentMainLayout's nav pane itself doesn't clip and
   on short displays runs off the bottom with no way to reach the
   Admin group. The correct max-height is "viewport minus the space
   above the scroll area" — measured via getBoundingClientRect the
   container starts at Y=91 (50px header + 41px pane label/padding).
   `calc(100vh - 100px)` gives a small buffer over that measurement
   so the scrollbar appears exactly when content overflows the
   visible pane, not when it overflows a smaller hardcoded cap. An
   earlier `calc(100vh - 60px)` here undershot the offset by ~30px
   and caused scrolling to "only kick in" when all three groups
   were expanded — because only then did content exceed both the
   too-small max-height AND the real visible area. */
.nav-scroll {
    max-height: calc(100vh - 100px);
    overflow-y: auto;
    overflow-x: hidden;
}


/* Unhandled-error banner. Blazor's error-handling pipeline toggles
   inline `style="display: block"` on this element when an exception
   propagates; initial state is display:none. The attribute selector
   catches that toggle and swaps to flex so the bottom toolbar lays
   out the reload link and dismiss button alongside the message.
   Safe-area aware so the iOS home indicator doesn't cover the bar.
   The dismiss is a real <button> (M-31) with an explicit aria-label
   and a 44×44 tap target on coarse pointers. */
#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100%;
    z-index: 1000;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom)) 16px;
    background-color: var(--error, #b91c1c);
    color: white;
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.18);
    align-items: center;
    gap: 12px;
}

#blazor-error-ui[style*="display: block"],
#blazor-error-ui[style*="display:block"] {
    display: flex !important;
}

#blazor-error-ui .error-ui-message {
    flex: 1;
    min-width: 0;
    font-size: 0.95rem;
}

#blazor-error-ui .reload {
    color: white;
    text-decoration: underline;
    font-weight: 600;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    padding: 0 12px;
}

#blazor-error-ui .dismiss {
    background: transparent;
    border: none;
    color: white;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    padding: 8px;
    border-radius: 4px;
}

#blazor-error-ui .dismiss:hover { background: rgba(255, 255, 255, 0.12); }
#blazor-error-ui .dismiss:focus-visible { outline: 2px solid white; outline-offset: 2px; }

/* Reconnect modal override (M-30). Blazor renders
   #components-reconnect-modal with default classes .components-reconnect-show,
   .components-reconnect-hide, .components-reconnect-failed,
   .components-reconnect-rejected. We skip the full class-by-class
   reskin — the default show/hide transitions are fine — and just
   replace the visual shell so mobile users see a readable dialog
   with a visible retry button instead of a plain grey overlay. */
#components-reconnect-modal {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.55);
    z-index: 1100;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 16px calc(16px + env(safe-area-inset-right)) calc(16px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left));
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
}

#components-reconnect-modal .reconnect-inner {
    background: var(--neutral-layer-1, #fff);
    color: var(--neutral-foreground-rest, #0f172a);
    border-radius: 8px;
    padding: 24px;
    max-width: 400px;
    width: 100%;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
}

#components-reconnect-modal .reconnect-inner h2 {
    margin: 0 0 8px 0;
    font-size: 1.1rem;
}

#components-reconnect-modal .reconnect-inner p {
    margin: 0 0 16px 0;
    color: var(--neutral-foreground-hint);
}

#components-reconnect-modal .reconnect-retry {
    min-height: 44px;
    min-width: 140px;
    padding: 0 16px;
    background: var(--accent-fill-rest);
    color: var(--foreground-on-accent-rest, white);
    border: none;
    border-radius: 4px;
    font-weight: 600;
    cursor: pointer;
}

#components-reconnect-modal .reconnect-retry:hover {
    background: var(--accent-fill-hover, var(--accent-fill-rest));
}

#components-reconnect-modal .reconnect-retry:focus-visible {
    outline: 2px solid var(--accent-fill-rest);
    outline-offset: 2px;
}

/* The FluentMainLayout .body-content area handles scrolling.
   Bottom padding ensures the last item isn't clipped by the viewport.
   env(safe-area-inset-*) keeps content clear of the notch and home
   indicator on iOS; max() guarantees we never fall below the visual
   minimum when the insets are 0 (most desktops, Android without a notch). */
.body-scroll-container {
    padding-top: max(24px, env(safe-area-inset-top));
    padding-right: max(24px, env(safe-area-inset-right));
    padding-bottom: calc(5rem + env(safe-area-inset-bottom));
    padding-left: max(24px, env(safe-area-inset-left));
}

/* Phones: trim the gutter so a 360px viewport keeps ~336px for content
   instead of 312px. Safe-area insets still win on notched devices. */
@media (max-width: 600px) {
    .body-scroll-container {
        padding-right: max(12px, env(safe-area-inset-right));
        padding-left: max(12px, env(safe-area-inset-left));
    }
}

/* Mobile data-grid column hiding (M-21 / C8).
   Below 768 px the pipeline grids collapse to Title + Company + Actions.
   The inline `grid-template-columns` set by FluentDataGrid's
   GridTemplateColumns prop is overridden here with !important (inline
   wins without it). Every non-essential column carries Class="col-hide-mobile"
   which Fluent forwards to both <th> and <td>, so the cells vanish
   together with the track re-computation.

   Product decision: default column set (title + company + primary
   action at 360 px). Row-action buttons in Actions column collapse
   to icon-only below 1600 px via the existing .action-label media
   rule, so Actions fits comfortably in the auto track on phones.

   Scoped by grid data-testid to avoid bleeding into admin/settings
   grids that follow a different column strategy. */
@media (max-width: 767px) {
    [data-testid="job-list-grid"][data-bulk="false"].fluent-data-grid {
        grid-template-columns: 2fr 1.2fr auto !important;
    }

    [data-testid="job-list-grid"][data-bulk="true"].fluent-data-grid {
        grid-template-columns: 40px 2fr 1.2fr auto !important;
    }

    .col-hide-mobile {
        display: none !important;
    }
}

/* Horizontal-scroll wrapper for FluentDataGrid on narrow viewports.
   Pipeline pages (Review, Shortlisted, Rejected, AutoRejected, Archive)
   carry 7–9 columns and compress to ~30 px per cell below ~600 px.
   Rather than fight that layout, let the table keep its natural
   min-width and scroll horizontally inside a bordered wrapper. The
   -webkit-overflow-scrolling hint gets iOS to use momentum scrolling.
   Above 768 px the wrapper is transparent — no scrollbar, no border,
   no change to desktop appearance (table fits, scroll never triggers).
   Pairs with C8 which hides non-essential columns below 768 px so
   the scroll is a fallback, not the primary mobile experience. */
.grid-scroll {
    width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

@media (max-width: 767px) {
    .grid-scroll {
        border: 1px solid var(--neutral-stroke-divider-rest);
        border-radius: 4px;
    }
}

/* Action-button label collapse for grid Actions columns.
   Below 1600 px the Actions column auto-track is too narrow for two
   labelled buttons side by side and they overlap. Wrap each button's
   visible text in <span class="action-label">; this rule hides the
   text and keeps the icon. Buttons must carry a Title attribute and
   aria-label so the action stays discoverable when icon-only.
   Lives in app.css (rather than scoped to JobListPage.razor.css where
   it originated) so admin grids — BetaAccess, etc. — can adopt the
   same idiom by adding the class. */
@media (max-width: 1600px) {
    .action-label {
        display: none;
    }
}

/* Give data grid cells enough vertical space for buttons and badges.
   Targets the actual rendered native <th> / <td> with the
   .fluent-data-grid-row class on the row — the older selector here
   pointed at <fluent-data-grid-cell> custom elements that the
   framework doesn't actually render, so the rule was a silent no-op
   for ages. */
tr.fluent-data-grid-row > th,
tr.fluent-data-grid-row > td {
    padding-top: 4px;
    padding-bottom: 4px;
    align-content: center;
}

/* Bulk-selection column on JobListPage.
   FluentDataGrid renders as <table class="fluent-data-grid"> with
   <th> for header cells and <td> for body cells (NOT as a custom
   <fluent-data-grid> / <fluent-data-grid-cell> element — earlier
   selectors here that targeted those tag names silently never matched).
   The header <th> carries an inline `min-width: 100px` that the body
   <td> doesn't, so the bulk-select column header was ~100px wide while
   the body cells were 40px — checkbox alignment was offset by ~30px
   between header and rows. The override below forces both to 40px and
   strips the cells' horizontal padding so the wrapper div has the full
   width to centre its checkbox in. !important is required because
   inline styles win on specificity otherwise. */
[data-testid="job-list-grid"] th[col-index="1"],
[data-testid="job-list-grid"] td[col-index="1"] {
    min-width: 40px !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
}

.job-list-checkbox-cell {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: 100%;
}

/* Remove focus outline from non-interactive layout containers */
fluent-stack:focus,
fluent-grid:focus,
fluent-grid-item:focus {
    outline: none;
}

/* Stretch the active tab indicator to the full tab width (default is a 20px dot) */
fluent-tabs::part(activeIndicator) {
    width: 100%;
    left: 0;
}

/* Admin/Suggestions.razor — feedback rows open a detail modal on click.
   Same cell-wrapper pattern as .applied-row-cell; kept under its own class so
   future tweaks don't entangle the two screens. */
fluent-data-grid-row.suggestion-row-clickable:hover {
    background: var(--neutral-fill-stealth-hover);
}

.suggestion-row-cell {
    cursor: pointer;
    width: 100%;
    min-height: 100%;
    display: flex;
    align-items: center;
}

/* Page-level back-navigation row. Convention: back navigation lives on
   its own row above the page's content cards, never nested inside a
   card or inline with a multi-line title block. The negative margin
   tucks the row a few pixels closer to the card below so the back
   button reads as a header for that card rather than as a separate
   block of chrome. See project CLAUDE.md "Back navigation" rule. */
.page-back-row {
    margin-bottom: -4px;
}

/* Job-title link in any FluentDataGrid title column. Plain <a> styled to
   match Fluent UI's hypertext appearance, but reliably clickable when
   wrapped in an overflow-clipped flex cell — the FluentAnchor web
   component had layout-vs-hit-test issues in this position. */
.job-title-link {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1;
    color: var(--accent-fill-rest);
    text-decoration: underline;
    cursor: pointer;
}

.job-title-link:hover {
    color: var(--accent-fill-hover);
    text-decoration: underline;
}

.job-title-link:focus-visible {
    outline: 2px solid var(--accent-fill-rest);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Application Workspace Documents panel header labels ("CV", "Cover
   Letter"). Without nowrap, "Cover Letter" wraps to two lines in a
   narrow panel while "CV" doesn't, making the two side-by-side panel
   headers different heights and breaking vertical alignment of
   everything below. FluentLabel doesn't forward `Style` or `Class`
   to its rendered <p class="fluent-typography">, so we hang the rule
   off the outer panel's class instead. Scoped to the first row of
   the panel (the header FluentStack) so the drop-zone "Drop a CV
   here..." paragraph further down in the same panel keeps its
   normal wrapping behaviour. */
.doc-panel > .stack-horizontal:first-child p.fluent-typography {
    white-space: nowrap;
}

/* FluentInputFile click dead-zones. The component renders the dotted
   drop container (.fluent-inputfile-container) with two grid-stacked
   children: a centred overlay (.inputfile-content) holding the text
   and progress bar, and a div wrapping the actual <input type="file">
   at opacity 0 covering the full container. The overlay has
   `z-index: 991` which puts it on top of the transparent input — so
   clicks on the centre of the box (where the text sits) hit the
   overlay, which has no handler, and do nothing. Only the exposed
   left/right edges open the file picker.
   Fix: pointer-events: none on the overlay so clicks pass through to
   the input. The overlay is display-only (text + progress bar), no
   interactive elements inside it need pointer events. */
.fluent-inputfile-container .inputfile-content {
    pointer-events: none;
}

/* Search Criteria page layout.
   The page is a stack of: header (title + subtitle + help link) → card
   (tabs + footer CTA) → "more actions" block (template suggestion +
   reset). The card gives the form a visible container and keeps the
   footer CTA anchored to the form it acts on, rather than floating in
   whitespace. Left-aligned and full-width to match the other settings
   pages (MyDocuments, SourceOfTruth) — the body-scroll-container's
   24px padding on the parent provides the left gutter. */
.criteria-page {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.criteria-header h1 {
    margin: 0 0 4px 0;
}

.criteria-header-subtitle {
    margin: 0;
    color: var(--neutral-foreground-hint);
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.criteria-card {
    background: var(--neutral-layer-1);
    border: 1px solid var(--neutral-stroke-divider-rest);
    border-radius: 8px;
    padding: 4px 24px 0 24px;
    display: flex;
    flex-direction: column;
}

.criteria-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 16px 0;
    margin-top: 8px;
    border-top: 1px solid var(--neutral-stroke-divider-rest);
}

/* Compensation tab: each payment mode (Permanent, Contract) is a small
   bordered group so the checkbox and its amount field read as coupled.
   Disabling the number field when the mode is off makes the
   relationship visually obvious without extra copy. */
.compensation-mode {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 16px;
    border: 1px solid var(--neutral-stroke-divider-rest);
    border-radius: 6px;
    min-width: 0;
    flex: 1 1 260px;
}


/* Inline text-link-styled button. Semantically a <button> (action, not
   navigation), visually a hyperlink. Used for "How scoring works" next
   to the page subtitle — opens a modal, so <a> would be misleading. */
.link-button {
    background: transparent;
    border: none;
    padding: 0;
    color: var(--accent-fill-rest);
    cursor: pointer;
    text-decoration: underline;
    font: inherit;
}

.link-button:hover {
    color: var(--accent-fill-hover);
}

.link-button:active {
    color: var(--accent-fill-active);
}

.link-button:focus-visible {
    outline: 2px solid var(--accent-fill-rest);
    outline-offset: 2px;
    border-radius: 2px;
}

.link-button:disabled {
    color: var(--neutral-foreground-disabled);
    cursor: not-allowed;
    text-decoration: none;
}

/* Job-card chrome. Self-contained bordered block used by ClosedApplications
   and (later) the JobListPage mobile reflow. The Kanban Board has its own
   .application-board-card class with the same base shape plus drag-cursor
   and SortableJS ghost states; the two were left as parallel classes rather
   than refactored together to keep this change blast-radius small. */
.job-card {
    background: var(--neutral-layer-1);
    border: 1px solid var(--neutral-stroke-rest);
    border-radius: 6px;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.job-card:hover {
    border-color: var(--accent-fill-rest);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
}

.job-card:focus-within {
    box-shadow: 0 0 0 2px var(--accent-fill-rest);
}

.job-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
}

@media (max-width: 600px) {
    .job-card-grid {
        grid-template-columns: 1fr;
    }
}

.job-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    flex-wrap: wrap;
}

.job-card-company {
    font-weight: 600;
    font-size: 0.95rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

/* Title link inside a card. Differs from .job-title-link (which is tuned
   for overflow-clipped table cells with display: block + ellipsis) — cards
   have room for titles to wrap, so we keep them inline-block and allow
   line-wrapping. Kept as plain <a> per the FluentAnchor clickability rule
   in CLAUDE.md. */
.job-card-title-link {
    color: var(--accent-fill-rest);
    text-decoration: underline;
    font-size: 0.9rem;
    line-height: 1.4;
    cursor: pointer;
}

.job-card-title-link:hover {
    color: var(--accent-fill-hover);
}

.job-card-title-link:focus-visible {
    outline: 2px solid var(--accent-fill-rest);
    outline-offset: 2px;
    border-radius: 2px;
}

.job-card-reason {
    margin: 4px 0 0 0;
    color: var(--neutral-foreground-hint);
    font-size: 0.85rem;
    line-height: 1.4;
}

.job-card-actions {
    display: flex;
    gap: 8px;
    margin-top: 4px;
    flex-wrap: wrap;
}

/* Filter disclosure on JobListPage mobile. The HeaderContent fragment
   (per-page checkbox rows — Review has up to 8 boxes across two stacks)
   overflows narrow viewports as a flat row. On mobile we collapse it into
   a <details> with a "Filters" summary; on desktop the same markup renders
   open and styled-flat so it reads as a normal filter bar. */
.filter-disclosure {
    margin: 0;
}

.filter-disclosure > summary {
    list-style: none;
    cursor: default;
    user-select: none;
    color: var(--neutral-foreground-hint);
    font-size: 0.85rem;
    font-weight: 600;
    padding: 4px 0;
    display: none;
}

.filter-disclosure > summary::-webkit-details-marker {
    display: none;
}

@media (max-width: 767px) {
    .filter-disclosure > summary {
        display: flex;
        align-items: center;
        gap: 6px;
        cursor: pointer;
        padding: 8px 12px;
        border: 1px solid var(--neutral-stroke-divider-rest);
        border-radius: 4px;
        background: var(--neutral-layer-1);
    }

    .filter-disclosure > summary:focus-visible {
        outline: 2px solid var(--accent-fill-rest);
        outline-offset: 2px;
    }

    .filter-disclosure > summary::after {
        content: "▾";
        margin-left: auto;
        transition: transform 120ms ease;
    }

    .filter-disclosure[open] > summary::after {
        transform: rotate(180deg);
    }

    .filter-disclosure[open] > .filter-disclosure-content {
        margin-top: 8px;
    }
}

/* Landing-form submit overlay. Created by JS in Landing.razor and attached
   to <body>, so it survives Blazor's enhanced-nav DOM morph (the form is
   replaced when _submitted becomes true). Lives in unscoped global CSS
   because the JS-created element has no Blazor scoping attribute. Bottom-
   centre toast — visible on mobile and desktop without obscuring content. */
.landing-submit-overlay {
    position: fixed;
    inset-inline: 0;
    bottom: max(24px, env(safe-area-inset-bottom));
    display: flex;
    justify-content: center;
    pointer-events: none;
    z-index: 1000;
}

.landing-submit-overlay-inner {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    padding: 12px 20px;
    border-radius: 999px;
    background: var(--ck-neutral-900, #0F172A);
    color: white;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.25);
    font-size: 15px;
    font-weight: 500;
    max-width: calc(100% - 32px);
}

.landing-submit-overlay-spinner {
    width: 16px;
    height: 16px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    flex-shrink: 0;
    animation: landing-submit-spin 0.7s linear infinite;
}

.landing-submit-overlay-label {
    line-height: 1.2;
}

@keyframes landing-submit-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .landing-submit-overlay-spinner {
        animation: none;
    }
}

/* Submit-feedback spinner for static SSR auth forms. Driven by
   wwwroot/js/form-spinner.js — see that file's header for the contract.
   Opt in by giving any submit button the `js-form-spinner-btn` class plus
   `.form-submit-label` and `.form-submit-spinner` child spans. */
.form-submit-spinner {
    display: none;
    align-items: center;
    justify-content: center;
    gap: 8px;
}

.is-submitting .form-submit-label {
    display: none;
}

.is-submitting .form-submit-spinner {
    display: inline-flex;
}

.form-submit-spinner svg {
    animation: form-submit-spin 0.8s linear infinite;
}

@keyframes form-submit-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .form-submit-spinner svg {
        animation: none;
    }
}
