/* ============================================================================
   dashboard-shell.css — app shell density & hierarchy pass
   ============================================================================

   WHY A SEPARATE FILE (read before adding rules anywhere else):

   The v2 shell is painted by css/dashboard-v2.css through `!important` rules
   hung off body.dashboard-v2-enabled (see the styling trap in CLAUDE.md). A
   rule that merely *exists* does not win — it needs same-or-higher specificity
   PLUS `!important` PLUS later load order. This file is linked immediately
   after dashboard-v2.css in index.html, which buys the third of those for
   free, so the overrides here stay readable instead of escalating into another
   specificity fight inside an 11.8k-line stylesheet.

   Every rule below is scoped to body.dashboard-v2-enabled for the same reason
   the rules it overrides are: the v1 shell must be untouched.

   Dashboard palette and component overrides are owned here; legacy --lm-*
   values below are aliases, not a separate dashboard palette.
   Verify changes here by reading the COMPUTED style off the live element in
   devtools, never by confirming your own rule is present.
   ========================================================================= */


/* ---------------------------------------------------------------------------
   1. TAB ALERT BADGE — inline, next to the label it counts
   ---------------------------------------------------------------------------
   .tab-alert-count (style.css) is position:absolute / top:2px / right:4px.
   That was sized for a narrow pill; inside a `flex-1` tab it parked the badge
   at the far edge with no visible owner — on a 4-tab bar the "5" floated
   between Done and Knowledge Base and read as belonging to neither.

   Scoped to the desktop tab bar only: #mobile-subtabs sets its own
   position:relative per button and still wants the corner badge.
   ------------------------------------------------------------------------ */

body.dashboard-v2-enabled .dashboard-v2-tabs .tab-btn .tab-alert-count {
    position: static;
    top: auto;
    right: auto;
    margin-left: 1px;
    height: 15px;
    min-width: 15px;
    padding: 0 4px;
    border: 0;
    font-size: 9.5px;
    line-height: 15px;
    /* The badge sits on the active tab's indicator gradient as well as on the
       bar, so it carries its own contrast rather than borrowing the bar's. */
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.28);
}


/* ---------------------------------------------------------------------------
   2. RESULT COUNT — a readout, not a control
   ---------------------------------------------------------------------------
   #ticket-count-display was a filled indigo pill with a border, i.e. the exact
   visual language of the buttons beside it, for something that is not
   clickable. Quiet text instead; it also gives ~55px back to the row.

   The HTML keeps its "Showing:" span even though dashboard-v2.css hides it via
   `span:first-child { display:none }` — removing it from the markup would slide
   that selector onto #ticket-count and hide the number itself.
   ------------------------------------------------------------------------ */

body.dashboard-v2-enabled #ticket-count-display {
    padding: 0 2px !important;
    background: transparent !important;
    border-color: transparent !important;
    box-shadow: none !important;
}

body.dashboard-v2-enabled #ticket-count-display #ticket-count {
    color: var(--dashboard-v2-text) !important;
    font-variant-numeric: tabular-nums;
}

body.dashboard-v2-enabled #ticket-count-display span:last-child {
    color: var(--dashboard-v2-muted);
}


/* ---------------------------------------------------------------------------
   3. TEAM RECOGNITION — one line of chips, not five stretched cards
   ---------------------------------------------------------------------------
   The strip was `grid-template-columns: repeat(5, minmax(0,1fr))` over the full
   content width with 50px-tall cards: ~88px of the most prominent space on the
   page spent on five numbers, four of which are typically "1". Because each
   card was itself `auto | 1fr | auto`, the stretched middle column pushed the
   count ~250px away from the badge name it belongs to, so the one number on the
   card had no visible owner.

   Now: a single non-wrapping row of intrinsic-width chips, each reading
   emoji · name · holders · count with nothing stretched between them, scrolling
   sideways if a team ever has more than fits. Unearned badges stay in place
   (the strip is also "what is still winnable") but recede.

   Nothing is hidden and no data is dropped — this is pure layout.
   ------------------------------------------------------------------------ */

/* The label is a real <h2> now (js/badges-ui.js). Retire the pseudo-element or
   the strip renders its title twice. */
body.dashboard-v2-enabled #badges-header.dashboard-v2-badge-strip::before {
    content: none !important;
}

body.dashboard-v2-enabled .badges-header-title {
    margin: 0 2px 4px;
    color: var(--dashboard-v2-muted);
    font-size: 9.5px;
    font-weight: 850;
    line-height: 1.05;
    text-transform: uppercase;
}

/* Intrinsic chips instead of five stretched columns.

   ALWAYS ONE LINE, scrolling sideways when it has to. This is what keeps the
   strip safe as a team grows: a badge held by a dozen people, or a team with
   more badges enabled, would otherwise wrap the track onto a second and third
   row and push the ticket list down — and silently break #badges-header's
   62px CLS reservation in index.html, which is sized for exactly one line.
   Scrolling keeps the strip's height constant no matter how many badges or
   holders exist. dashboard-v2.css already ships the thin indigo scrollbar
   styling for this track, so this restores the pattern it was built for.
   Chip-level overflow is handled separately by MAX_CHIP_HOLDERS in
   js/badges-ui.js. */
body.dashboard-v2-enabled .dashboard-v2-badge-track {
    display: flex !important;
    flex-wrap: nowrap !important;
    grid-template-columns: none !important;
    justify-content: flex-start !important;
    gap: 5px !important;
    overflow-x: auto !important;
    overflow-y: hidden;
    /* Chips must not be squeezed by the flex line; they keep their size and
       the track scrolls instead. */
    scroll-snap-type: x proximity;
}

