/* ==========================================================================
   RT — SHARED COMPONENT LAYER  (batch 2 of the app-wide RT rollout, 2026-08-17)
   --------------------------------------------------------------------------
   Source of truth for tokens : Design Reference/rt-design-system/tokens.json
   Token layer                : static/css/rt-dashboard.css
   Loaded                     : globally, from templates/layouts/base.html,
                                immediately after rt-dashboard.css.

   WHAT THIS FILE IS FOR
   Batch 1 made RT the app-wide default. Because the RT tokens are declared on
   body[data-ds="rt"] and every page is now that, the 29 screens that had never
   been migrated INHERITED the RT look for free — measured in a real browser:
   6px radii, coral primary buttons, neutral table heads, flat bordered toolbars.

   So this file is deliberately NOT a restyle. It fixes the four RULE-LEVEL
   defects that tokens cannot express, each measured before it was touched, and
   each living in a component shared by all 19 master screens:

     1  .master-page capped at 1280px -> a 180px dead right gutter at 1880px
     2  .master-table-wrap still floating on a two-layer shadow with no border
     3  the empty state rendered 1886px wide inside a 1280px scroll viewport,
        so "No employees yet" sat off-screen
     4  every shared control below the 40px tap floor at 390px

   WHY A SEPARATE FILE AND NOT JUST main.css
   The canonical values ARE also corrected in main.css — this file does not
   fight it, it agrees with it. It exists because main.css is ~108KB and months
   old, so browsers hold it, and a stale cached copy has already shipped a
   visible defect to this user once (see the collectstatic/cached-CSS note in
   project memory). A brand-new filename has no cache entry and loads
   immediately, so the fixes land even on a stale main.css. Duplicating fifteen
   declarations is the right trade against that.

   SCOPE
   Everything is under body[data-ds="rt"], which is every page. Keeping the
   scope makes the file consistent with the other rt-*.css files and keeps it
   removable in one edit.
   ========================================================================== */


/* ==========================================================================
   1 · PAGE WRAPPER — no dead gutters on a wide monitor
   --------------------------------------------------------------------------
   .master-page was `max-width: 1280px; margin: 0 auto`. On a 1880px window the
   content body is 1640px wide, so the page centred at 1280 and left a MEASURED
   180px dead band down each side while the table inside it was horizontally
   scrolling. The RT screen wrappers were fixed the same way on 2026-08-01
   (.pd-wrap: padding 0, max-width 1920px) and this is the same fix arriving at
   the master screens, exactly as the rollout note predicted it would need to.

   Horizontal padding stays 0: .content-body already supplies 24px 32px, and
   adding more here is what produced the doubled-padding band on the dashboard.
   ========================================================================== */
body[data-ds="rt"] .master-page {
    max-width: 1920px;
    padding: 0 0 var(--space-4);
    margin: 0 auto;
}

/* Both hub screens (Settings and Reports) use .settings-page, NOT .master-page,
   so the rule above misses them entirely. There is no .reports-page class —
   assuming one is the easy mistake here. */
body[data-ds="rt"] .settings-page {
    max-width: 1920px;
}

/* A fixed three-column grid at 1920px stretches each tile to ~620px, which
   leaves the icon and two lines of copy adrift in a very wide box. auto-fit
   keeps the tile size honest and fills the row — the same reason the dashboard
   attention feed uses auto-fit rather than a hard column count. */
body[data-ds="rt"] .settings-grid {
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
}


/* ==========================================================================
   2 · TABLE SHELL — the border carries the edge, not a shadow
   --------------------------------------------------------------------------
   main.css says, in a comment, "Border removed — the soft shadow now defines
   the edge (floats on the wash)". That is the Notion Calendar reading. RT is
   the opposite: a 1px border defines the edge and elevation nearly vanishes.
   The visible symptom was a flat bordered .master-toolbar sitting directly
   above a table card floating on a two-layer shadow — two systems, 8px apart.

   RT also has no 12px radius anywhere, so --radius-lg resolves to 6px through
   the token layer; it is named rather than hardcoded so it tracks the tokens.
   ========================================================================== */
body[data-ds="rt"] .master-table-wrap {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-background);
    box-shadow: none;
}

/* The scroll-affordance fade on a wide table has to sit on the surface it
   fades, or it shows as a grey smear over the border. */
body[data-ds="rt"] .master-table-wrap--scrollable::after {
    background: linear-gradient(90deg,
        rgba(255, 255, 255, 0) 0%, var(--color-background) 78%);
}

/* Table head: neutral fill, hairline rule, no uppercase (see the no-ALL-CAPS
   rule). Restated here because a stale main.css is the cache risk this file
   exists to cover. */
body[data-ds="rt"] .master-table thead th {
    background: var(--color-surface-secondary);
    color: var(--color-text-secondary);
    text-transform: none;
    border-bottom: 1px solid var(--color-border);
}


/* ==========================================================================
   3 · EMPTY STATE — must not be pushed off the side of a wide table
   --------------------------------------------------------------------------
   THE DEFECT, measured on Employee Master with zero employees:
       .master-table--wide  min-width: 1800px   -> table renders 1886px
       .master-empty-cell   colspan across it   -> empty state 1886px wide
       .master-table-wrap   clientWidth 1280px  -> visible viewport
   The empty state centres itself in 1886px, so its icon and both lines of
   copy sit at ~943px — beyond the 1280px the user can see. The message that
   tells a new user how to create their first record was only readable after
   scrolling right. It bites every wide master table in its zero-row state,
   which is exactly the state a first-time user is in.

   THE FIX
   A table with no rows has nothing to lay out, so it does not need the 1800px
   floor. :has() lets that be expressed in CSS with no template edit across 19
   files and no JS. Where :has() is unsupported the rule simply does not apply
   and the behaviour is today's, so this degrades rather than breaks.

   .master-table--wide also sets `max-width: 180px; overflow: hidden;
   white-space: nowrap` on EVERY td. The empty cell is a td, so those have to
   be undone explicitly or the fix above just produces a 180px-wide clipped
   message instead of an off-screen one.
   ========================================================================== */
body[data-ds="rt"] .master-table:has(.master-empty-cell) {
    min-width: 0;
    width: 100%;
    /* table-layout:fixed is load-bearing, not tidiness. With `auto`, the table
       still cannot shrink below the MIN-CONTENT width of its 14 header cells —
       about 1000px — so at 390px the wrap kept scrolling and the empty message
       centred itself off-screen all over again. Dropping min-width alone fixed
       the 1880px case and left the phone case broken, which is precisely why
       this was measured at both widths and not just the one. */
    table-layout: fixed;
}

/* A column header row with no rows under it is noise, and it is also the only
   thing forcing the table wider than the viewport in the empty state. Hiding it
   lets the empty message occupy the full visible width at every size, which is
   how every other RT empty state in the app already reads. */
body[data-ds="rt"] .master-table:has(.master-empty-cell) thead {
    display: none;
}

body[data-ds="rt"] .master-table--wide td.master-empty-cell,
body[data-ds="rt"] .master-table td.master-empty-cell {
    max-width: none;
    overflow: visible;
    white-space: normal;
}

body[data-ds="rt"] .master-empty-state {
    max-width: 520px;
    margin-left: auto;
    margin-right: auto;
}

body[data-ds="rt"] .master-empty-title {
    color: var(--color-text-primary);
}

body[data-ds="rt"] .master-empty-desc {
    color: var(--color-text-muted);
    white-space: normal;
}

body[data-ds="rt"] .master-empty-icon {
    background: var(--color-surface-secondary);
    border: 1px solid var(--color-border);
    color: var(--color-text-muted);
    border-radius: var(--radius-lg);
}


/* ==========================================================================
   4 · THE 40px TAP FLOOR — declared ONCE, for every screen in the app
   --------------------------------------------------------------------------
   This is the part that stops being copied per screen.

   Every RT screen so far has carried its own copy of this block, because the
   shared controls live outside any screen's namespace: .theme-btn and
   .hamburger-btn are in the app chrome, .ch-btn / .ch-cycle are in the shared
   header, .ms-filter-* is the shared multi-select, .btn is the shared button,
   .modal-* is the shared dialog. Every one of them has been missed by at least
   one screen's block, and the executive screens shipped without one for six
   days. Now that RT is the only system there is no reason for that list to
   live anywhere but here, once.

   MEASURED at 390px on the master screens before writing this (with
   offsetHeight — getBoundingClientRect returns the TRANSFORMED box, and closed
   menus and modals carry a scale(), which has sent three builds chasing a
   ghost 38px):
       .btn 26-28   .btn-sm 26-28   .master-page-size 26   .theme-btn 32
       .ch-btn 32   .master-dropdown-item 32/36   .master-action-btn 30
       .master-search-input 34   .ms-filter-btn 34   .ms-filter-item 34
       .hamburger-btn 36

   The per-screen copies in the rt-*.css files are now redundant. They are left
   in place: they set the same 40px and their probes assert them, so removing
   them is churn with a test cost and no user-visible effect.

   min-height on an inline element does nothing, so anything that is a <span>
   or an <a> without a display gets one here.
   ========================================================================== */