/* "+4" when a badge has more holders than the chip shows. The count pill in
   the same chip carries the true total; this just says the avatars are a
   sample rather than the whole list. */
body.dashboard-v2-enabled .dashboard-v2-badge-users .badge-holder-more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 16px;
    min-width: 16px;
    padding: 0 3px;
    color: var(--dashboard-v2-muted);
    font-size: 8px;
    font-weight: 850;
    background: rgba(148, 163, 184, 0.16);
    border-radius: 999px;
    white-space: nowrap;
}

body.dashboard-v2-enabled .dashboard-v2-badge-card {
    display: inline-flex !important;
    grid-template-columns: none !important;
    flex: 0 0 auto;
    align-items: center;
    min-height: 30px !important;
    padding: 3px 8px 3px 4px !important;
    gap: 6px !important;
    border-radius: 999px !important;
}

/* name and holders were stacked; on a 30px chip they sit side by side. */
body.dashboard-v2-enabled .dashboard-v2-badge-card .badge-info {
    display: flex !important;
    flex-direction: row !important;
    align-items: center;
    gap: 6px !important;
}

/* .dashboard-v2-badge-users is `overflow: hidden` (dashboard-v2.css) — it was
   clipping a fixed-width column in the old card. In a chip that sizes to its
   content there is nothing to clip, and leaving it on would cut the "+N" pill
   in half. */
body.dashboard-v2-enabled .dashboard-v2-badge-card .dashboard-v2-badge-users {
    overflow: visible !important;
    flex-wrap: nowrap;
}

body.dashboard-v2-enabled .dashboard-v2-badge-icon {
    width: 22px;
    height: 22px;
    font-size: 12px !important;
}

body.dashboard-v2-enabled .dashboard-v2-badge-count {
    min-width: 18px !important;
    height: 17px;
    padding: 0 5px;
    font-size: 10px !important;
}

/* Unearned: present, but not competing with the badges someone actually holds. */
body.dashboard-v2-enabled .dashboard-v2-badge-empty {
    opacity: 0.45;
}

/* The strip's own frame no longer needs to look like a panel — it holds one
   line of chips, not a card grid. */
body.dashboard-v2-enabled .dashboard-v2-badge-strip-inner {
    padding: 4px !important;
    box-shadow: none !important;
    border-radius: 12px !important;
}


/* ---------------------------------------------------------------------------
   4. HEADER — the loud thing should be the time-critical thing
   ---------------------------------------------------------------------------
   The team name was a 24px gradient headline, centred, and is the least
   actionable element on the page. The countdowns next to it ("Deployment in
   8h 31m") are the only genuinely time-critical text in the header and were
   muted pills. This swaps the emphasis.

   .dashboard-v2-team-name sets `font-size: clamp(16px,1.25vw,20px) !important`
   (dashboard-v2.css:160), so the demotion has to happen HERE — the Tailwind
   text-sm on the markup loses to it. Exactly the trap in CLAUDE.md: the class
   in the HTML is not what paints the element.
   ------------------------------------------------------------------------ */

/* The ID in this selector is load-bearing, and was found by reading the
   COMPUTED font-size rather than trusting the rule. A class-only version
   (0,3,0) applied its colour but NOT its size, because css/style.css:2791
   carries `#team-name-header { font-size: 20px !important }` — an ID, (1,0,1),
   which no number of classes outranks. The name stayed a 20px headline while
   looking like the demotion had worked. */
/* Owner 2026-09-07: "the old name color was better". The demotion originally
   dimmed the colour AND the size; only the size was wanted. The name keeps its
   own paint now — `text-gray-300` in dark, the near-black light-theme rules in
   design-system-v2.css — and this rule sets SIZE ONLY. Do not add a colour
   declaration here. */
body.dashboard-v2-enabled #team-name-header.dashboard-v2-team-name {
    font-size: 14px !important;
    font-weight: 600 !important;
    letter-spacing: 0.01em;
}

/* Refresh now sits among the other header actions rather than beside the app
   mark, so it matches them (34px) instead of keeping its old 38x36 outlier
   size. dashboard-v2.js also stamps .dashboard-v2-header-refresh on it, which
   is where that size comes from. */
body.dashboard-v2-enabled #app-container > header #pwa-refresh-btn.dashboard-v2-header-refresh {
    width: auto !important;
    min-width: 34px !important;
    height: 34px !important;
    min-height: 34px !important;
    padding: 0 10px !important;
    border-radius: 999px !important;
}

body.dashboard-v2-enabled #app-container > header #pwa-refresh-btn.dashboard-v2-header-refresh > svg {
    width: 18px !important;
    min-width: 18px !important;
    height: 18px !important;
    flex: 0 0 18px;
}

/* Countdown ranking. js/schedule.js marks whichever of meeting/deployment
   actually lands first; both stay visible and keep their text and tooltip.
   Before, "in 23h" and "in 8h" were painted identically and in a fixed
   meeting-then-deployment order, so the reader had to compare numbers to find
   out which one to care about. */
body.dashboard-v2-enabled #schedule-countdown-stack .countdown-chip--next {
    font-weight: 700;
    box-shadow: 0 0 0 1px rgba(251, 191, 36, 0.28);
}

body.dashboard-v2-enabled #schedule-countdown-stack .countdown-chip--later {
    opacity: 0.62;
    font-weight: 500;
}

body.dashboard-v2-enabled #schedule-countdown-stack .countdown-chip--later:hover {
    opacity: 1;
}


/* ---------------------------------------------------------------------------
   5. LIGHT MODE — the same two overrides, at id specificity
   ---------------------------------------------------------------------------
   Caught by reading computed styles off the live element in light mode, which
   is the only way these ever get caught (CLAUDE.md).

   The demotions in section 4 and section 2 are class-scoped, e.g.
   `body.dashboard-v2-enabled .dashboard-v2-team-name` -> (0,3,0). But
   css/design-system-v2.css carries `html[data-theme="light"] #team-name-header`
   and `html[data-theme="light"] #ticket-count-display` -> an ID, which
   outranks any number of classes. So in DARK mode the team name demoted
   correctly and in LIGHT mode it stayed a large purple headline, and the
   result count kept its filled pill.

   These repeat the same intent with an ID in the selector so they win in light
   mode too. Same values as the theme-agnostic rules above — deliberately not a
   different light-mode design, just the same one actually applying.
   ------------------------------------------------------------------------ */

/* Light mode needs the ID for the SIZE only — the colour is left to
   design-system-v2.css's own light-theme rules (see the note above). */
html[data-theme="light"] body.dashboard-v2-enabled #team-name-header.dashboard-v2-team-name {
    font-size: 14px !important;
    font-weight: 600 !important;
}

html[data-theme="light"] body.dashboard-v2-enabled #ticket-count-display {
    padding: 0 2px !important;
    background: transparent !important;
    border-color: transparent !important;
    box-shadow: none !important;
}


/* ---------------------------------------------------------------------------
   6. MERGED TEAM PANEL — one list, three calm lines
   ---------------------------------------------------------------------------
   Team Stats and the Weekly Leaderboard listed the SAME people with two
   different numbers in two different orders, stacked. One card per member now:

     line 1   rank · name · device                    (identity)
     line 2   ● Idle · since 08:55                     (presence, stated ONCE)
     line 3   20 tickets ................ 205 pts      (both numbers, labelled)

   The first attempt kept this to two lines and the owner rejected it twice.
   Two lines meant seven competing items, presence encoded three separate ways
   (status dot + Online/Idle pill + "(Today 08:55 AM)"), and two bare numbers
   with nothing to say which was which. Height is cheaper than confusion.

   THE HARD CONSTRAINT: #stats-container is a grid whose single implicit track
   is sized to the widest row's MIN-CONTENT (dashboard-v2.css), and the panel
   clips. So exactly ONE element per line may be unshrinkable-by-default; every
   line here has a `truncate` target (the name, the since-label) that gives way
   first. Adding a fixed-width cell to any of these lines will grow the whole
   panel and bring back the horizontal scrollbar.
   ------------------------------------------------------------------------ */

body.dashboard-v2-enabled #stats-container .team-stat-card {
    padding: 8px 10px !important;
}

/* Line 1 — identity. */
body.dashboard-v2-enabled #stats-container .team-stat-name {
    font-size: 12px !important;
}

body.dashboard-v2-enabled #stats-container .lb-rank {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    min-width: 16px;
    height: 16px;
    padding: 0 3px;
    color: var(--dashboard-v2-muted);
    font-size: 9px;
    font-weight: 850;
    font-variant-numeric: tabular-nums;
    background: rgba(148, 163, 184, 0.14);
    border: 0 !important;
    border-radius: 999px;
}

/* Top three keep the medal colours the standalone leaderboard used, so the
   ranking still reads at a glance down the column. */
body.dashboard-v2-enabled #stats-container .lb-rank[data-rank="1"] {
    color: #FCD34D;
    background: rgba(250, 204, 21, 0.16);
}

body.dashboard-v2-enabled #stats-container .lb-rank[data-rank="2"] {
    color: #E2E8F0;
    background: rgba(203, 213, 225, 0.16);
}

body.dashboard-v2-enabled #stats-container .lb-rank[data-rank="3"] {
    color: #FDBA74;
    background: rgba(251, 146, 60, 0.16);
}

/* Excluded from scoring: keeps the column so rows stay aligned, carries no
   weight — the member is on the team, just not ranked. The .lb-score override
   is needed because the accent colour below is !important. */
body.dashboard-v2-enabled #stats-container .lb-rank--unscored,
body.dashboard-v2-enabled #stats-container .team-score-cell--unscored {
    color: var(--dashboard-v2-muted);
    background: none;
    opacity: 0.5;
}

body.dashboard-v2-enabled #stats-container .team-score-cell--unscored .lb-score {
    color: var(--dashboard-v2-muted) !important;
    font-weight: 600 !important;
}

/* Row actions stop competing with the data: revealed on hover, and on
   keyboard focus so they stay reachable without a mouse. Kept in the layout
   at all times (visibility, not display) so revealing one cannot reflow the
   card or shift the name. */
body.dashboard-v2-enabled #stats-container .team-stat-actions {
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.14s ease;
}

body.dashboard-v2-enabled #stats-container .team-stat-card:hover .team-stat-actions,
body.dashboard-v2-enabled #stats-container .team-stat-card:focus-within .team-stat-actions {
    visibility: visible;
    opacity: 1;
}

/* Line 2 — presence, said once. */
body.dashboard-v2-enabled #stats-container .team-stat-presence {
    font-size: 10px;
    line-height: 1.3;
}

body.dashboard-v2-enabled #stats-container .team-stat-since {
    color: var(--dashboard-v2-muted);
    font-size: 10px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Line 3 — the two metrics, each with its own word so "20" and "205" can
   never be read as the same kind of thing. */
body.dashboard-v2-enabled #stats-container .team-stat-metrics {
    font-size: 11px;
}

body.dashboard-v2-enabled #stats-container .team-stat-metric {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    min-width: 0;
}