@media (max-width: 900px) {
    /* Shared buttons and the shared dialog. */
    body[data-ds="rt"] .btn,
    body[data-ds="rt"] .btn-sm,
    body[data-ds="rt"] .btn-primary,
    body[data-ds="rt"] .btn-secondary,
    body[data-ds="rt"] .btn-ghost {
        min-height: 40px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    body[data-ds="rt"] .modal-close-btn {
        min-width: 40px;
        min-height: 40px;
    }

    /* App chrome. .hamburger-btn lives in .mobile-header, a SIBLING of
       .content-header — scoping it under a header class silently misses it. */
    body[data-ds="rt"] .theme-btn {
        width: 40px;
        height: 40px;
    }
    body[data-ds="rt"] .hamburger-btn,
    body[data-ds="rt"] .hamburger {
        width: 40px;
        height: 40px;
    }
    body[data-ds="rt"] .ch-btn,
    body[data-ds="rt"] .ch-cycle {
        min-height: 40px;
        height: 40px;
    }

    /* Shared multi-select filter. */
    body[data-ds="rt"] .ms-filter-btn {
        min-height: 40px;
        height: auto;
    }
    body[data-ds="rt"] .ms-filter-item {
        min-height: 40px;
        display: flex;
        align-items: center;
    }
    body[data-ds="rt"] .ms-filter-clear {
        min-height: 40px;
    }

    /* Master-screen controls. */
    body[data-ds="rt"] .master-search-input {
        height: 40px;
    }
    body[data-ds="rt"] .master-page-size {
        min-height: 40px;
        height: 40px;
    }
    body[data-ds="rt"] .master-action-btn {
        min-width: 40px;
        min-height: 40px;
        height: 40px;
    }
    body[data-ds="rt"] .master-dropdown-item {
        min-height: 40px;
        display: flex;
        align-items: center;
    }
    body[data-ds="rt"] .master-filter-select {
        min-height: 40px;
    }
    body[data-ds="rt"] .master-pagination-nav .btn {
        min-height: 40px;
        min-width: 40px;
    }

    /* The toolbar stacks rather than squeezing four filters and three actions
       into 390px. The search box takes the full first row: it is the control
       people reach for first, and at a shared row width it was 90px wide. */
    body[data-ds="rt"] .master-toolbar {
        flex-wrap: wrap;
        align-items: stretch;
        gap: var(--space-2);
    }
    body[data-ds="rt"] .master-search {
        flex: 1 1 100%;
    }
    /* THE FILTER ROW — this needed diagnosing, not guessing.
       main.css already sets `.master-toolbar-filters { flex-direction: column }`
       in its own mobile block. That was written when the filters were
       `.master-filter-select`, which the same block gives `width: 100%`. The
       selects were later replaced by the shared `.ms-filter` multi-select, which
       takes its width from its content — so in a column with centred alignment
       each one became a narrow pill on a row of its own: four pills, four rows,
       all centred, in a 390px viewport.

       Setting flex-wrap and justify-content is useless against
       flex-direction: column, which is why the first attempt at this measured as
       applied and changed nothing on screen. The direction itself has to go back
       to row. Two filters per row, each filling its half. */
    body[data-ds="rt"] .master-toolbar-filters {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: stretch;
        justify-content: flex-start;
        width: 100%;
        gap: var(--space-2);
    }
    body[data-ds="rt"] .master-toolbar-filters .ms-filter {
        flex: 1 1 calc(50% - var(--space-2));
        min-width: 0;
    }
    body[data-ds="rt"] .master-toolbar-filters .ms-filter-btn {
        width: 100%;
        justify-content: space-between;
    }
    body[data-ds="rt"] .master-toolbar-actions {
        flex-wrap: wrap;
        justify-content: flex-start;
        width: 100%;
    }
}

/* ==========================================================================
   4b · THE SAME 40px FLOOR ON EVERY TOUCH SCREEN (11-Sep-2026)
   The block above is width-scoped, so an iPad in landscape (1024–1366px)
   got 26px desktop buttons under a finger. This repeats ONLY the sizing
   rules for any coarse pointer, whatever the width. Layout changes (the
   stacked toolbar) stay in the width block — a wide tablet does not need them.
   ========================================================================== */
@media (pointer: coarse) {
    body[data-ds="rt"] .btn,
    body[data-ds="rt"] .btn-sm,
    body[data-ds="rt"] .btn-primary,
    body[data-ds="rt"] .btn-secondary,
    body[data-ds="rt"] .btn-ghost {
        min-height: 40px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    body[data-ds="rt"] .modal-close-btn,
    body[data-ds="rt"] .master-action-btn,
    body[data-ds="rt"] .master-pagination-nav .btn {
        min-width: 40px;
        min-height: 40px;
    }
    body[data-ds="rt"] .theme-btn,
    body[data-ds="rt"] .hamburger-btn,
    body[data-ds="rt"] .hamburger {
        width: 40px;
        height: 40px;
    }
    body[data-ds="rt"] .ch-btn,
    body[data-ds="rt"] .ch-cycle,
    body[data-ds="rt"] .master-search-input,
    body[data-ds="rt"] .master-page-size {
        min-height: 40px;
        height: 40px;
    }
    body[data-ds="rt"] .ms-filter-btn,
    body[data-ds="rt"] .ms-filter-clear,
    body[data-ds="rt"] .master-filter-select {
        min-height: 40px;
    }
    body[data-ds="rt"] .ms-filter-btn { height: auto; }
    body[data-ds="rt"] .ms-filter-item,
    body[data-ds="rt"] .master-dropdown-item {
        min-height: 40px;
        display: flex;
        align-items: center;
    }
    /* Row and header tick boxes: 20px box; the cell padding supplies the rest. */
    body[data-ds="rt"] .bulk-row,
    body[data-ds="rt"] .bulk-select-all {
        width: 20px;
        height: 20px;
    }
}


/* ==========================================================================
   5 · TWO MORE SHARED CONTROLS, FOUND BY MEASURING BATCH 3's SCREENS
   --------------------------------------------------------------------------
   Batch 3 was meant to be seven screens with bespoke inline CSS. Probing them
   found almost nothing screen-specific — but it did find two SHARED components
   below the tap floor, which is the same shape of defect batch 2 fixed and
   belongs in the same place rather than in seven templates.
   ========================================================================== */

/* .pms-check — a checkbox with its text label, the standard inline pattern.
   MEASURED on Email Notifications at 390px: nineteen of these, each a 20px
   label wrapping a 13px box. The LABEL is the tap target; the box never is, so
   raising the box would be both ugly and pointless. Defined as a class because
   the screen had it as an inline style, which no stylesheet can raise cleanly.
   The 24px minimum below is WCAG 2.5.8; the 40px floor at phone width is this
   project's own bar and lives in the media block further down. */
body[data-ds="rt"] .pms-check {
    /* main.css styles bare `label` with margin-bottom:6px, which is meant for
       a field CAPTION sitting above its input. A label that WRAPS its own
       control is not a caption, and that phantom 6px lifted this checkbox off
       the baseline of the button beside it — MEASURED top=0 bottom=6 against
       its flex parent on Email Notifications. */
    margin-bottom: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 24px;
    font-size: var(--font-size-sm);
    color: var(--color-text-primary);
    cursor: pointer;
}
body[data-ds="rt"] .pms-check input[type="checkbox"] {
    flex-shrink: 0;
    accent-color: var(--color-primary);
}

/* .pms-tab — the shared tab strip. MEASURED 38px: `padding: 10px 18px` with no
   min-height, two pixels under the floor, on EIGHT screens (Bell Curve,
   Enrollment, Finance, Approvals, HR Review, Increment Approval, Increments
   Review, Roles & Permissions). Two pixels is not worth eight per-screen
   overrides. Raised here once.
   Roles & Permissions was the screen that surfaced it, via its .rp-tab JS hook —
   the visuals were already coming from this shared component, so fixing .rp-tab
   would have fixed one screen and hidden the other seven. */
@media (max-width: 900px) {
    body[data-ds="rt"] .pms-tab {
        min-height: 40px;
        display: inline-flex;
        align-items: center;
    }
    body[data-ds="rt"] .pms-check {
        min-height: 40px;
    }
}

/* ==========================================================================
   ERROR MODAL — app-wide (2026-08-30)
   Replaces the top pink err-banner as the destination for every error.
   Declared here rather than in main.css because main.css is ~108KB and long
   cached; a new global component must not depend on a same-build main.css.
   The banner rules in main.css are left in place and unreferenced.

   Rules this component keeps:
     - deep red on pink, never coral (RT's danger sits too close to primary)
     - dismissed only by the X, Close or Esc — NEVER by a backdrop click, so a
       blocking error cannot be waved away by accident
     - no auto-dismiss
     - every error answers what happened, why, and how to resolve it
   ========================================================================== */
.pms-err-overlay {
    position: fixed;
    inset: 0;
    background: rgba(17, 24, 39, .45);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 1200;
    padding: 3vh 3vw;
}
.pms-err-overlay.is-open { display: flex; }

.pms-err {
    width: 560px;
    max-width: 100%;
    max-height: 92vh;
    background: var(--color-surface, #fff);
    border: 1px solid #F1B0B0;
    border-radius: var(--radius-lg, 8px);
    box-shadow: 0 1px 2px rgba(9, 9, 11, .04), 0 12px 34px -12px rgba(9, 9, 11, .28);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.pms-err-head {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 18px 20px 14px;
    background: #FDECEC;
    border-bottom: 1px solid #F1B0B0;
    flex: 0 0 auto;
}
.pms-err-icon {
    width: 32px;
    height: 32px;
    flex: 0 0 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #F4CCCC;
    color: #C00000;
}
.pms-err-title {
    margin: 0;
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-snug);
    color: #C00000;
}
.pms-err-code {
    margin: 3px 0 0;
    font-size: var(--fs-11, 11px);
    color: #C00000;
    opacity: .85;
    font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
}
.pms-err-code[hidden] { display: none !important; }
.pms-err-x {
    margin-left: auto;
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm, 6px);
    color: #C00000;
    cursor: pointer;
    flex: 0 0 40px;
}
.pms-err-x:hover { background: #F4CCCC; }
.pms-err-x:focus-visible { outline: 2px solid #C00000; outline-offset: 2px; }

.pms-err-body {
    padding: 18px 20px 4px;
    overflow-y: auto;
    flex: 1 1 auto;
}
.pms-err-body section { margin-bottom: 16px; }
.pms-err-body h4 {
    margin: 0 0 4px;
    font-size: var(--fs-12, 12px);
    font-weight: var(--font-weight-bold);
    color: var(--color-text-secondary, #3F3F46);
}
.pms-err-body p {
    margin: 0;
    font-size: var(--font-size-body);
    line-height: var(--line-height-normal);
    color: var(--color-text-secondary, #3F3F46);
}
.pms-err-body ol {
    margin: 6px 0 0;
    padding-left: 20px;
    font-size: var(--font-size-body);
    line-height: var(--line-height-relaxed);
    color: var(--color-text-secondary, #3F3F46);
}
.pms-err-body li { margin-bottom: 4px; }

.pms-err-foot {
    padding: 14px 20px;
    border-top: 1px solid var(--color-border, #E4E4E7);
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    flex: 0 0 auto;
    flex-wrap: wrap;
}
.pms-err-foot .btn { min-height: 40px; }
.pms-err-foot .btn--danger {
    background: #C00000;
    border-color: #C00000;
    color: #fff;
}
.pms-err-foot .btn--danger:hover { background: #A30000; border-color: #A30000; }

@media (max-width: 560px) {
    .pms-err-head { padding: 14px 16px 12px; }
    .pms-err-body { padding: 14px 16px 4px; }
    .pms-err-foot { padding: 12px 16px; }
    .pms-err-foot .btn { flex: 1 1 auto; justify-content: center; }
}


/* ==========================================================================
   Bulk selection (multi-select + delete) — shared by all 18 Settings tables
   Markup: core/partials/bulk_th.html, bulk_td.html, bulk_bar.html.
   Behaviour: window.pmsBulk in app.js. Declared once here so every master
   gets the identical control, the 40px tap floor included.
   ========================================================================== */
.master-table .bulk-th,
.master-table .bulk-td {
    width: 40px;
    min-width: 40px;
    max-width: 40px;
    padding-left: var(--space-3, 12px);
    padding-right: 0;
    text-align: center;
    vertical-align: middle;
}
/* .master-table--wide clips every td to 180px/ellipsis; the checkbox cell
   must stay visible and unclipped. */
.master-table .bulk-td { overflow: visible; white-space: nowrap; }

.bulk-select-all,
.bulk-row {
    width: 16px;
    height: 16px;
    margin: 0;
    accent-color: var(--color-primary, #f2542d);
    cursor: pointer;
    vertical-align: middle;
}
.bulk-select-all:focus-visible,
.bulk-row:focus-visible {
    outline: 2px solid var(--color-primary, #f2542d);
    outline-offset: 2px;
}

/* A ticked row reads as selected without relying on the checkbox alone. */
.master-table tbody tr.bulk-selected > td {
    background: var(--color-primary-tint, rgba(242, 84, 45, 0.10));
}

/* The action bar. Sits between the record count and the table, hidden
   until at least one row is ticked. */
.bulk-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3, 12px);
    padding: var(--space-2, 8px) var(--space-4, 16px);
    margin-bottom: var(--space-3, 12px);
    background: var(--color-surface-secondary, #f4f4f5);
    border: 1px solid var(--color-border, #e4e4e7);
    border-radius: var(--radius-md, 6px);
    font-size: var(--font-size-sm);
}
.bulk-bar[hidden] { display: none; }
.bulk-bar-count {
    font-weight: var(--font-weight-medium, 500);
    color: var(--color-text, #18181b);
}
.bulk-bar-spacer { flex: 1 1 auto; }
.bulk-bar-link {
    background: none;
    border: 0;
    padding: 0 var(--space-1, 4px);
    min-height: 40px;
    color: var(--color-primary, #f2542d);
    font: inherit;
    font-weight: var(--font-weight-medium, 500);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.bulk-bar-link:hover { color: var(--color-primary-hover, #db4620); }
.bulk-bar-link[hidden] { display: none; }
.bulk-bar .btn { min-height: 40px; }
.bulk-bar .btn-danger {
    background: var(--color-error, #dc2626);
    border-color: var(--color-error, #dc2626);
    color: #fff;
}
.bulk-bar .btn-danger:hover:not(:disabled) {
    background: var(--color-error-text, #991b1b);
    border-color: var(--color-error-text, #991b1b);
}
.bulk-bar .btn:disabled { opacity: .6; cursor: progress; }

@media (max-width: 640px) {
    .bulk-bar-spacer { display: none; }
    .bulk-bar .btn { flex: 1 1 calc(50% - var(--space-3, 12px)); justify-content: center; }
    .bulk-bar-count, .bulk-bar-link { flex: 1 1 100%; text-align: left; }
}


/* ==========================================================================
   Scope picker (Assign Role modal) — cascading division → department /
   sub-division lists with search. Markup in core/assignment_modal.html.
   ========================================================================== */
.scope-picker .scope-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3, 12px);
}
.scope-picker .scope-head label { margin: 0; }
.scope-picker .scope-count {
    font-weight: var(--font-weight-regular);
    color: var(--color-text-muted, #66666f);
    font-size: var(--font-size-xs);
    margin-left: var(--space-2, 8px);
}
.scope-picker .scope-link {
    background: none;
    border: 0;
    padding: 0;
    min-height: 24px;
    color: var(--color-primary, #f2542d);
    font: inherit;
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium, 500);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.scope-picker .scope-search {
    width: 100%;
    margin: var(--space-2, 8px) 0;
    padding: 6px 10px;
    min-height: 34px;
    font: inherit;
    font-size: var(--font-size-sm);
    border: 1px solid var(--color-border, #e4e4e7);
    border-radius: var(--radius-sm, 4px);
    background: var(--color-background, #fff);
}
.scope-picker .scope-list {
    max-height: 180px;
    overflow-y: auto;
    border: 1px solid var(--color-border, #e4e4e7);
    border-radius: var(--radius-sm, 4px);
    padding: var(--space-1, 4px) var(--space-3, 12px);
}
.scope-picker .scope-group-title {
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium, 500);
    color: var(--color-text-muted, #66666f);
    padding: var(--space-2, 8px) 0 var(--space-1, 4px);
    border-bottom: 1px solid var(--color-border, #e4e4e7);
    margin-bottom: var(--space-1, 4px);
}
.scope-picker .scope-group--flat .scope-group-title { display: none; }
.scope-picker .scope-item {
    display: flex;
    align-items: center;
    gap: var(--space-2, 8px);
    padding: var(--space-1, 4px) 0;
    font-size: var(--font-size-sm);
    cursor: pointer;
    margin: 0;
}
.scope-picker .scope-item input[type="checkbox"] {
    accent-color: var(--color-primary, #f2542d);
    width: 16px;
    height: 16px;
    margin: 0;
    flex: 0 0 16px;
}
.scope-picker .scope-prefix { color: var(--color-text-muted, #66666f); }
.scope-picker .scope-group:not(.scope-group--flat) .scope-prefix { display: none; }
.scope-picker .scope-item[hidden], .scope-picker .scope-group[hidden], .scope-picker .scope-none[hidden] { display: none; }
.scope-picker .scope-empty {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted, #66666f);
    margin: var(--space-2, 8px) 0;
}


/* ==========================================================================
   5 · RESPONSIVE COMPONENTS — Phase 1 (11-Sep-2026)
   --------------------------------------------------------------------------
   Plan: docs/responsive_phase1_component_plan_v1.md (approved by RT).
   Lives here, not in main.css, because this file loads on every page AFTER
   main.css and is not the long-cached one. Breakpoints are the three tokens
   only: phone ≤767px · tablet ≤1024px · desktop.
   Every component below is lifted from one that already ships; the source is
   named on each.
   ========================================================================== */

/* --------------------------------------------------------------------------
   C2 · KPI strips — reflow only; the tile itself is untouched.
   Source: .hrv-kpi (HR Review), the canonical flat tile. One phone column put
   five tiles (≈800px) above the first record; two columns halve that.
   !important because each screen's own stylesheet loads later and sets
   1fr at the same weight.
   -------------------------------------------------------------------------- */
@media (min-width: 768px) and (max-width: 1100px) {
    body[data-ds="rt"] :is(.hrv-kpis, .fnr-kpis, .iap-kpis, .inc-metrics, .ms-kpis,
                           .exr-kpis, .pay-kpis, .cfo-kpis, .ceo-kpis, .md-kpis, .eap-kpis) {
        grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
    }
}

/* T4 · Appraisal Approval used private breakpoints (840px / 600px), so it was
   2-up where every other strip was 3-up and 1-up where they were 2-up. It now
   reflows on the shared set. It keeps 4 / 2 / 1 rather than 5 / 3 / 2 because
   its card is horizontal — a figure beside a label — and needs the width. */
@media (min-width: 768px) and (max-width: 1100px) {
    body[data-ds="rt"] .summary-grid { grid-template-columns: repeat(2, 1fr) !important; }
}
@media (max-width: 767px) {
    body[data-ds="rt"] .summary-grid { grid-template-columns: 1fr !important; }
}
/* Phone: ONE row of tiles that swipes sideways (Phase 2 D5, 01-Oct-2026,
   approved by RT). The 2-up grid stacked five or six tiles 400-560px deep, so
   Increments Approval put its first employee card at 938px on an 844px phone;
   the swipe row puts it at 433px. Each tile is 44% wide, so the edge of the
   third tile shows that there is more. */
@media (max-width: 767px) {
    body[data-ds="rt"] :is(.hrv-kpis, .fnr-kpis, .iap-kpis, .inc-metrics, .ms-kpis,
                           .exr-kpis, .pay-kpis, .cfo-kpis, .ceo-kpis, .md-kpis, .eap-kpis) {
        display: flex !important;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scroll-snap-type: x mandatory;
        gap: 8px !important;
        padding-bottom: 4px;
    }
    body[data-ds="rt"] :is(.hrv-kpis, .fnr-kpis, .iap-kpis, .inc-metrics, .ms-kpis,
                           .exr-kpis, .pay-kpis, .cfo-kpis, .ceo-kpis, .md-kpis, .eap-kpis) > * {
        flex: 0 0 44% !important;
        min-width: 0;
        scroll-snap-align: start;
    }
}

/* --------------------------------------------------------------------------
   C2b · ONE TILE STANDARD — tile sizing review, 12-Sep-2026 (T1, T2, T3, T6)
   The C2 rule above reflows the strips together but says "the tile itself is
   untouched", and the tiles had drifted: padding ran 11px-20px across six
   conventions, the value was set at 20px, 24px or 36px, and the label was
   clamped on two screens and free on six — which is what made the same tile
   72px tall on one screen and 115px on another.

   The standard is .hrv-kpi (HR Review), which C2 already names as canonical.
   !important follows C2's own precedent and for C2's reason: each screen's
   stylesheet and its template <style> block both load after this file. The
   now-duplicated per-screen declarations have been deleted, so the source no
   longer states a value that does not apply.

   .summary-card (Appraisal Approval) is NOT here: it is a horizontal card —
   figure beside a label, with a 4px accent bar its left padding clears — not a
   vertical KPI tile. It takes the shared VALUE size and the shared breakpoints
   and keeps its own box. Forcing it into a vertical tile would break it.
   -------------------------------------------------------------------------- */
:root {
    --tile-pad: 11px 13px;                  /* .hrv-kpi, the canonical tile */
    --tile-label: var(--fs-11, 11px);       /* 5 of 9 screens already used it */
    --tile-label-lines: 2;
}

body[data-ds="rt"] :is(.hrv-kpi, .fnr-kpi, .iap-kpi, .inc-metric, .ms-kpi,
                       .exr-kpi, .pay-kpi, .cfo-kpi, .ceo-kpi, .md-kpi, .eap-kpi,
                       .stat-tile, .rating-tile) {
    padding: var(--tile-pad) !important;
    min-width: 0;
}

/* One label: same size, same clamp, same reserved height everywhere. Two lines
   is what the widest current label needs; reserving it keeps every tile in a
   strip the same height whether its label wraps or not. */
body[data-ds="rt"] :is(.hrv-kpi, .cfo-kpi, .ceo-kpi, .md-kpi, .eap-kpi) .l,
/* Appraisal Input (Phase 2 D3, 01-Oct-2026): its tiles sit five-across beside a
   chart, about 167px wide. A two-line reserve made both rows TALLER than before
   (116 / 100px), so these two rows reserve one line for the label and one for
   the note; both rows come out 85px. A note that does not fit ends in an
   ellipsis and rt-tooltip shows it in full. */
body[data-ds="rt"] :is(.row1-stats, .row2-ratings) { --tile-label-lines: 1; }
body[data-ds="rt"] :is(.row1-stats .st-sub, .row2-ratings .rt-sub) {
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
body[data-ds="rt"] :is(.fnr-kpi-label, .iap-kpi-label, .exr-kpi-label,
                       .pay-kpi-label, .ms-kpi-label, .inc-metric-l, .st-lbl, .rt-name) {
    font-size: var(--tile-label) !important;
    line-height: var(--line-height-snug);
    display: -webkit-box;
    -webkit-line-clamp: var(--tile-label-lines);
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: calc(var(--tile-label-lines) * 1.35em);
}

/* One value size, and — T6 — a number is never allowed to break. A figure that
   wraps mid-digits reads as a different number, which is worse than any layout
   problem it solves. Five screens allowed it via word-break:break-word or
   overflow-wrap:anywhere; only Finance Review had defended against it. */
body[data-ds="rt"] :is(.hrv-kpi, .cfo-kpi, .ceo-kpi, .md-kpi, .eap-kpi) .v,
body[data-ds="rt"] :is(.fnr-kpi-val, .iap-kpi-val, .exr-kpi-val,
                       .pay-kpi-val, .ms-kpi-val, .inc-metric-v, .st-val, .rt-count),
body[data-ds="rt"] .summary-card .card-value {
    font-size: var(--fs-kpi, 24px) !important;
    line-height: var(--line-height-flat);
    font-variant-numeric: tabular-nums;
    white-space: nowrap !important;
    word-break: normal !important;
    overflow-wrap: normal !important;
}

/* Phone: a money tile gets 80% of the swipe row. A 44% tile has a ~130px
   content box and "OMR 1,234,567.890" needs 239px, so a Group figure could not
   fit at any size worth reading; 80% gives ~260px and every realistic figure
   fits whole. Count tiles have no currency prefix and stay 44%. The three
   prefix classes (.cur, .u, .unit) are all that mark a money value here. */
@media (max-width: 767px) {
    body[data-ds="rt"] :is(.hrv-kpis, .fnr-kpis, .iap-kpis, .inc-metrics, .ms-kpis,
                           .exr-kpis, .pay-kpis, .cfo-kpis, .ceo-kpis, .md-kpis, .eap-kpis)
        > :has(.cur, .u, .unit) {
        flex-basis: 80% !important;
    }
}

/* --------------------------------------------------------------------------
   C3 · Wide tables on tablet and desktop: the identity column and the action
   column stay on screen while the middle scrolls.
   Source: Overview matrix (dashboard.html .pd-corner / .pd-div-cell) —
   position: sticky with an OPAQUE background and a z-index above the body.
   Row tints are translucent, so a pinned cell takes its colour from
   --rv-pin-bg, which the row sets; otherwise scrolled text shows through it.
   Applies to every .master-table and to any table given .rv-pin; add
   .rv-pin-last to pin that table's last column as well.
   The edge shadows appear only while there is more to scroll (app.js §RV).
   -------------------------------------------------------------------------- */
@media (min-width: 768px) {
    body[data-ds="rt"] :is(.master-table, table.rv-pin) tbody tr {
        --rv-pin-bg: var(--color-background);
    }
    body[data-ds="rt"] :is(.master-table, table.rv-pin) thead tr {
        --rv-pin-bg: var(--color-surface-secondary);
    }
    body[data-ds="rt"] :is(.master-table, table.rv-pin) tbody tr.bulk-selected {
        --rv-pin-bg: color-mix(in srgb, var(--color-primary) 10%, var(--color-background));
    }
    @media (hover: hover) and (pointer: fine) {
        body[data-ds="rt"] :is(.master-table, table.rv-pin) tbody tr:hover {
            --rv-pin-bg: color-mix(in srgb, var(--color-primary) 4%, var(--color-background));
        }
    }

    /* Left: the tick-box column (if any) and the first data column. */
    body[data-ds="rt"] :is(.master-table, table.rv-pin) :is(th, td):first-child,
    body[data-ds="rt"] :is(.master-table, table.rv-pin) :is(.bulk-th + th, .bulk-td + td) {
        position: sticky;
        left: 0;
        z-index: 1;
        background-color: var(--rv-pin-bg);
    }
    body[data-ds="rt"] :is(.master-table, table.rv-pin) :is(.bulk-th + th, .bulk-td + td) {
        left: 40px;   /* the bulk column is fixed at 40px (section above) */
    }
    /* Right: the action column. */
    body[data-ds="rt"] .master-table :is(.master-table-actions, .master-table-actions-th),
    body[data-ds="rt"] table.rv-pin :is(th, td).rv-pin-end,
    body[data-ds="rt"] table.rv-pin.rv-pin-last :is(th, td):last-child {
        position: sticky;
        right: 0;
        z-index: 1;
        background-color: var(--rv-pin-bg);
    }
    body[data-ds="rt"] :is(.master-table, table.rv-pin) thead :is(th:first-child, .bulk-th + th,
        .master-table-actions-th, th.rv-pin-end),
    body[data-ds="rt"] table.rv-pin.rv-pin-last thead th:last-child {
        z-index: 3;   /* above the sticky thead row (z 2) and the body cells */
    }
    /* Edge shadows, only while content is hidden on that side. */
    body[data-ds="rt"] .rv-scrolled :is(.master-table, table.rv-pin) :is(th, td):is(:first-child:not(.bulk-th):not(.bulk-td), .bulk-th + th, .bulk-td + td) {
        box-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.14);
    }
    body[data-ds="rt"] .rv-more :is(.master-table, table.rv-pin) :is(.master-table-actions, .master-table-actions-th, .rv-pin-end),
    body[data-ds="rt"] .rv-more table.rv-pin.rv-pin-last :is(th, td):last-child {
        box-shadow: -6px 0 8px -6px rgba(0, 0, 0, 0.14);
    }
    /* The pinned edge replaces the old right-edge scroll hint. */
    body[data-ds="rt"] .master-table-wrap--scrollable::after {
        display: none;
    }
}

/* --------------------------------------------------------------------------
   C4 · Master data on phones: STACKED ROWS (RT's choice, 11-Sep-2026).
   The same <table> — so tick-boxes, the row menu, HTMX sorting and paging
   keep working — lays each row out as a card. Each cell shows its column
   name from data-label, which app.js §RV copies from the header row (also
   after every HTMX swap), so no template repeats the header text.
   The header row becomes a strip of sort chips.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
    body[data-ds="rt"] .master-table-wrap {
        overflow: visible;
        border: 0;
        border-radius: 0;
        background: transparent;
    }
    body[data-ds="rt"] .master-table-wrap .master-table {
        display: block;
        width: 100%;
        min-width: 0 !important;
        font-size: var(--font-size-sm);
    }
    body[data-ds="rt"] .master-table thead {
        display: block;
        position: static;
    }
    body[data-ds="rt"] .master-table thead tr {
        display: flex;
        gap: 6px;
        overflow-x: auto;
        padding: 0 0 10px;
        scrollbar-width: none;
    }
    body[data-ds="rt"] .master-table thead tr::-webkit-scrollbar { display: none; }
    body[data-ds="rt"] .master-table thead th {
        flex: none;
        display: inline-flex;
        align-items: center;
        gap: 4px;
        min-width: 0 !important;
        min-height: 36px;
        padding: 0 12px;
        border: 1px solid var(--color-border);
        border-radius: var(--radius-full);
        background: var(--color-surface);
        font-size: var(--fs-12, 12px);
        letter-spacing: 0;
    }
    /* Only headers that DO something survive as chips. */
    body[data-ds="rt"] .master-table thead th:not(.sortable):not(.bulk-th) {
        display: none;
    }
    body[data-ds="rt"] .master-table thead th.bulk-th {
        width: auto;
        max-width: none;
        padding: 0 12px;
    }
    body[data-ds="rt"] .master-table tbody {
        display: block;
    }
    body[data-ds="rt"] .master-table tbody tr {
        position: relative;
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: 10px 14px;
        padding: 12px 14px;
        margin-bottom: 10px;
        border: 1px solid var(--color-border);
        border-radius: var(--radius-md, 8px);
        background: var(--color-background);
    }
    body[data-ds="rt"] .master-table tbody tr.bulk-selected {
        border-color: var(--color-primary);
    }
    body[data-ds="rt"] .master-table tbody td {
        display: block;
        height: auto;   /* the desktop 40px row floor must not stretch card fields */
        min-width: 0;
        max-width: none !important;
        padding: 0;
        border: 0;
        white-space: normal !important;
        overflow: visible !important;
        text-overflow: clip !important;
        overflow-wrap: anywhere;
    }
    body[data-ds="rt"] .master-table tbody tr.bulk-selected > td { background: transparent; }
    body[data-ds="rt"] .master-table tbody td[data-label]::before {
        content: attr(data-label);
        display: block;
        margin-bottom: 2px;
        font-size: var(--fs-12, 12px);
        font-weight: var(--font-weight-medium);
        color: var(--color-text-muted);
    }
    /* Names run the full width and read as the card's title. */
    body[data-ds="rt"] .master-table tbody td.master-table-name {
        grid-column: 1 / -1;
        font-size: var(--font-size-md);
        font-weight: var(--font-weight-semibold);
    }
    /* Tick-box in the top-right corner of the card; room is made for it. */
    body[data-ds="rt"] .master-table tbody tr:has(> .bulk-td) {
        padding-right: 48px;
    }
    body[data-ds="rt"] .master-table tbody td.bulk-td {
        position: absolute;
        top: 8px;
        right: 8px;
        width: 40px !important;
        min-width: 40px;
        height: 40px;
        padding: 0;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    body[data-ds="rt"] .master-table tbody td.bulk-td::before { content: none; }
    /* Row actions: a footer across the card. */
    body[data-ds="rt"] .master-table tbody td.master-table-actions {
        grid-column: 1 / -1;
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: 4px;
        padding-top: 8px;
        border-top: 1px solid var(--color-border);
    }
    body[data-ds="rt"] .master-table tbody td.master-table-actions::before { content: none; }
    /* Cells a master hides on small screens stay hidden. */
    body[data-ds="rt"] .master-table :is(th, td).hide-mobile { display: none !important; }
    /* Empty state and any full-width row. */
    body[data-ds="rt"] .master-table tbody td[colspan] { grid-column: 1 / -1; }
    body[data-ds="rt"] .master-table tbody td[colspan]::before { content: none; }
    body[data-ds="rt"] .master-table tbody tr:has(> td.master-empty-cell) {
        border-style: dashed;
    }
}

/* --------------------------------------------------------------------------
   C5 · Division decision cards for the ED & Group CEO and MD gates, below
   desktop. Look ported value-for-value from the Group CFO division board
   (.cfo-board / .cfo-card, rt-cfo-cockpit.css): background, 1px border,
   8px radius, 15px 16px padding, 11px rhythm, 14px/700 name, 10.5px labels.
   Mechanism: the SAME table stacks (as C4), so each division's figures and
   its two decision buttons exist once — the buttons are never duplicated
   and never off-screen. Add .rv-dcards to the table; mark the identity cell
   .rv-c-head, the decision cell .rv-c-act, a cell not needed on a card
   .rv-c-hide, and a note row that belongs to the card above .rv-c-note.
   -------------------------------------------------------------------------- */
@media (max-width: 1024px) {
    body[data-ds="rt"] :has(> table.rv-dcards) {
        overflow: visible !important;
        border: 0 !important;
        background: transparent !important;
    }
    body[data-ds="rt"] table.rv-dcards {
        display: block;
        position: relative;   /* contains the absolutely-positioned caption and
                                 thead; without it they hung off the initial
                                 containing block and gave the whole page a
                                 second scrollbar (MD Final Approval, phone) */
        width: 100%;
        min-width: 0 !important;
    }
    body[data-ds="rt"] table.rv-dcards tbody td :is(.md-pill, .ceo-pill) {
        white-space: normal;
        max-width: 100%;
    }
    body[data-ds="rt"] table.rv-dcards thead {
        position: absolute;
        width: 1px; height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }
    body[data-ds="rt"] table.rv-dcards tbody { display: block; }
    body[data-ds="rt"] table.rv-dcards tbody tr {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 11px 14px;
        padding: 15px 16px;
        margin-bottom: 13px;
        background: var(--color-background);
        border: 1px solid var(--color-border);
        border-radius: var(--radius-md, 8px);
    }
    body[data-ds="rt"] table.rv-dcards tbody tr.muted { background: var(--color-surface); }
    body[data-ds="rt"] table.rv-dcards tbody td {
        display: block;
        min-width: 0;
        padding: 0 !important;
        border: 0 !important;
        text-align: left !important;
        white-space: normal;
        background: transparent !important;
        font-variant-numeric: tabular-nums;
    }
    body[data-ds="rt"] table.rv-dcards tbody td[data-label]::before {
        content: attr(data-label);
        display: block;
        margin-bottom: 2px;
        font-size: var(--fs-12, 12px);
        font-weight: 500;
        color: var(--color-text-muted);
    }
    body[data-ds="rt"] table.rv-dcards tbody td:is(.rv-c-head, .rv-c-act)::before { content: none; }
    body[data-ds="rt"] table.rv-dcards tbody td.rv-c-head { grid-column: 1 / -1; }
    body[data-ds="rt"] table.rv-dcards tbody td.rv-c-hide { display: none !important; }
    body[data-ds="rt"] table.rv-dcards tbody td.rv-c-act {
        grid-column: 1 / -1;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 8px;
        padding-top: 11px !important;
        border-top: 1px solid var(--color-border) !important;
    }
    body[data-ds="rt"] table.rv-dcards tbody td.rv-c-act .btn { flex: 1 1 180px; }
    /* A note row joins the card above it into one surface. */
    body[data-ds="rt"] table.rv-dcards tbody tr:has(+ tr.rv-c-note) {
        margin-bottom: 0;
        border-bottom: 0;
        border-radius: var(--radius-md, 8px) var(--radius-md, 8px) 0 0;
    }
    body[data-ds="rt"] table.rv-dcards tbody tr.rv-c-note {
        display: block;
        padding: 0 16px 13px;
        margin-bottom: 13px;
        border-top: 0;
        border-radius: 0 0 var(--radius-md, 8px) var(--radius-md, 8px);
        font-size: var(--fs-12, 12px);
        line-height: var(--line-height-normal);
        color: var(--color-text-secondary);
    }
    body[data-ds="rt"] table.rv-dcards tbody tr.rv-c-note > td::before { content: none; }
}
@media (max-width: 480px) {
    body[data-ds="rt"] table.rv-dcards tbody td.rv-c-act .btn { flex-basis: 100%; }
}

/* --------------------------------------------------------------------------
   C1 · Record list, table and card views of the same rows.
   Source: Increments Review (.view-toggle / .vt-btn, one wrapper carrying
   the view, cards forced on phones). Shared names so screens stop carrying
   their own copy: wrapper .rv-wrap[data-rv="table|cards"], blocks .rv-table
   and .rv-cards, switch core/partials/_view_toggle.html, behaviour
   window.rtViewSwitch (app.js §RV). Screens adopt it in Phase 2.
   -------------------------------------------------------------------------- */
body[data-ds="rt"] .rv-wrap .rv-cards { display: none; }
body[data-ds="rt"] .rv-wrap[data-rv="cards"] .rv-table { display: none; }
body[data-ds="rt"] .rv-wrap[data-rv="cards"] .rv-cards { display: block; }
body[data-ds="rt"] .rv-viewbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
body[data-ds="rt"] .rv-viewbar .sp { flex: 1 1 auto; }
body[data-ds="rt"] .rv-viewbar-label { font-size: var(--fs-12, 12px); font-weight: 500; color: var(--color-text-muted); }
body[data-ds="rt"] .view-toggle {
    display: inline-flex; flex-shrink: 0; overflow: hidden;
    border: 1px solid var(--color-border); border-radius: var(--radius-sm, 6px);
    background: var(--color-surface);
}
body[data-ds="rt"] .vt-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; padding: 0; border: none; background: transparent;
    color: var(--color-text-muted); cursor: pointer;
    transition: background-color 150ms ease, color 150ms ease;
}
body[data-ds="rt"] .vt-btn + .vt-btn { border-left: 1px solid var(--color-border); }
body[data-ds="rt"] .vt-btn.active { background: var(--color-primary-tint); color: var(--color-primary); }
@media (pointer: coarse) {
    body[data-ds="rt"] .vt-btn { width: 40px; height: 40px; }
}
@media (max-width: 767px) {
    body[data-ds="rt"] .rv-wrap .rv-table { display: none !important; }
    body[data-ds="rt"] .rv-wrap .rv-cards { display: block !important; }
    body[data-ds="rt"] .rv-viewbar .view-toggle { display: none; }
}

/* --------------------------------------------------------------------------
   C6 · Detail panels become bottom sheets on phones.
   Source: the Phase 0 shared dialog sheet (main.css). These panels do not use
   .modal-box, so each is named. Each keeps its own open/close switch.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
    /* HOD Approval dossier, Appraisal Details, HRBP detail. Open state is
       .open; the closed sheet waits below the screen edge. */
    body[data-ds="rt"] .slide-panel {
        top: auto;
        bottom: 0;
        left: 0;
        width: 100vw;
        max-width: 100vw;
        height: auto;
        max-height: 94vh;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
        transform: translateY(24px);
    }
    body[data-ds="rt"] .slide-panel.open { transform: none; }
    /* HR Review, Increment Approval, HRBP rating form. */
    body[data-ds="rt"] :is(.hrv-modal, .iap-modal-overlay, .paw-sheet-overlay) {
        align-items: flex-end;
        padding: 0;
    }
    body[data-ds="rt"] :is(.hrv-modal-box, .iap-modal, .paw-sheet) {
        max-width: 100%;
        max-height: 94vh;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    }
    body[data-ds="rt"] .hrv-modal-box { overflow-y: auto; }
    @supports (height: 100dvh) {
        body[data-ds="rt"] :is(.slide-panel, .hrv-modal-box, .iap-modal, .paw-sheet) {
            max-height: 94dvh;
        }
    }
}

/* --------------------------------------------------------------------------
   C7 · Header controls on phones and tablets fold behind one button
   (extended to tablets in Phase 2: Bell Curve, Management Summary and Payroll
   headers were still 135–149px at 768 and 1024).
   Source: the shared header action slot (.content-header-right, main.css)
   and the .btn-secondary control. app.js §RV adds the button only when the
   controls would wrap onto more than one row, and writes the current choices
   onto it ("ES - Jul-26 · All categories"). The bell always stays visible.
   -------------------------------------------------------------------------- */
body[data-ds="rt"] .ch-more-btn { display: none; }
@media (max-width: 1024px) {
    body[data-ds="rt"] .content-header.ch-foldable .ch-more-btn {
        display: inline-flex;
        flex: 1 1 0;      /* basis 0: shares the row with the bell instead of wrapping */
        min-width: 0;
        justify-content: space-between;
        gap: 8px;
        min-height: 40px;
    }
    body[data-ds="rt"] .ch-more-btn .ch-more-sum {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        font-weight: var(--font-weight-regular);
        color: var(--color-text-secondary);
    }
    body[data-ds="rt"] .ch-more-btn svg { flex: none; transition: transform 160ms var(--ease-out-strong); }
    body[data-ds="rt"] .content-header.ch-open .ch-more-btn svg { transform: rotate(180deg); }
    body[data-ds="rt"] .content-header.ch-foldable:not(.ch-open) > .content-header-right
        > :not(.theme-controls):not(.ch-more-btn) {
        display: none !important;
    }
    body[data-ds="rt"] .content-header.ch-foldable.ch-open > .content-header-right
        > :not(.theme-controls):not(.ch-more-btn) {
        flex: 1 1 100%;
        order: 2;
    }
}

/* --------------------------------------------------------------------------
   C8 · Phones: the page title once, and the toolbar behind one button
   (Phase 2 D5, 01-Oct-2026, approved by RT)
   * Every screen printed its title twice on a phone: in the 64px top bar and
     again as the page heading. The heading is kept for screen readers and
     hidden from view; the top bar carries the title.
   * Toolbars: the search box stays visible and every other filter and action
     sits behind one "Filters and actions" button — the same pattern C7 uses
     for the header. app.js §RV (rvFoldToolbars) adds the button only when the
     toolbar would take more than two rows, and writes how many filters are
     applied onto it. Employee Master's first record moved 506 -> 362px.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
    body[data-ds="rt"] .content-header h1.content-header-title {
        position: absolute !important;
        top: 0;
        left: 0;
        width: 1px !important;
        height: 1px !important;
        padding: 0 !important;
        margin: -1px !important;
        overflow: hidden !important;
        clip: rect(0, 0, 0, 0) !important;
        clip-path: inset(50%) !important;
        white-space: nowrap !important;
        border: 0 !important;
    }
    body[data-ds="rt"] .rv-tfold:not(.rv-tfold-open) .rv-tfold-hide {
        display: none !important;
    }
    body[data-ds="rt"] .rv-tfold-btn {
        flex: 1 1 100%;
        width: 100%;
        justify-content: space-between;
        gap: 8px;
        min-height: 40px;
    }
    body[data-ds="rt"] .rv-tfold-btn .rv-tfold-sum {
        font-weight: var(--font-weight-regular);
        color: var(--color-text-secondary);
    }
    body[data-ds="rt"] .rv-tfold-btn svg { flex: none; transition: transform 160ms var(--ease-out-strong); }
    body[data-ds="rt"] .rv-tfold.rv-tfold-open .rv-tfold-btn svg { transform: rotate(180deg); }
}
body[data-ds="rt"] .rv-tfold-btn[hidden] { display: none !important; }

/* Phone header: when the only control is the bell, it stays on the title row
   instead of taking a second 56px row of its own (main.css puts the title on
   its own row for screens that DO have controls). */
@media (max-width: 767px) {
    body[data-ds="rt"] .content-header:not(:has(> .content-header-right > :not(.theme-controls))) > .content-header-left {
        flex: 1 1 0;
    }
    body[data-ds="rt"] .content-header:not(:has(> .content-header-right > :not(.theme-controls))) > .content-header-right {
        width: auto;
        flex: none;
    }
}

/* --------------------------------------------------------------------------
   TEXT FLOOR — responsive Phase 2 (11-Sep-2026)
   Every sub-12px font-size in the screen stylesheets and templates reads a
   token: var(--fs-11, 11px). On a desktop with a mouse the token IS the old
   size, so nothing moves. On phones and tablets (the drawer shell) every one
   of them becomes 12px, the legibility floor. Chart axis ticks are drawn by
   Chart.js options, not CSS, and keep their own size. PDF, print and email
   templates were not rewritten and keep literal sizes.
   Defined in main.css (sign-in pages) and again in rt-masters.css (app pages,
   not long-cached) — keep the two identical.
   -------------------------------------------------------------------------- */
:root {
    --fs-8: 8px; --fs-9: 9px; --fs-9-5: 9.5px; --fs-10: 10px;
    --fs-10-5: 10.5px; --fs-11: 11px; --fs-11-5: 11.5px;
    --fs-12: 12px;
}

/* --------------------------------------------------------------------------
   TYPE TOKENS MIRROR — typography audit P1 (12-Sep-2026)
   --font-family-mono and --font-weight-bold are defined in main.css:118/134.
   They are mirrored here for the same reason the text-floor tokens above are:
   main.css is served unhashed and long-cached, so a browser holding a stale
   copy would see the page stylesheets ask for tokens its main.css does not
   define. Every consumer also carries a literal fallback, so the worst case is
   the pre-audit rendering rather than an invalid declaration.
   Keep these identical to main.css.
   -------------------------------------------------------------------------- */
:root {
    --font-family-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    --font-weight-bold: 700;
    --font-size-base: 16px;
    --font-size-4xl: 36px;
    --fs-kpi: var(--font-size-2xl);
    --line-height-none: 1;
    --line-height-flat: 1.15;
    --line-height-snug: 1.35;
}
@media (max-width: 1024px), (pointer: coarse) {
    :root {
        --fs-8: 12px; --fs-9: 12px; --fs-9-5: 12px; --fs-10: 12px;
        --fs-10-5: 12px; --fs-11: 12px; --fs-11-5: 12px;
        --font-size-xs: 12px;   /* the 11px design token */
    }
}

/* --------------------------------------------------------------------------
   C3b · Auto-pinned first column (Phase 2) — any table that app.js §RV finds
   wider than its scroll box, at every width: CFO/CEO/MD analytics, Finance
   roster, HR Review, Payroll, appraisal bell tables. Zero-specificity
   (:where) so a cell's own background — a heat-map or status tint — still
   wins; an unstyled cell gets the opaque row colour instead of letting the
   scrolled text show through.
   -------------------------------------------------------------------------- */
:where(table.rv-pin-auto) tr { --rv-pin-bg: var(--color-background); }
:where(table.rv-pin-auto) :where(thead) tr { --rv-pin-bg: var(--color-surface-secondary); }
:where(table.rv-pin-auto) :where(th, td):first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background-color: var(--rv-pin-bg);
}
:where(table.rv-pin-auto) :where(thead) :where(th):first-child { z-index: 3; }
:where(.rv-scrolled) :where(table.rv-pin-auto) :where(th, td):first-child {
    box-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.14);
}

/* C6 (Phase 2 fix): the shared dialog's phone sheet from main.css lost to
   screen stylesheets that re-centre .modal-overlay (found on Submit
   Performance Appraisals, where the rating form sat as a 352px card with
   gaps). Restated here at a weight those rules cannot beat. */
@media (max-width: 767px) {
    body[data-ds="rt"] .modal-overlay {
        padding: 0 !important;
        align-items: flex-end !important;
    }
    body[data-ds="rt"] .modal-overlay .modal-container {
        width: 100% !important;
        max-width: 100% !important;
    }
    body[data-ds="rt"] .modal-overlay .modal-box {
        width: 100% !important;
        max-width: 100% !important;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
        max-height: 94vh;
    }
    /* A screen's zoom-in open state (scale .95) left a 9px gap each side. */
    body[data-ds="rt"] .modal-overlay.active .modal-box { transform: none !important; }
    @supports (height: 100dvh) {
        body[data-ds="rt"] .modal-overlay :is(.modal-container, .modal-box) { max-height: 94dvh; }
    }
}

/* Phase 2 (E2E find): table views sit inside cards with overflow:hidden, so
   once tablets kept the table (Phase 2) the right-hand columns — including
   "View appraisal" on Performance Appraisal Approvals at 768px — were cut
   off with no way to scroll to them. Each table view scrolls its own table. */
body[data-ds="rt"] :is(.emp-view-table, .ad-view-table, .fnrb-view-table, .dsh-view-table, .inc-view-table) {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* Phase 2 (E2E find): the increment dossier header (.incv2-hd — HOD
   Increment Approval and HR Review) is one nowrap row. At 375px the name
   squeezed to 0px under the status pill and the previous/next/close buttons
   ran off the right edge, so the dossier could not be closed by tap.
   Phones: name on the first row, status and navigation on the second. */
@media (max-width: 767px) {
    body[data-ds="rt"] .incv2-hd {
        flex-wrap: wrap;
        row-gap: 8px;
        padding: 12px 14px;
    }
    body[data-ds="rt"] .incv2-hd .incv2-who { flex: 1 1 calc(100% - 60px); }
    body[data-ds="rt"] .incv2-hd .incv2-state { order: 3; }
    body[data-ds="rt"] .incv2-hd .incv2-nav { order: 4; margin-left: auto; }
}

/* Phase 2 (E2E find): the dossier is a flex column whose header, decision
   strip and footer (checks, comment, buttons) are fixed and whose body is the
   only scroller. On a phone those fixed parts took 755 of 763px and left the
   record itself a 6px window. On phones and short landscape screens the
   whole dossier scrolls as one page instead: read top to bottom, decide at
   the foot. */
@media (max-width: 767px), (max-height: 640px) {
    body[data-ds="rt"] :is(.iap-modal, .hrv-modal-box, .modal-box):has(> .incv2-body) {
        overflow-y: auto;
        overscroll-behavior: contain;
    }
    body[data-ds="rt"] .incv2-body {
        flex: none !important;
        overflow: visible !important;
        max-height: none !important;
        min-height: 0;
    }
}

/* Phase 2 (E2E find): the confirm sub-dialogs (.iap-sub Increment Approval
   and Appraisal Details, .exr-sub CFO/CEO/MD, .fnr-sub Finance) keep their
   buttons in one nowrap row aligned right. "Approve and forward for HR
   review" is long, so at 375px it pushed Cancel 40px off the left edge.
   Phones: the sub-dialog is a bottom sheet and its buttons stack full width,
   the forwarding action on top. The shared .modal-actions row gets the
   same treatment. */
@media (max-width: 767px) {
    body[data-ds="rt"] :is(.iap-sub, .exr-sub, .fnr-sub).open,
    body[data-ds="rt"] :is(.iap-sub, .exr-sub, .fnr-sub)[style*="flex"] {
        align-items: flex-end;
        padding: 0;
    }
    body[data-ds="rt"] :is(.iap-sub, .exr-sub, .fnr-sub) > :is(.iap-sub-box, .exr-sub-box, .fnr-sub-box) {
        width: 100%;
        max-width: 100% !important;
        max-height: 94vh;
        overflow-y: auto;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
        padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    }
    body[data-ds="rt"] :is(.iap-sub-acts, .exr-sub-acts, .fnr-sub-acts, .modal-actions) {
        flex-direction: column-reverse;
        align-items: stretch;
        flex-wrap: nowrap;
        gap: 8px;
    }
    body[data-ds="rt"] :is(.iap-sub-acts, .exr-sub-acts, .fnr-sub-acts, .modal-actions) > :is(.btn, button, a, form),
    body[data-ds="rt"] :is(.iap-sub-acts, .exr-sub-acts, .fnr-sub-acts, .modal-actions) > form > :is(.btn, button) {
        width: 100%;
        margin: 0;
        white-space: normal;
    }
}

/* Phase 2 (E2E find): HR Review "Send to Finance" gate table. A blocked
   division's reason sits in a nowrap status pill, which stretched the table
   to 1181px inside a 736px card; the main area clips overflow, so the
   division's button was out of reach on tablets. The reason now wraps, and
   below desktop each division is a card (.rv-dcards). */
body[data-ds="rt"] .hrv-gate-tbl .hrv-st { white-space: normal; }
body[data-ds="rt"] .hrv-gate-tbl { max-width: 100%; }

/* Phase 2: Bell Curve "Caps: <bracket>" button carries a long scope label
   and .btn is nowrap, so on phones it ran past the card edge. Wraps there. */
@media (max-width: 767px) {
    body[data-ds="rt"] .bcr-tbl-toolbar { flex-wrap: wrap; }
    body[data-ds="rt"] .bcr-tbl-toolbar .btn {
        white-space: normal;
        text-align: left;
        max-width: 100%;
        height: auto;
        line-height: var(--line-height-snug);
        padding-top: 8px;
        padding-bottom: 8px;
    }
}

/* Phase 2 (E2E find): Finance Approval division modal — same shape as the
   dossier: fixed header, KPI band and footer, with the records table the only
   scroller, left a 103px window on a phone. Phones: bottom sheet that scrolls
   as one page; the records table keeps its own sideways scroll. */
@media (max-width: 767px), (max-height: 640px) {
    body[data-ds="rt"] .fnr-modal-overlay.open .fnr-modal {
        overflow-y: auto;
        overscroll-behavior: contain;
    }
    body[data-ds="rt"] .fnr-modal .fnrm-body {
        flex: none !important;
        max-height: none !important;
        height: auto !important;
    }
}
@media (max-width: 767px) {
    body[data-ds="rt"] .fnr-modal-overlay.open {
        align-items: flex-end !important;
        padding: 0 !important;
    }
    body[data-ds="rt"] .fnr-modal-overlay.open .fnr-modal {
        width: 100% !important;
        max-width: 100% !important;
        max-height: 94vh;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    }
    @supports (height: 100dvh) {
        body[data-ds="rt"] .fnr-modal-overlay.open .fnr-modal { max-height: 94dvh; }
    }
}

/* C2 (Phase 2): a four-tile strip (Payroll) went 3 + 1 on tablets, leaving
   one tile alone on a row. Four tiles stay four across on tablets. */
@media (min-width: 768px) and (max-width: 1100px) {
    body[data-ds="rt"] :is(.hrv-kpis, .fnr-kpis, .iap-kpis, .inc-metrics, .ms-kpis,
                           .exr-kpis, .pay-kpis, .cfo-kpis, .ceo-kpis, .md-kpis, .eap-kpis):has(> :nth-child(4):last-child) {
        grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
    }
}

/* Phase 2 (E2E find): screen-reader-only captions and labels are
   position:absolute with no offset, so each sat at its static position far
   down the page. Inside the drawer shell the page scrolls in .main-content,
   so those 1px boxes stretched the DOCUMENT instead and phones got a second,
   outer scroll that dragged the hamburger bar away (MD, CEO, Management
   Summary). Anchoring them at the top-left keeps them where they cannot
   extend anything. Still read by screen readers; still invisible. */
body[data-ds="rt"] .sr-only,
body[data-ds="rt"] table.rv-dcards thead {
    top: 0;
    left: 0;
}

/* Phase 2: the executive division header (.md-dhead / .ceo-dhead /
   .cfo-dhead) keeps status, Previous and Next in one nowrap cluster pushed to
   the right; at 375px "Next ›" ran past the edge. Phones: the cluster takes
   its own row and wraps. */
@media (max-width: 767px) {
    body[data-ds="rt"] :is(.md-dhead, .ceo-dhead, .cfo-dhead) .sp {
        flex: 1 1 100%;
        flex-wrap: wrap;
        margin-left: 0;
    }
}

/* Phase 3: master Import / Export menus open right-aligned to their button.
   On phones the buttons sit at the left of the stacked toolbar, so the Import
   menu started 61px off the left edge. Phones: menus open from the button's
   left edge and never exceed the screen. */
@media (max-width: 767px) {
    body[data-ds="rt"] .master-dropdown-menu {
        left: 0;
        right: auto;
        max-width: calc(100vw - 32px);
    }
}

/* Phase 3: Role Permissions — after ticking a box near the top of a
   5,000px matrix, "Save Permissions" was a long scroll away on phones and
   tablets. The save bar stays at the foot of the screen while the matrix is
   in view. */
@media (max-width: 1024px), (pointer: coarse) {
    body[data-ds="rt"] .perm-save-bar {
        position: sticky;
        bottom: 0;
        z-index: 5;
        background: var(--color-background);
        border-top: 1px solid var(--color-border);
        padding: 10px 0 calc(10px + env(safe-area-inset-bottom, 0px));
    }
}

/* Phase 3: Appraisal Enrollment's row tick-box is a plain first cell, not the
   shared .bulk-td, so on phone cards it sat centred in a grid cell of its own.
   Any cell that holds nothing but a tick-box goes to the card's corner. */
@media (max-width: 767px) {
    body[data-ds="rt"] .master-table tbody tr:has(> td:not(.bulk-td) > input[type="checkbox"]:only-child) {
        padding-right: 48px;
    }
    body[data-ds="rt"] .master-table tbody td:not(.bulk-td):has(> input[type="checkbox"]:only-child) {
        position: absolute;
        top: 8px;
        right: 8px;
        width: 40px;
        height: 40px;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    body[data-ds="rt"] .master-table tbody td:not(.bulk-td):has(> input[type="checkbox"]:only-child)::before {
        content: none;
    }
}

/* Phase 3: some master cells carry an inline min-width for the desktop
   table (Help Content: 240px), which pushed their card column 76px past
   the screen. On phone cards no cell keeps a desktop width. */
@media (max-width: 767px) {
    body[data-ds="rt"] .master-table tbody td:not(.bulk-td) {
        min-width: 0 !important;
        width: auto !important;
    }
}

/* ==========================================================================
   ERROR MODAL — visibility pass (RT, 2026-09-12)

   Appended to rt-masters.css because that is the file base.html links; an
   override written anywhere else does not reach the page.

   Three changes, in the order they were asked for:
     1. The reference code is never shown. It stays in the DOM as
        [data-err-code] on the overlay for support, and nowhere else.
     2. Centred and unmissable: a heavier backdrop, a wider card, a red rule
        across the top, and a short pop-in so the eye lands on it.
     3. Red / pink throughout, with the four sections visibly separated so
        Title, What happened, Why it happened and How to resolve it read as
        four answers rather than one block of text.
   ========================================================================== */

/* 1. the code, hidden for good ------------------------------------------- */
.pms-err-code { display: none !important; }

/* 2. centred and unmissable ---------------------------------------------- */
.pms-err-overlay {
    background: rgba(24, 18, 20, .58);   /* dims the page; the CARD carries the red */
    backdrop-filter: blur(1.5px);
    -webkit-backdrop-filter: blur(1.5px);
    padding: 4vh 4vw;
}
.pms-err {
    width: 620px;
    border: 1px solid #E88E8E;
    border-top: 4px solid #C00000;
    box-shadow: 0 18px 48px -12px rgba(120, 0, 0, .45),
                0 0 0 1px rgba(192, 0, 0, .08);
}
.pms-err-overlay.is-open .pms-err {
    animation: pmsErrPop .16s cubic-bezier(.2, .8, .3, 1);
}
@keyframes pmsErrPop {
    from { opacity: 0; transform: translateY(-10px) scale(.985); }
    to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .pms-err-overlay.is-open .pms-err { animation: none; }
}

/* 3. red / pink, four readable sections ---------------------------------- */
.pms-err-head {
    background: #FBE3E3;
    border-bottom: 1px solid #E88E8E;
    padding: 18px 22px 15px;
}
.pms-err-icon { background: #F0A9A9; color: #8E0000; }
.pms-err-title {
    font-size: var(--fs-17, 17px);
    color: #8E0000;
}
.pms-err-body {
    background: #FFF8F8;
    padding: 18px 22px 6px;
}
.pms-err-body section {
    margin-bottom: 14px;
    padding-bottom: 12px;
    border-bottom: 1px dashed #F0C9C9;
}
.pms-err-body section:last-child {
    border-bottom: 0;
    margin-bottom: 4px;
}
.pms-err-body h4 {
    color: #C00000;
    font-size: var(--fs-11, 11px);
    letter-spacing: .04em;
    text-transform: uppercase;
    margin-bottom: 6px;
}
.pms-err-body p,
.pms-err-body ol { color: #3A2A2A; }
.pms-err-body ol { padding-left: 18px; }
.pms-err-body li { margin-bottom: 6px; }
.pms-err-body li::marker { color: #C00000; font-weight: 700; }
.pms-err-foot {
    background: #FFF8F8;
    border-top: 1px solid #F0C9C9;
    padding: 14px 22px;
}
.pms-err-foot .btn {
    background: #C00000;
    border-color: #C00000;
    color: #FFFFFF;
    font-weight: var(--font-weight-bold);
}
.pms-err-foot .btn:hover { background: #A30000; border-color: #A30000; }
.pms-err-foot .btn:focus-visible { outline: 2px solid #8E0000; outline-offset: 2px; }

@media (max-width: 560px) {
    .pms-err { width: 100%; }
    .pms-err-head { padding: 14px 16px 12px; }
    .pms-err-body { padding: 14px 16px 4px; }
    .pms-err-foot { padding: 12px 16px; }
}

/* ==========================================================================
   Toolbar filters WRAP instead of running under the actions (15-Sep-2026)
   .master-toolbar-filters is flex:1 with min-width:0 but never wrapped, so
   once search + filters were wider than the space left of Import / Export /
   Add, the last filters slid UNDER the action buttons. Measured before this
   rule: Employee Master overlapped by 224px at 1280 and 64px at 1440 (after
   the Personnel Area filter was added); Salary Banding 77px and Increment
   Budget 134px at 1280. Wrapping only changes a toolbar that overflows; a
   toolbar that fits stays on one row exactly as before.
   ========================================================================== */
body[data-ds="rt"] .master-toolbar-filters {
    flex-wrap: wrap;
    row-gap: var(--space-2);
}

/* Policy Increment Configuration (16-Sep-2026): the "Setup needed" note above
   the table. Advisory only, so it is NOT .alert-warning, which app.js hoists
   into the error modal as a failed action. */
.master-gap-note {
    margin-bottom: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    border: 1px solid var(--color-warning-border);
    background: var(--color-warning-bg);
    color: var(--color-warning);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-normal);
}
.master-gap-note ul {
    margin: var(--space-2) 0 0;
    padding-left: var(--space-5);
}

/* ==========================================================================
   9 · SPACING CONTRACT — CONTROL HEIGHTS (01-Oct-2026)
   --------------------------------------------------------------------------
   MEASURED across 56 screens at 1440px before this block: 19 different
   control heights (21-47px). Every master toolbar mixed three of them —
   34px search and filters, 28px secondary buttons, a 26px primary (it has no
   border, so it came out 2px shorter than its neighbours) — and the workflow
   toolbars mixed 34/36/38. Review: docs/reviews/app_whitespace_review_v2.md.

   Two desktop sizes from here on:
     32px  every control on a toolbar, filter row, view bar, pager and the
           page header. The header's .ch-btn / .theme-btn were already 32px,
           so 32 is the existing anchor, not a new number.
     36px  form fields: inputs and selects in modals and search forms.

   Scoped to a fine pointer at desktop width ONLY. The 40px touch floor in
   section 4 (max-width: 900px) and 4b (pointer: coarse) is untouched, and
   neither block can be overridden by this one: different media, no overlap.

   Controls INSIDE table cells and cards are deliberately not listed — their
   height sets row and card height, and row density is a separate decision.
   ========================================================================== */
@media (min-width: 901px) and (pointer: fine) {
    /* 32px — toolbar and inline controls */
    body[data-ds="rt"] :is(.master-toolbar, .master-pagination-footer,
                           .inc-toolbar, .inc-viewbar, .iap-toolbar,
                           .hrd-toolbar, .fnr-toolbar, .pay-toolbar,
                           .pay-actions, .dsh-filters, .ms-filters,
                           .hod-filters-row, .eap-flt, .filter-row,
                           .sbd-tools) :is(.btn, .ms-filter-btn, .ms-trigger,
                           .pay-btn, .hod-commit-btn, .master-search-input,
                           .hod-search-input, .master-page-size, select:not([multiple]),
                           input[type="search"], input[type="text"], input.form-input),
    body[data-ds="rt"] .content-header .ch-cycle {
        height: 32px;
        min-height: 32px;
        padding-top: 0;
        padding-bottom: 0;
        box-sizing: border-box;
    }
    /* Borderless primary buttons get a transparent border so a primary and
       a bordered secondary of the same height line up edge to edge. */
    body[data-ds="rt"] :is(.master-toolbar, .master-pagination-footer,
                           .inc-toolbar, .iap-toolbar, .hrd-toolbar,
                           .fnr-toolbar, .pay-toolbar, .pay-actions,
                           .dsh-filters, .ms-filters, .hod-filters-row,
                           .filter-row, .sbd-tools) .btn:is(.btn-primary, .btn-ghost) {
        border: 1px solid transparent;
    }
    /* The view toggle is a 1px-bordered frame around its buttons, so the
       buttons are 30px to make the frame 32px. */
    body[data-ds="rt"] .vt-btn {
        width: 30px;
        height: 30px;
    }

    /* 36px — form fields */
    body[data-ds="rt"] :is(.modal-field, .ad-grid, .sc-filter-grid)
        :is(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]), select:not([multiple])),
    body[data-ds="rt"] :is(.ad-grid, .sc-filter-grid) :is(.btn, .ms-filter-btn, .ms-trigger) {
        height: 36px;
        min-height: 36px;
        padding-top: 0;
        padding-bottom: 0;
        box-sizing: border-box;
    }
}

/* ==========================================================================
   10 · LIST DECISIONS — Phase 3 (01-Oct-2026)
   --------------------------------------------------------------------------
   Review: docs/reviews/app_whitespace_review_v5.md. The behaviour lives in
   app.js §RV-LISTS; these are its styles plus the two density fixes (E1, E2).

   c  .rv-csort   header of a whole-list table sorted in the browser. Same
                  arrow and colour as the server-sorted th.sortable.
   d  .rv-lsearch "Search this list" on the two screens with no way at all
                  to narrow their list (Workflow Exceptions, Help Content).
   a  .rv-place-note  the one-line "Showing where you left off" note with its
                  "Start fresh" link.
   ========================================================================== */
body[data-ds="rt"] th.rv-csort {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
body[data-ds="rt"] th.rv-csort:hover { color: var(--color-text-primary); }
body[data-ds="rt"] th.rv-csort .sort-arrow {
    margin-left: 4px;
    font-size: var(--fs-10, 10px);
    color: var(--color-primary);
}
body[data-ds="rt"] th.rv-csort:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: -2px;
}

body[data-ds="rt"] .rv-lsearch {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: 0 0 var(--space-3);
}
body[data-ds="rt"] .rv-lsearch .rv-lsearch-input {
    width: 240px;
    max-width: 100%;
    height: 32px;
    min-height: 32px;
    padding: 0 var(--space-3);
    box-sizing: border-box;
}
body[data-ds="rt"] .rv-lsearch-count {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}

body[data-ds="rt"] .rv-place-note {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-4);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface-secondary);
    color: var(--color-text-secondary);
    font-size: var(--font-size-sm);
}
body[data-ds="rt"] .rv-place-fresh { font-weight: var(--font-weight-semibold); }

/* E1 — decision tables: one row height per kind of table.
   MEASURED at 1440 before: Approvals 64px, Finance roster 38-46px, Bell Curve
   ladder 42px, Management Summary 42px, executive queue 84px. Decision rows
   (a person or a figure someone approves) get the comfortable 48px floor;
   the Management Summary roll-up gets the compact 40px floor. Padding drops to
   6px so the floor, not the padding, sets the height. Desktop only: below
   768px these tables become cards and keep their own rules. */
@media (min-width: 768px) {
    body[data-ds="rt"] table.approval-table tbody tr:not(.category-group-row) > td,
    body[data-ds="rt"] table.fnr-roster-t2 tbody td,
    body[data-ds="rt"] table.bcr-cat tbody td,
    body[data-ds="rt"] table.eap-queue tbody td {
        padding-top: 6px !important;
        padding-bottom: 6px !important;
        height: 48px;
    }
    body[data-ds="rt"] table.ms-table tbody td {
        padding-top: 6px !important;
        padding-bottom: 6px !important;
        height: 40px;
    }
}

/* E2 — phone: Payroll and Management Summary. MEASURED at 390 before: the
   first record started 906px (Payroll) and 902px (Management Summary) down.
   The intro paragraph shows two lines with "More about this screen"
   (app.js adds the button only when the text is actually cut), the sign-off
   cells lose their secondary line, and the four Payroll actions sit 2 x 2. */
@media (max-width: 767px) {
    body[data-ds="rt"] :is(.pay-strap, .ms-strap):not(.rv-strap-open) .txt {
        display: -webkit-box;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }
    body[data-ds="rt"] .rv-strap-more {
        min-height: 32px;
        padding: 0;
        border: 0;
        background: none;
        color: var(--color-primary);
        font-size: var(--font-size-xs);
        font-weight: var(--font-weight-semibold);
        cursor: pointer;
    }
    body[data-ds="rt"] .ms-so-cell {
        padding: 8px 12px !important;
        gap: 8px !important;
        align-items: center !important;
    }
    body[data-ds="rt"] .ms-so-txt {
        display: flex !important;
        flex-wrap: wrap;
        column-gap: 8px;
        align-items: baseline;
    }
    body[data-ds="rt"] .ms-so-s { display: none !important; }
    body[data-ds="rt"] .pay-actions {
        display: grid !important;
        grid-template-columns: 1fr 1fr;
        gap: 8px;
    }
    body[data-ds="rt"] .pay-actions > * { width: 100%; }
}
/* The button only exists on a phone; hide it if the window is widened. */
@media (min-width: 768px) {
    body[data-ds="rt"] .rv-strap-more { display: none; }
}