body.dashboard-v2-enabled #stats-container .team-stat-count {
    color: var(--dashboard-v2-text) !important;
    font-size: 14px !important;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

body.dashboard-v2-enabled #stats-container .team-stat-metric-label,
body.dashboard-v2-enabled #stats-container .team-score-label {
    color: var(--dashboard-v2-muted);
    font-size: 10px;
}

body.dashboard-v2-enabled #stats-container .team-score-cell {
    display: inline-flex;
    align-items: baseline;
    justify-content: flex-end;
    gap: 3px;
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* .lb-score ships a translucent fill + border (dashboard-v2.css:696) drawn for
   the old padded leaderboard pill. Unpadded here it rendered as a boxed white
   slab that read like a selected input. Accent text instead. */
body.dashboard-v2-enabled #stats-container .lb-score {
    padding: 0 !important;
    color: #A5B4FC !important;
    font-size: 14px !important;
    font-weight: 800 !important;
    background: none !important;
    border: 0 !important;
    box-shadow: none !important;
}

html[data-theme="light"] body.dashboard-v2-enabled #stats-container .lb-rank {
    color: #64748B;
    background: rgba(100, 116, 139, 0.12);
}

html[data-theme="light"] body.dashboard-v2-enabled #stats-container .lb-score {
    color: #4338CA !important;
    background: none !important;
    border: 0 !important;
}


/* ---------------------------------------------------------------------------
   7. TEAM STATS TITLE IS THE WAY INTO THE HISTORY
   ---------------------------------------------------------------------------
   The weekly ranking merged into the member rows, which left the old
   "Past weeks" panel as a lone button taking a whole card in the sidebar for
   one link. The panel is gone; its trigger is the Team Stats title, which is
   where someone looks for "show me more about this team" anyway.

   Kept deliberately quiet: it must still read as the panel's heading first and
   a control second, so there is no button chrome until hover/focus.
   ------------------------------------------------------------------------ */

body.dashboard-v2-enabled #open-history-btn.team-stats-title-btn {
    min-width: 0;
    margin: -4px -6px;
    padding: 4px 6px;
    background: none;
    border: 0;
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.16s ease;
}

body.dashboard-v2-enabled #open-history-btn.team-stats-title-btn:hover,
body.dashboard-v2-enabled #open-history-btn.team-stats-title-btn:focus-visible {
    background: rgba(148, 163, 184, 0.12);
}

/* The chevron is the only thing that says "this opens something", so it earns
   its keep on hover rather than shouting at rest. */
body.dashboard-v2-enabled #open-history-btn .team-stats-title-chevron {
    opacity: 0.55;
    transition: opacity 0.16s ease, transform 0.16s ease;
}

body.dashboard-v2-enabled #open-history-btn:hover .team-stats-title-chevron,
body.dashboard-v2-enabled #open-history-btn:focus-visible .team-stats-title-chevron {
    opacity: 1;
    transform: translateX(2px);
}

html[data-theme="light"] body.dashboard-v2-enabled #open-history-btn.team-stats-title-btn:hover,
html[data-theme="light"] body.dashboard-v2-enabled #open-history-btn.team-stats-title-btn:focus-visible {
    background: rgba(15, 23, 42, 0.06);
}


/* ---------------------------------------------------------------------------
   8. THE TOP ROW'S DEVICE TOOLTIP WAS CUT IN HALF
   ---------------------------------------------------------------------------
   Reported on a real board: hovering the desktop/device icon on the HIGHEST
   card shows a tooltip with its top sliced off.

   Cause, found by walking the ancestor chain rather than guessing — and it is
   NOT the panel. `css/style.css:10409` makes #stats-container a scroll
   container (`max-height: 540px; overflow-y: auto`, so a 25+ member team shows
   ~10 rows and scrolls). .team-device-tooltip opens UPWARD
   (`bottom: calc(100% + 8px)`, css/style.css), so on the first row it tries to
   render above the scroll box's top edge and is clipped.

   Worth knowing: the long comment at dashboard-v2.css:8296 states this was
   "NOT DONE — deliberately … a scroll container would clip the first row's
   device tooltip outright". It reasoned correctly and then style.css made it a
   scroll container anyway. The predicted bug is exactly the one reported.

   Fix: the FIRST row's tooltip opens downward instead, where the rows below
   give it room. Rows 2+ keep opening upward, which is the better direction
   when there is space for it.

   Known limit, stated rather than hidden: this fixes the first row, not a
   middle row that happens to be scrolled hard against the top edge of the
   list. Fixing that properly means either dropping the 540px scroll box (and
   letting the sidebar scroll, which is what dashboard-v2.css argued for) or
   positioning the tooltip from JS. Both are bigger changes than this report
   warrants.
   ------------------------------------------------------------------------ */

body.dashboard-v2-enabled #stats-container > [data-team-stat-card]:first-child .team-device-tooltip {
    top: calc(100% + 8px);
    bottom: auto;
    transform: translate(-50%, -4px);
}

body.dashboard-v2-enabled #stats-container > [data-team-stat-card]:first-child .team-device-indicator:hover .team-device-tooltip,
body.dashboard-v2-enabled #stats-container > [data-team-stat-card]:first-child .team-device-indicator:focus .team-device-tooltip,
body.dashboard-v2-enabled #stats-container > [data-team-stat-card]:first-child .team-device-indicator:focus-within .team-device-tooltip {
    transform: translate(-50%, 0);
}

/* The arrow has to move to the tooltip's TOP edge and point up. */
body.dashboard-v2-enabled #stats-container > [data-team-stat-card]:first-child .team-device-tooltip::after {
    top: auto;
    bottom: 100%;
    border-top-color: transparent;
    border-bottom-color: rgba(34, 211, 238, 0.28);
}

/* The tooltip is anchored to the ROW, not to the 16px icon.
   .team-device-indicator is `position: relative` (css/style.css), so it was the
   tooltip's containing block and `left: 50%` centred a 148px-wide box on a
   16px icon. That hangs ~44px past the row's right edge, and because
   #stats-container scrolls, an absolutely-positioned descendant sticking out
   counts toward scrollWidth — so the members list grew a horizontal scrollbar
   even with the tooltip hidden (`visibility: hidden` still takes part in
   layout). Measured: 276px of scroll width in a 233px box, and exactly 233px
   once the tooltips are taken out.

   Making the indicator `static` inside this panel hands the containing block to
   the card, which is the full row width, so the same tooltip centres in 227px
   with room either side and contributes nothing to horizontal scroll.
   Pre-existing, not introduced by the merged panel — but it lives in the panel
   this work rebuilt, and it is two lines to fix. */
body.dashboard-v2-enabled #stats-container .team-device-indicator {
    position: static;
}

/* Positioned ancestors are gone from the icon, so the hovered CARD is what has
   to paint above its neighbours for the tooltip to clear them. */
body.dashboard-v2-enabled #stats-container .team-stat-card:hover,
body.dashboard-v2-enabled #stats-container .team-stat-card:focus-within {
    position: relative;
    z-index: 60;
}

/* ---------------------------------------------------------------------------
   9. TICKETS TOOLBAR — grouping, a sort that looks like a sort, a shrink order
   ---------------------------------------------------------------------------
   Three problems in one row, measured at 1280px with BOTH rails expanded
   (column 688px, content 725px):

     1. Export hung 37px outside the column and could not be clicked, and
        #bulk-select-btn was squeezed to 30px so its "Select" label was CLIPPED
        rather than dropped. css/style.css pins this row `flex-wrap: nowrap`
        between 1024 and 1540px, so there is nowhere for the tail to go.
     2. #ticket-sort is a SORT but wears the same grey dropdown as
        #filter-period, which is a FILTER — nothing said that changing it does
        not change which tickets you see.
     3. #ticket-count-display carries `ml-auto`, so the status text landed
        BETWEEN the two action buttons and split them.

   Note on wrapping: letting the row wrap is NOT a safe floor here. The search
   field is `flex-1`, so with wrap enabled it takes a whole line to itself and
   pushes everything else down — that is the failure this file already had to
   re-pin `nowrap` for above 1540px (see section 4). The row therefore stays
   nowrap and sheds items in a defined order instead.
   ------------------------------------------------------------------------ */

/* Grouping: filters and the count they describe on the left, actions on the
   right. Only `order` changes — the DOM order is untouched, because
   dashboard-v2.js inserts the mobile actions after the row's first `.relative`
   (the search wrap) and moving nodes would break that contract. */
body.dashboard-v2-enabled #tickets-filter-bar #bulk-select-btn { order: 0; }
body.dashboard-v2-enabled #tickets-filter-bar div:has(> #search-input) { order: 1; }
body.dashboard-v2-enabled #tickets-filter-bar #filters-panel-wrap { order: 2; }
body.dashboard-v2-enabled #tickets-filter-bar #filter-period { order: 3; }
body.dashboard-v2-enabled #tickets-filter-bar #ticket-sort { order: 4; }
body.dashboard-v2-enabled #tickets-filter-bar #filtered-mail-btn { order: 5; }
body.dashboard-v2-enabled #tickets-filter-bar #ticket-count-display { order: 6; }
body.dashboard-v2-enabled #tickets-filter-bar #export-btn { order: 7; }
body.dashboard-v2-enabled #tickets-filter-bar #priority-legend-wrap { order: 8; }

/* The count keeps company with the filters it describes. The row's free space
   is parked in ONE place — Export's auto margin — which is also what absorbs
   the hover expansion below, so the legend to its right never moves. */
body.dashboard-v2-enabled #tickets-filter-bar #ticket-count-display {
    margin-left: 0 !important;
}

body.dashboard-v2-enabled #tickets-filter-bar #export-btn {
    margin-left: auto;
}

/* The search field is `flex-1`, so it swallowed every spare pixel — 644px at
   1600px, far more than a search box needs, while everything else huddled on
   the right. Capped on desktop; the surplus becomes the gap before Export.
   Phones keep the full-width field. */
@media (min-width: 1024px) {
    body.dashboard-v2-enabled #tickets-filter-bar div:has(> #search-input) {
        max-width: 460px;
    }
}

/* Export as a download button that says its name on hover.

   FIRST ATTEMPT — animating the label's max-width inside the button — was
   wrong: the row has only ~18px of slack at 1600px, so the button's growth was
   paid for by the SEARCH FIELD and the whole middle of the row jumped ~30px
   left on hover. Measured, not guessed.

   The label is now a tooltip positioned OUT of flow, so hovering changes
   nothing about the layout. It opens downward — upward would go behind the
   toolbar's own border — and is right-anchored because the button sits at the
   end of the row. Keyboard focus shows it too, so this is not mouse-only, and
   the button keeps its aria-label for screen readers. */
@media (min-width: 1024px) {
    body.dashboard-v2-enabled #tickets-filter-bar #export-btn {
        position: relative;
        padding-left: 11px !important;
        padding-right: 11px !important;
        gap: 0;
    }

    body.dashboard-v2-enabled #tickets-filter-bar #export-btn [data-export-label] {
        position: absolute;
        top: calc(100% + 7px);
        right: 0;
        z-index: 70;
        padding: 4px 9px;
        border-radius: 7px;
        border: 1px solid var(--dashboard-v2-border, rgba(148, 163, 184, 0.28));
        background: var(--dashboard-v2-surface-strong, rgba(15, 23, 42, 0.97));
        color: var(--dashboard-v2-text, #E2E8F0);
        font-size: 11px;
        font-weight: 600;
        white-space: nowrap;
        box-shadow: 0 8px 20px rgba(2, 6, 23, 0.45);
        opacity: 0;
        visibility: hidden;
        transform: translateY(-3px);
        transition: opacity 0.13s ease, transform 0.13s ease, visibility 0.13s;
        pointer-events: none;
    }

    body.dashboard-v2-enabled #tickets-filter-bar #export-btn:hover [data-export-label],
    body.dashboard-v2-enabled #tickets-filter-bar #export-btn:focus-visible [data-export-label] {
        opacity: 1;
        visibility: visible;
        transform: translateY(0);
    }
}


/* A sort control that reads as one. The icon is a background-image and must
   carry !important: css/v2-tokens.css paints every select with a `background`
   SHORTHAND !important, which resets background-image to none. Later load
   order alone would not survive that. */
body.dashboard-v2-enabled #tickets-filter-bar #ticket-sort {
    padding-left: 30px !important;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394A3B8' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 7h13M4 12h9M4 17h5'/%3E%3C/svg%3E") !important;
    background-repeat: no-repeat !important;
    background-position: left 10px center !important;
    background-size: 14px 14px !important;
}

[dir="rtl"] body.dashboard-v2-enabled #tickets-filter-bar #ticket-sort {
    padding-left: 12px !important;
    padding-right: 30px !important;
    background-position: right 10px center !important;
    transform: none;
}

/* Select was the only control in the row shaped like a pill: it carries
   `.dashboard-v2-filter-control`, which rounds it to 999px, while Filters, the
   selects, Export and the legend are all 8px rectangles. Leading the row, that
   mismatch is the first thing the eye lands on. MEASURED off the live element,
   not read off the class list: 999px vs 8px everywhere else. */
body.dashboard-v2-enabled #tickets-filter-bar #bulk-select-btn {
    border-radius: 8px !important;
}

/* Breathing room for the control that now LEADS the row. Measured: only 10px
   separated the bar's left edge from the first control. That was fine when a
   wide search field led — a big field close to the edge reads as deliberate —
   but a small button there just looks squeezed against the wall. It gets a real
   gutter, a little more internal padding than the 14px it inherits, and a gap
   after it so it reads as its own thing rather than a prefix to the search.

   Scoped so the squeezed case does not pay for it: the compact block below
   zeroes these again, because between 1024 and 1365px with both rails open the
   row has no width to spare. */
@media (min-width: 1024px) {
    body.dashboard-v2-enabled #tickets-filter-bar #bulk-select-btn {
        margin-left: 8px;
        margin-right: 6px;
        padding-left: 16px !important;
        padding-right: 16px !important;
    }

    [dir="rtl"] body.dashboard-v2-enabled #tickets-filter-bar #bulk-select-btn {
        margin-left: 6px;
        margin-right: 8px;
    }
}

/* One height for the whole row. MEASURED, because the eye reads this as
   "slightly off" without being able to name it: the row shipped THREE heights
   — 38px (search, period, sort, count), 36px (Select) and 32px (Filters, the
   legend, Export). 38px is the majority and it is what the bar is already sized
   to, so levelling up costs no vertical space at all.

   css/style.css:10252 pins the legend and Export to `height: 32px !important`.
   That pass was fixing a REAL bug (Export sat taller than its neighbours and the
   legend had no light-mode arm) and its stated intent was "same metrics as
   Filters" — consistency. It just settled on the minority value. This keeps the
   intent and moves the number, so it needs !important to beat that rule. */
body.dashboard-v2-enabled #tickets-filter-bar #priority-legend-btn,
body.dashboard-v2-enabled #tickets-filter-bar #filters-panel-btn,
body.dashboard-v2-enabled #tickets-filter-bar #bulk-select-btn,
body.dashboard-v2-enabled #tickets-filter-bar #export-btn {
    height: 38px !important;
    min-height: 38px !important;
}

/* The "?" stays square, so its width tracks the new height. */
body.dashboard-v2-enabled #tickets-filter-bar #priority-legend-btn {
    width: 38px !important;
    min-width: 38px !important;
}

/* Its panel opened from the left edge; at the end of the row that would run
   off-screen. */
body.dashboard-v2-enabled #tickets-filter-bar #priority-legend-panel {
    right: 0;
    left: auto;
}

/* The actions are small and fixed-content; they must never be the thing that
   gives. Without this the row satisfied itself by crushing #bulk-select-btn to
   27px — narrower than its own 16px icon plus padding, so the ICON clipped.
   The search field absorbs instead, which is what a flex-1 field is for. */
body.dashboard-v2-enabled #tickets-filter-bar #bulk-select-btn,
body.dashboard-v2-enabled #tickets-filter-bar #export-btn,
body.dashboard-v2-enabled #tickets-filter-bar #priority-legend-wrap {
    flex-shrink: 0;
}

/* Above 1540px css/style.css stops pinning this row, so it is free to wrap —
   and with a rail open it does not fit, so it pushed its LAST item onto a second
   line: 104px of toolbar instead of 38px, and after the reorder the item that
   dropped was the legend. On desktop this row must SHRINK, never wrap — the
   search field absorbs (that is what a flex-1 field is for), the actions hold
   their size, and nothing lands on a second line. Left rail open at 1600px:
   104px -> 38px, measured.

   Deliberately NOT scoped to the rail classes: the wrap happens with ONE rail
   open too, which is the app's default state. Below 1024px it must still wrap —
   that is the phone layout — hence the media query. */
@media (min-width: 1024px) {
    body.dashboard-v2-enabled #tickets-filter-bar > .max-w-7xl {
        flex-wrap: nowrap !important;
    }
}

/* --- The shrink order -----------------------------------------------------
   Pressure is a function of viewport width AND how many rails are open, and a
   media query cannot see the rails — so this pairs the width with the rail
   state classes dashboard-v2.js stamps on <body> (dashboard-v2-left-collapsed /
   dashboard-v2-right-collapsed; absent = that rail is EXPANDED). If those class
   names ever change, this quietly stops applying and the clipping returns.

   What yields, in order: the legend (pure help, and its panel is one click away
   again once a rail is closed), then the word "tickets" after the count, then
   the Select label — deliberately HIDDEN with the button squared off, instead of
   being clipped mid-word as it was. Nothing becomes unreachable. */
@media (min-width: 1024px) and (max-width: 1365px) {
    body.dashboard-v2-enabled:not(.dashboard-v2-left-collapsed):not(.dashboard-v2-right-collapsed) #tickets-filter-bar #priority-legend-wrap {
        display: none;
    }

    body.dashboard-v2-enabled:not(.dashboard-v2-left-collapsed):not(.dashboard-v2-right-collapsed) #tickets-filter-bar #ticket-count-display span:last-child {
        display: none;
    }

    body.dashboard-v2-enabled:not(.dashboard-v2-left-collapsed):not(.dashboard-v2-right-collapsed) #tickets-filter-bar #bulk-select-btn > span {
        display: none;
    }

    body.dashboard-v2-enabled:not(.dashboard-v2-left-collapsed):not(.dashboard-v2-right-collapsed) #tickets-filter-bar #bulk-select-btn {
        margin-left: 0;
        margin-right: 0;
        padding-left: 10px !important;
        padding-right: 10px !important;
    }

    /* With the actions no longer shrinkable, the search field is what has to
       give, so its floor comes down in this case only. It is still wide enough
       to read a typed query; every other width keeps the roomy field. */
    body.dashboard-v2-enabled:not(.dashboard-v2-left-collapsed):not(.dashboard-v2-right-collapsed) #tickets-filter-bar div:has(> #search-input) {
        min-width: 116px !important;
    }
}

/* Dashboard design audit, 2026-09-10. Authoritative dashboard light palette.
   The aliases let existing shared components consume the same values without
   changing supporting pages. Component rules below beat legacy ID-level paint. */
html[data-theme="light"] body.dashboard-v2-enabled {
    --v2-bg: #f8fafc;
    --v2-surface: #ffffff;
    --v2-surface-soft: #f1f5f9;
    --v2-surface-strong: #ffffff;
    --v2-surface-elevated: #ffffff;
    --v2-border: #e2e8f0;
    --v2-border-strong: #64748b;
    --v2-text: #0f172a;
    --v2-muted: #475569;
    --v2-accent: #4338ca;
    --v2-danger: #b91c1c;
    --v2-warning: #92400e;
    --v2-success: #166534;
    --v2-info: #075985;
    --dashboard-v2-bg: var(--v2-bg);
    --dashboard-v2-panel: var(--v2-surface);
    --dashboard-v2-panel-soft: var(--v2-surface-soft);
    --dashboard-v2-panel-strong: var(--v2-surface-strong);
    --dashboard-v2-border: var(--v2-border);
    --dashboard-v2-border-strong: var(--v2-border-strong);
    --dashboard-v2-text: var(--v2-text);
    --dashboard-v2-muted: var(--v2-muted);
    --dashboard-v2-accent: var(--v2-accent);
    --lm-shell: var(--v2-bg);
    --lm-shell-strong: var(--v2-surface-soft);
    --lm-panel: var(--v2-surface-soft);
    --lm-card: var(--v2-surface);
    --lm-card-soft: var(--v2-surface);
    --lm-border: var(--v2-border);
    --lm-border-soft: var(--v2-border);
    --lm-text: var(--v2-text);
    --lm-muted: var(--v2-muted);
    --lm-faint: var(--v2-muted);
    --lm-primary: var(--v2-accent);
    --lm-primary-dark: var(--v2-accent);
    --v2-shadow-soft: 0 1px 2px rgba(15, 23, 42, 0.05);
}

html[data-theme="light"] body.dashboard-v2-enabled #app-container > header,
html[data-theme="light"] body.dashboard-v2-enabled #tickets-filter-bar,
html[data-theme="light"] body.dashboard-v2-enabled #desktop-tab-bar {
    background: var(--v2-surface) !important;
    border-color: var(--v2-border) !important;
    color: var(--v2-text) !important;
    box-shadow: none !important;
}

html[data-theme="light"] body.dashboard-v2-enabled #desktop-tab-bar > .relative {
    background: var(--v2-bg) !important;
    border-color: var(--v2-border) !important;
    box-shadow: none !important;
}
html[data-theme="light"] body.dashboard-v2-enabled #desktop-tab-bar #tab-indicator {
    background: #eef2ff !important;
    border-color: #c7d2fe !important;
    box-shadow: inset 0 -2px var(--v2-accent) !important;
}
html[data-theme="light"] body.dashboard-v2-enabled #desktop-tab-bar .tab-btn {
    color: var(--v2-muted) !important;
}
html[data-theme="light"] body.dashboard-v2-enabled #desktop-tab-bar .tab-btn:is(.text-white, [aria-selected="true"]) {
    color: var(--v2-accent) !important;
}

/* Countdown colors are paired, including the less imminent notice. */
html[data-theme="light"] body.dashboard-v2-enabled #deployment-countdown {
    color: var(--v2-warning);
    background: #fffbeb;
    border-color: #d97706;
}
html[data-theme="light"] body.dashboard-v2-enabled #team-meeting-countdown {
    color: var(--v2-info);
    background: #f0f9ff;
    border-color: #0284c7;
}
html[data-theme="light"] body.dashboard-v2-enabled #shift-end-countdown {
    color: var(--v2-danger);
    background: #fef2f2;
    border-color: #dc2626;
}
body.dashboard-v2-enabled #schedule-countdown-stack .countdown-chip--later {
    opacity: 1;
    box-shadow: none;
}

/* Secondary actions have explicit variants; do not repaint every button. */
html[data-theme="light"] body.dashboard-v2-enabled #tickets-filter-bar :is(button, select, input),
html[data-theme="light"] body.dashboard-v2-enabled #tickets-footer :is(input, select, .dashboard-secondary-action, .source-btn) {
    background: var(--v2-surface) !important;
    color: var(--v2-text) !important;
    border-color: var(--v2-border-strong) !important;
    box-shadow: none !important;
}
html[data-theme="light"] body.dashboard-v2-enabled #tickets-filter-bar button:hover,
html[data-theme="light"] body.dashboard-v2-enabled #tickets-footer .dashboard-secondary-action:hover {
    background: var(--v2-surface-soft) !important;
}
html[data-theme="light"] body.dashboard-v2-enabled #tickets-filter-bar #bulk-select-btn[aria-pressed="true"],
html[data-theme="light"] body.dashboard-v2-enabled #tickets-footer .source-btn[data-selected="true"] {
    background: #eef2ff !important;
    color: var(--v2-accent) !important;
    border-color: var(--v2-accent) !important;
}
html[data-theme="light"] body.dashboard-v2-enabled #tickets-footer .dashboard-v2-quick-create-button {
    background: var(--v2-accent) !important;
    border-color: var(--v2-accent) !important;
    color: #fff !important;
    box-shadow: none !important;
}
html[data-theme="light"] body.dashboard-v2-enabled #tickets-footer .dashboard-v2-quick-create-button:hover {
    background: #3730a3 !important;
}
/* #tickets-footer itself is a transparent, click-through band (dashboard-v2.css
   ~1271); the composer you see is .dashboard-v2-quick-create-inner, so that is
   what gets the opaque surface. Painting the footer instead loses to the
   (1,3,2) transparent rule and would be a full-width band if it ever won. */
html[data-theme="light"] body.dashboard-v2-enabled #tickets-footer .dashboard-v2-quick-create-inner {
    background: var(--v2-surface) !important;
    border-color: var(--v2-border) !important;
    box-shadow: 0 -2px 12px rgba(15, 23, 42, 0.08) !important;
}
body.dashboard-v2-enabled #tickets-footer :is(input, select, button),
body.dashboard-v2-enabled #tickets-filter-bar :is(input, select, button) {
    min-height: 38px;
    border-radius: 8px !important;
    font-size: 13px;
}
body.dashboard-v2-enabled #tickets-footer #assign-to { min-width: 128px; }
body.dashboard-v2-enabled #tickets-footer :is(button, input, select):disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

/* Card paint is deliberately NOT repainted here. A (1,3,2) !important card
   background/shadow outranks the (1,2,1) super-urgent frame and mention-flash
   rules in dashboard-v2.css, and the super-urgent red frame is an owner-
   requested fix for light mode. Audit D08 (quieter urgency) needs that call.
   Row typography/target growth (D09/D12) is also left out: measured, it took a
   collapsed row 74px -> 86px in BOTH themes, ~16% fewer tickets on screen. */

html[data-theme="light"] body.dashboard-v2-enabled #stats-container .team-stat-card {
    background: var(--v2-surface) !important;
    border-color: var(--v2-border) !important;
    box-shadow: none !important;
}
body.dashboard-v2-enabled #stats-container .team-stat-name { font-size: 13px !important; }
/* Visibility:hidden removed these buttons from keyboard navigation. Opacity
   keeps them focusable; focus-within and touch reveal the existing actions. */
body.dashboard-v2-enabled #stats-container .team-stat-actions { visibility: visible; }
@media (hover: none) {
    body.dashboard-v2-enabled #stats-container .team-stat-actions { opacity: 1; }
}
html[data-theme="light"] body.dashboard-v2-enabled #dashboard-v2-floating-dock .dashboard-v2-floating-dock-group {
    background: var(--v2-surface) !important;
    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08) !important;
    border-color: var(--v2-border) !important;
}
html[data-theme="light"] body.dashboard-v2-enabled #dashboard-v2-floating-dock .dashboard-v2-floating-button:not([aria-expanded="true"]) {
    color: var(--v2-muted) !important;
    box-shadow: none !important;
}

/* Focus is independent of shadows and survives forced-colors mode. */
body.dashboard-v2-enabled #app-container :focus-visible,
body.dashboard-v2-enabled :is(.modal, #notification-center, #dashboard-v2-floating-dock) :focus-visible {
    outline: 2px solid var(--dashboard-v2-accent) !important;
    outline-offset: 2px !important;
    scroll-margin-block: 16px;
}
body.dashboard-v2-enabled .dashboard-v2-main {
    scroll-padding-bottom: calc(var(--compose-bar-height, 80px) + 24px);
}
@media (forced-colors: active) {
    body.dashboard-v2-enabled :focus-visible { outline: 2px solid Highlight !important; }
}
