/* ============================================================================
   responsive.css — one layout system for every window size.
   ============================================================================

   Loaded LAST (after mobile.css and team.css) so it wins on cascade order
   without !important wars against the ~90 scattered media queries above it.

   The problem it solves
   ---------------------
   Every breakpoint in this app asks about the WINDOW. Nothing on screen is as
   wide as the window: the sidebar and gutters take 304px off the top, and the
   sidebar's width changes at runtime. So a card that is 1130px wide at a 1440px
   window with the rail collapsed is 700px wide at a 1024px window with the rail
   open — and a viewport query calls both "desktop" and hands them the same
   one-row layout. That is how an initiative header ends up with its rule text
   squeezed into a 90px column of single words while the buttons hang off the
   card's right edge.

   So this file keys off `html[data-cw]` — the CONTENT width bucket, stamped by
   responsive.js:

       xs  < 440    sm  440–639    md  640–879    lg  880–1139    xl  ≥ 1140

   Three rules for editing this file
   ---------------------------------
   1. Numbers never wrap. A metric that breaks across lines is unreadable; a
      metric that forces a scrollbar is merely inconvenient. Always pick the
      scrollbar.
   2. Identity before metrics. When a row can't hold everything, the name keeps
      the full width and the metrics go to the next line — never the reverse.
   3. No fixed widths on anything that holds text. `calc(100% - 460px)` is a
      time bomb; `min()` / `clamp()` / `auto-fit` are not.
   ========================================================================== */


/* ══════════════════════════════════════════════════════════════════════════
   1. RAILS — the things that must be true at every width
   ══════════════════════════════════════════════════════════════════════════ */

/* Flex and grid children default to min-width:auto, which means "refuse to
   shrink below my content." That single default is the root cause of most of
   this app's overflow: one long campaign name in a flex row shoves its
   siblings off the edge instead of wrapping. The mobile layer already does this
   below 768px; it needs to be true everywhere. */
/* Wrapped in :where() so the whole selector has ZERO specificity. A rail must
   never win against a component that asks for a real minimum — `.cmp-search {
   min-width: 200px }` is deliberate, and an un-scoped `min-width: 0` here
   collapsed that search box to 4px. :where() makes every one of these a
   default that any real rule overrides. */
:where(
  .container, .page, .card, .grid, .stat,
  [class*="-row"], [class*="-head"], [class*="-header"],
  [class*="-toolbar"], [class*="-bar"]
) > * { min-width: 0; }

/* Long unbroken tokens — UTM campaign names, lead source codes, account ids,
   URLs — have no break opportunity, so they set an enormous min-content width
   and push their container open. Let them break mid-token as a last resort. */
:where(
  .card, .stat, .modal, .page,
  .cmp-camp-row, .alert-item,
  .initiative-card, .init-group,
  .pm-cat, .cpl-stat,
  td, th, pre, code
) { overflow-wrap: anywhere; }

/* Media never exceeds its box. */
img, svg, video, canvas, iframe { max-width: 100%; }
/* ...EXCEPT an inline icon inside a flex/grid container. A percentage max-width
   on a replaced element whose containing block is sized from its own content
   resolves to 0 in Blink, so the icon collapses to a 0-wide box and its strokes
   get clipped away — a button that renders as an empty square. Icons carry their
   own explicit width/height, so they never needed the cap. Measured on the
   Ineligible > By Division expand chevron (Aug 2026); `flex: none` alone does
   NOT fix it.

   The exemption deliberately does NOT require width/height ATTRIBUTES. Most of
   the app's icons are sized in CSS and carry only a `viewBox`, so the attribute
   form of this rule left them collapsing — that is what hid all three BridgeBI
   Agent header buttons (New / Expand / Close) behind an empty square: measured
   0×15px, `max-width: 100%` resolving to 0 inside a `display: grid` button
   (Aug 2026). An svg is never a DIRECT child of a button/a/label/chip unless it
   is an icon, so lifting the cap for that position is safe; a chart svg lives in
   a div and still gets capped. */
:is(button, a, summary, label, .chip, [role="button"]) > svg { max-width: none; }
/* The OTHER way an icon reaches zero, and the one that hid the conversation
   rail's delete button (measured 0×13px, Aug 2026): index.html styles the bare
   `button` tag `display: inline-flex; padding: 10px 16px`, so ANY icon button
   narrower than 32px has a zero-width content box — and a flex item with the
   default `flex-shrink: 1` obeys it. `flex: none` makes an icon use its own
   width and overflow instead of vanishing; a clipped icon can be seen and
   fixed, an absent one reads as a broken feature. Components that care still
   set their own padding; this only stops the silent collapse. */
:is(button, a, summary, label, .chip, [role="button"]) > svg { flex: none; }

/* A <pre> full of JSON is the single easiest way to make a page scroll sideways
   forever — and re-wrapping it instead makes it thousands of pixels tall, which
   just trades a horizontal problem for a vertical one. So: keep the source
   formatting, cap the height, and scroll in both axes inside the block. */
:where(pre) {
  max-width: 100%;
  max-height: 60vh;
  overflow: auto;
  overscroll-behavior: contain;
}


/* ══════════════════════════════════════════════════════════════════════════
   2. SHELL — gutters that shrink with the room available
   ══════════════════════════════════════════════════════════════════════════ */

:root { --nb-gutter: 32px; }
html[data-cw="lg"] { --nb-gutter: 24px; }
html[data-cw="md"] { --nb-gutter: 20px; }
html[data-cw="sm"] { --nb-gutter: 14px; }
html[data-cw="xs"] { --nb-gutter: 12px; }

html[data-cw="md"] .container,
html[data-cw="lg"] .container { padding-left: var(--nb-gutter); padding-right: var(--nb-gutter); }

html[data-cw="md"] .header-row,
html[data-cw="lg"] .header-row { padding-left: var(--nb-gutter); padding-right: var(--nb-gutter); }

/* Nothing may make the document itself scroll sideways. Every overflow in this
   app now has a local scroller or wraps, so this is a backstop, not a strategy —
   but a backstop that turns "the whole page is 160px off" into "one component
   is clipped" is worth having. */
html, body { max-width: 100%; overflow-x: clip; }

/* The header's own right cluster: five controls that must never stack. */
.header-actions-wrap { flex-wrap: nowrap; }
html[data-cw="md"] .header-row,
html[data-cw="sm"] .header-row,
html[data-cw="xs"] .header-row { gap: 10px; }


/* ══════════════════════════════════════════════════════════════════════════
   3. TABLES — numbers never wrap, the table scrolls instead
   ══════════════════════════════════════════════════════════════════════════

   A 13-column table squeezed into 620px gives each cell 23px of usable width,
   and "487 +59.2%" becomes six lines of one or two characters. That is the
   heatmap bug, the channel-quality bug and the CPL bug — one cause, three
   symptoms.

   The fix is not to make the table narrower. It is to let the table be as wide
   as its numbers need and put the overflow in a local scroller with a fade at
   the edge so it is obvious there is more. responsive.js does the wrapping. */

.nb-tscroll {
  position: relative;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  max-width: 100%;
}
/* Right-edge fade = the affordance that says "more columns this way". Only
   drawn while there is something to scroll to. */
.nb-tscroll.has-overflow:not(.at-end) {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent 100%);
          mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent 100%);
}
.nb-tscroll > table { margin-bottom: 0; }

/* Numeric cells hold their line. This raises the table's min-content width,
   which is exactly what we want: it overflows into the scroller instead of
   shredding the numbers. Text columns still wrap and absorb the reflow. */
th, td.numeric, td.num, td.mono,
td[class*="-cell"], td[class*="-val"],
.hm-table td, .cpl-table td, #channelTable td, #cplChannelTable td {
  white-space: nowrap;
}
/* …except the first column, which is almost always a name and is the one
   column that SHOULD wrap rather than widen the table. */
.hm-table td:first-child,
#channelTable td:first-child, #channelTable th:first-child,
#cplChannelTable td:first-child, #cplChannelTable th:first-child {
  white-space: normal;
}

/* The heatmap's paired value+delta cells: keep each part on its own single
   line, never let either part wrap inside itself. */
.hm-table .hm-yoy { white-space: nowrap; }

/* The three all-numeric dashboards. `min-width: max-content` says "be as wide as
   your numbers need" — the scroller absorbs the rest. Deliberately NOT applied to
   every table in the app: a table with a sentence in a cell would grow to the
   width of that sentence and scroll when it should simply wrap. */
.hm-table,
#channelTable,
#cplChannelTable { min-width: max-content; }

/* …and the label column of each keeps a floor, so "Paid Display & Video" gets
   two comfortable lines instead of six cramped ones. */
#channelTable td:first-child, #channelTable th:first-child,
#cplChannelTable td:first-child, #cplChannelTable th:first-child,
.hm-table td:first-child { min-width: 14ch; }

/* Header cells with two-word labels ("Closed Lost %", "Conv New") were
   wrapping to four lines in a 76px column. Nowrap above handles it; this keeps
   them from getting taller than the numbers they label. */
th { vertical-align: bottom; }


/* ══════════════════════════════════════════════════════════════════════════
   4. CONTROL ROWS — labels never stack, rows wrap or scroll
   ══════════════════════════════════════════════════════════════════════════

   "🔗 Compartir" rendering as three lines of four characters is the same bug
   thirty times over: a button in a flex row with no `white-space` and a parent
   that would rather crush its children than wrap. Buttons get nowrap; the rows
   that hold them get permission to wrap. */

.filters .chip,
.ov-pill,
.cmp-period-btn,
.inelig-subtab,
.init-action-btn,
.cmp-pin-btn,
.header-btn,
.hm-mode-btn,
#hmModeToggle > button,
#ovRankMetricSeg > button,
#bbCustomBtn, #kpiCustomizeBtn, #syncLiveAdsBtn,
#shareBoardBtn, #newGroupBtn, #bulkInitiativesBtn,
#cmpRawNamesToggle, #cplFreshnessMark,
.initiatives-toolbar button,
.pm-cat-head button,
.powerbi-banner-hint,
.tracker-actions button { white-space: nowrap; }

/* The systemic version of the rule above. A button, chip, badge, tag or pill is
   a LABEL — it names one thing, and a label broken across four lines of three
   characters has stopped naming anything. Prose wraps; labels don't.
   `max-width: 100%` keeps a long label from escaping its parent, and :where()
   means any component that deliberately wraps its own button still wins.

   Keep this list to things that ARE labels. `[class*="-flag"]` was in it briefly
   and matched `.pm-flag-name` — a full sentence — which then refused to wrap and
   ran 96px out of its column. Patterns here must not be able to catch prose. */
:where(button, .chip, [class*="badge"], [class*="-tag"], [class*="-pill"], .cmp-waste-flag) {
  white-space: nowrap;
  max-width: 100%;
}

/* Rows of controls wrap onto a second line rather than squeezing their labels. */
.filters,
.initiatives-toolbar,
.initiatives-toolbar-right,
.tracker-actions,
#kpiScopeBar,
.cmp-period-presets,
.pm-cat-head { flex-wrap: wrap; }

/* Segmented controls are the exception: wrapping breaks the "one connected
   control" read, so they scroll as a unit instead. */
#hmModeToggle,
#ovRankMetricSeg,
.inelig-subtab-bar {
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}
#hmModeToggle::-webkit-scrollbar,
#ovRankMetricSeg::-webkit-scrollbar,
.inelig-subtab-bar::-webkit-scrollbar { display: none; }
.inelig-subtab { flex: 0 0 auto; }
html[data-cw="sm"] .inelig-subtab,
html[data-cw="xs"] .inelig-subtab { padding: 12px 13px; font-size: 13px; }

/* Section headings sit next to badges and hints. Baseline-aligned flex with a
   gap keeps them from ever landing on top of each other, and nowrap keeps the
   heading itself from being crushed to a 23px column. */
.initiatives-toolbar h2,
#byProgramHeader,
.card > h2, .card > h3 { min-width: 0; }
.initiatives-toolbar h2 { white-space: nowrap; flex: 0 0 auto; }
#byProgramHeader {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
}
#byProgramHeader > span { min-width: 0; }
.byprogram-goals-note { margin-left: 0; overflow-wrap: anywhere; }
.hm-hint { margin-left: 0; flex: 0 0 auto; }


/* ══════════════════════════════════════════════════════════════════════════
   5. STAT / KPI GRIDS — auto-fit, never a hard column count
   ══════════════════════════════════════════════════════════════════════════

   `repeat(6, 1fr)` at a 700px content width gives each tile 105px, and
   "$320.47" in 20px JetBrains Mono needs 92px plus 28px of padding. The tile
   can't say no, so the number spills out of it. auto-fit lets the grid drop to
   the column count that actually fits. */

.cpl-stats { grid-template-columns: repeat(auto-fit, minmax(clamp(132px, 15vw, 168px), 1fr)); }
.grid-6 { grid-template-columns: repeat(auto-fit, minmax(clamp(130px, 14vw, 180px), 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(clamp(150px, 18vw, 210px), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(clamp(180px, 22vw, 260px), 1fr)); }

/* A metric's value scales with the room its tile got, so it never has to wrap
   and never has to be truncated. */
.cpl-stat .val { font-size: clamp(15px, 1.35vw, 21px); white-space: nowrap; }
.stat .stat-value, .stat .value { white-space: nowrap; }


/* ══════════════════════════════════════════════════════════════════════════
   6. INITIATIVE CARDS — the flagship fix
   ══════════════════════════════════════════════════════════════════════════

   The header was one flex row holding: grip, chevron, [name + rule summary],
   four KPI tiles, three buttons. The KPI tiles and the buttons were both
   `flex-shrink: 0`, so at a 726px content width the name block — the only
   flexible child — collapsed to ~90px. The rule summary (a 380px monospace UTM
   string) then wrapped to one word per line, which made the header 300px tall,
   which made the vertically-centred KPI tiles sit ON TOP of the wrapped text.
   And because the tiles and buttons could not shrink, the row still overflowed
   and `overflow: visible` on this page let "Remove" hang off the card's edge.

   Four things fix it for good:
     a) the header is a GRID with named rows, so at narrow widths the metrics
        and actions move to their own line instead of competing for the name's
        space;
     b) the rule summary is clamped — it is a technical detail, not prose, and
        the full string is on the element's title attribute;
     c) the KPI strip may wrap internally and its tiles may shrink;
     d) nothing in the header is allowed to be wider than the card. */

.initiative-card-header {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto auto;
  grid-template-areas:
    "grip chev name    kpis    actions"
    ".    .    summary summary summary";
  align-items: center;
  column-gap: 12px;
  row-gap: 5px;
}
.initiative-card-header > .init-card-grip { grid-area: grip; align-self: center; }
.initiative-card-header > .initiative-card-chevron { grid-area: chev; }
/* `display: contents` promotes the name and the rule summary to grid items of
   the header itself, which is what lets the summary claim a full-width row of
   its own instead of being trapped in the name's column. Without it the summary
   is stuck sharing ~170px with the name while 380px of card sits unused. */
.initiative-card-header > .initiative-card-identity { display: contents; }
.initiative-card-header > div:not([class]) { grid-area: name; min-width: 0; }
.initiative-card-name { grid-area: name; }
.initiative-card-rules-summary { grid-area: summary; }
.initiative-card-header > .initiative-card-kpis { grid-area: kpis; }
.initiative-card-header > .initiative-card-actions { grid-area: actions; }

.initiative-card-name {
  overflow-wrap: anywhere;
  line-height: 1.25;
}

/* The rule summary: one line at any width, full text on hover/tap via title.
   Two lines would be worse than one — the point of this line is "which UTM does
   this initiative match", and if it needs three lines to answer that, the card
   is the wrong place to read it. */
.initiative-card-rules-summary {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1.5;
}

.initiative-card-kpis { flex-wrap: wrap; justify-content: flex-end; min-width: 0; }
.init-kpi { flex: 0 1 auto; min-width: 0; padding: 6px 12px; }
.init-kpi .k-val { white-space: nowrap; }
.init-kpi .k-label { white-space: nowrap; }
.initiative-card-actions { flex-wrap: nowrap; }

/* ── md (640–879): metrics get their own row, actions sit at its end ─────── */
html[data-cw="md"] .initiative-card-header {
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  grid-template-areas:
    "grip chev name    name"
    ".    .    summary summary"
    ".    .    kpis    actions";
  align-items: center;
  row-gap: 12px;
}
html[data-cw="md"] .initiative-card-header > .initiative-card-kpis { justify-content: flex-start; }

/* ── sm / xs: full stack, KPIs on a 2-up grid, actions full width ────────── */
html[data-cw="sm"] .initiative-card-header,
html[data-cw="xs"] .initiative-card-header {
  grid-template-columns: auto auto minmax(0, 1fr);
  grid-template-areas:
    "grip chev name"
    "summary summary summary"
    "kpis kpis kpis"
    "actions actions actions";
  align-items: start;
  row-gap: 10px;
  padding: 14px 14px;
}
html[data-cw="sm"] .initiative-card-header > .init-card-grip,
html[data-cw="xs"] .initiative-card-header > .init-card-grip { align-self: start; }
html[data-cw="sm"] .initiative-card-kpis,
html[data-cw="xs"] .initiative-card-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: 8px;
  width: 100%;
}
html[data-cw="sm"] .init-kpi,
html[data-cw="xs"] .init-kpi { padding: 8px 10px; text-align: left; }
html[data-cw="sm"] .initiative-card-actions,
html[data-cw="xs"] .initiative-card-actions { width: 100%; }
html[data-cw="sm"] .init-action-btn,
html[data-cw="xs"] .init-action-btn { flex: 1 1 auto; padding: 9px 10px; min-height: 34px; }

/* ── the expanded card body (built on first open, so never seen by a sweep of
      initial page states) ──────────────────────────────────────────────────── */

/* Its campaign table is all money and counts: same treatment as the other
   numeric dashboards. Without this, "$2,272.82" came apart into four lines of
   two characters in a 55px column, and the rename pencil was clipped out of a
   fixed 260px name cell. */
.init-campaign-table { min-width: max-content; }
.init-campaign-table th,
.init-campaign-table td.tc-mono,
.init-campaign-table td.tc-r { white-space: nowrap; }
.init-campaign-table td.tc-name { white-space: normal; min-width: 24ch; overflow: visible; }
.init-campaign-table .tc-tags { display: inline-flex; flex-wrap: wrap; gap: 4px; }

/* Section headers inside the body: the "go to Campaigns" link is a label. */
.init-report-section-title { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.init-report-section-title > * { min-width: 0; }

/* The matched-values rows are inline-styled flex rows: [contains] [field:
   "value"] [13 leads]. The row could not wrap, so the lead count was squeezed
   into a 21px column and came out as "13 / lead / s". The row wraps now; the
   badge and the count hold their line; the field/value text in the middle is the
   only part that reflows, which is the right thing to give up. */
.init-report-section > div > div { flex-wrap: wrap; }
.init-report-section > div > div > span { white-space: nowrap; }
.init-report-section .init-rule-badge { flex: 0 0 auto; }
.init-matched-chip { max-width: 100%; overflow-wrap: anywhere; }

html[data-cw="sm"] :is(.tc-tag, .init-rule-badge),
html[data-cw="xs"] :is(.tc-tag, .init-rule-badge) { font-size: 10px; }
html[data-cw="sm"] .camp-rename-btn,
html[data-cw="xs"] .camp-rename-btn { min-width: 28px; }

/* Group headers have the same shape as the cards, so they get the same
   treatment — identity keeps the width, metrics move down. */
.init-group-head { flex-wrap: wrap; row-gap: 10px; }
.init-group-name { overflow-wrap: anywhere; min-width: 0; }
.init-group-kpis { flex-wrap: wrap; min-width: 0; }
html[data-cw="sm"] .init-group-kpis,
html[data-cw="xs"] .init-group-kpis { width: 100%; justify-content: flex-start; }

/* The toolbar above the board: title block and buttons on separate lines when
   they can't share one, and the date-range note wraps instead of stretching. */
html[data-cw="md"] .initiatives-toolbar,
html[data-cw="sm"] .initiatives-toolbar,
html[data-cw="xs"] .initiatives-toolbar { align-items: flex-start; }
.initiatives-toolbar-left { flex-wrap: wrap; min-width: 0; }
.initiatives-window-note { overflow-wrap: anywhere; }
html[data-cw="sm"] .initiatives-toolbar-right,
html[data-cw="xs"] .initiatives-toolbar-right { width: 100%; }
html[data-cw="sm"] .initiatives-toolbar-right > *,
html[data-cw="xs"] .initiatives-toolbar-right > * { flex: 1 1 auto; }


/* ══════════════════════════════════════════════════════════════════════════
   7. CAMPAIGNS — the fixed-width name column
   ══════════════════════════════════════════════════════════════════════════

   `flex: 0 0 var(--cmp-name-w)` with `max-width: calc(100% - 460px)` reserves
   460px for the metrics whether or not 460px exists. Below ~620px of content
   the name column's max-width goes to zero (or negative) and the name spills
   straight out of the row. Make the reservation proportional instead. */

.cmp-camp-row-info {
  flex: 1 1 var(--cmp-name-w, 320px);
  max-width: 100%;
  min-width: 0;
}

/* The search box had `min-width: 200px`, but the mobile layer's `.container *
   { min-width: 0 }` outranks it below 768px, which collapsed the field to 4px
   with the <input> hanging 44px out of it. Re-assert a floor that can also give
   up when there genuinely isn't 220px, and let the toolbar wrap so it usually
   gets its own line. */
#cmpToolbar { flex-wrap: wrap; row-gap: 10px; }
#cmpToolbar .cmp-search { flex: 1 1 220px; min-width: min(220px, 100%); }
#cmpToolbar .cmp-search-input { min-width: 0; }
.cmp-camp-row { flex-wrap: wrap; row-gap: 10px; }
.cmp-camp-row-actions { flex-wrap: wrap; }
html[data-cw="md"] .cmp-camp-row-info,
html[data-cw="sm"] .cmp-camp-row-info,
html[data-cw="xs"] .cmp-camp-row-info { flex: 1 1 100%; }
.cmp-camp-name { overflow-wrap: anywhere; }


/* ══════════════════════════════════════════════════════════════════════════
   8. POST-MORTEM + CPL + banners
   ══════════════════════════════════════════════════════════════════════════ */

.pm-cat-head { flex-wrap: wrap; row-gap: 8px; }
.pm-cat-name { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; white-space: normal; }
/* Heading + count on one line while there is room, stacked when there isn't —
   rather than the heading being crushed to a 148px column beside the count. */
.pm-framework-head { flex-wrap: wrap; row-gap: 4px; }
/* The flag rows are a 5-column grid of fixed tracks (44+1fr+92+116+78 plus
   gaps = 370px of hard minimum). Below that the audience tag is pushed out of
   its own column, so let the name wrap and the tag sit on its own line. */
.pm-flag-name { min-width: 0; white-space: normal; }
.pm-flag-name .pm-flag-aud { white-space: nowrap; }

/* The raw-rollup panel is a debugging affordance; it must not be able to change
   the page's width. Contain it in both axes and let the <pre> scroll inside. */
details.cpl-debug { min-width: 0; max-width: 100%; overflow: hidden; }
.cpl-history-wrap { min-width: 0; }

/* Tracker insight panels: small stat rows that wrap rather than spill. The
   panel is built from anonymous nested divs, so the rule has to reach any depth
   instead of naming a level. */
#isPanelTracker div { flex-wrap: wrap; row-gap: 6px; }
/* "↓ -0.3 pts vs PY" is a nowrap delta chip that is 9px wider than its column at
   360px. Let the phrase break rather than leave the column. */
#isPanelTracker span { white-space: normal; }

/* Banner hints ("run /powerbi-pull weekly") are a single short phrase. Let them
   take the width they need on their own line rather than becoming a 55px
   column of four stacked words. */
.powerbi-banner-hint { flex: 1 1 100%; white-space: normal; }
#powerbiBanner, #cplFreshnessBanner { flex-wrap: wrap; row-gap: 6px; }


/* ══════════════════════════════════════════════════════════════════════════
   9. DASHBOARD — scope pills and the KPI strip
   ══════════════════════════════════════════════════════════════════════════ */

/* The date-range card is the most-used control on the page, and on a phone it
   was the worst-looking one: `.filters-spacer` (flex:1) pushed "Lead vintage"
   and the sync button to the far right of their own wrapped rows, so the card
   read as three unrelated fragments. Kill the spacer where there is no second
   half to push away, let the four presets split the width evenly instead of
   wrapping 3+1, and pair From/To on one row. */
html[data-cw="sm"] .filters-spacer,
html[data-cw="xs"] .filters-spacer { display: none; }
html[data-cw="sm"] .filters,
html[data-cw="xs"] .filters { justify-content: flex-start; row-gap: 10px; }
html[data-cw="sm"] .filters > .chip:not(.sync-live-btn),
html[data-cw="xs"] .filters > .chip:not(.sync-live-btn) { flex: 1 1 40%; }
/* nowrap on the label so "From" can't break into "Fro / m" — the input is the
   flexible part, the word is not. */
html[data-cw="sm"] .filters > label,
html[data-cw="xs"] .filters > label {
  flex: 1 1 150px; display: flex; align-items: center; gap: 6px; white-space: nowrap;
}
html[data-cw="sm"] .filters > label input[type="date"],
html[data-cw="xs"] .filters > label input[type="date"] { flex: 1 1 auto; min-width: 0; }
/* `margin-left: auto` is what was shoving this group to the right edge of its
   own wrapped row. On one line with the rest of the toolbar that reads as
   "grouped at the end"; on its own row it just reads as misaligned. */
html[data-cw="sm"] .vintage-filter,
html[data-cw="xs"] .vintage-filter { flex-wrap: wrap; margin-left: 0; }

/* Report-card channel rows: "Paid Search" is a channel NAME sitting next to its
   swatch, and it was coming apart into three lines of four characters in a 29px
   column. The label holds its line and the cell keeps them side by side. */
.channel-cell { min-width: 0; }
.channel-label { white-space: nowrap; }

/* Division summary cards: "2,866 · 602 · 21.0%" is one compound figure, so it
   moves to its own line rather than splitting into three stacked fragments. */
.division-summary-card .metric-row { flex-wrap: wrap; row-gap: 2px; }
.division-summary-card .metric-value { white-space: nowrap; }

#kpiScopeBar { row-gap: 8px; }
.ov-pill { flex: 0 0 auto; }
html[data-cw="sm"] #kpiScopeBar,
html[data-cw="xs"] #kpiScopeBar {
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}
html[data-cw="sm"] #kpiScopeBar::-webkit-scrollbar,
html[data-cw="xs"] #kpiScopeBar::-webkit-scrollbar { display: none; }


/* ══════════════════════════════════════════════════════════════════════════
   9b. SEARCH PALETTE ON A PHONE
   ══════════════════════════════════════════════════════════════════════════

   The palette is anchored to the VIEWPORT (fixed backdrop, width clamped
   against 100vw), so unlike everything else in this file it is a media query
   rather than a content-width bucket.

   Two things it was spending width on that a phone has no use for: an "ESC"
   key hint on a device with no Esc key, and a secondary description column
   ("KPIs · Heatmap · By Program") that took 42% of the row and left the actual
   result name 123px to truncate inside. The name is what you are reading. */
@media (max-width: 560px) {
  .cmdk-backdrop { padding: 7vh 12px 12px; }
  .cmdk-input-row { padding: 12px 14px; gap: 8px; }
  .cmdk-input-row kbd { display: none; }
  .cmdk-input { min-width: 0; font-size: 16px; }   /* 16px = iOS won't zoom the page on focus */
  .cmdk-item-sub { display: none; }
  .cmdk-item { padding: 10px; gap: 8px; min-height: 44px; }
  .cmdk-item-label { white-space: normal; overflow: visible; }
  .cmdk-results { max-height: min(64vh, 520px); }
}


/* ══════════════════════════════════════════════════════════════════════════
   10. TOUCH — targets and bottom-bar clearance
   ══════════════════════════════════════════════════════════════════════════ */

/* The fixed mobile tab bar sits over the end of the page. Reserve its height
   plus the home-indicator inset so the last row of content is reachable.

   `body >` is load-bearing, not tidiness. `.page` is not a unique name in this
   app: the search palette tags a result as `.cmdk-tag.page`, and an unscoped
   `.page` rule gave that little badge 76px of bottom padding — an 89px-tall
   chip that blew every search result row up to 105px. The page containers this
   is for are all direct children of <body>, which is also how the app's own
   sidebar-offset rules select them. */
@media (max-width: 768px) {
  body:has(.m-tabbar) > .container,
  body:has(.m-tabbar) > .page {
    padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px));
  }
}

/* "INITIATIVES" is the longest label the tab bar carries — 11 characters into a
   72px column at 360px. It must stay on one line (a two-line tab label pushes
   the bar's height and breaks the icon/label rhythm), so the type gives a little
   instead of the word breaking. */
.m-tab > span { white-space: nowrap; }
@media (max-width: 412px) {
  .m-tab { font-size: 8.8px; letter-spacing: .12px; padding-left: 1px; padding-right: 1px; }
}

html[data-cw="sm"] .init-action-btn,
html[data-cw="xs"] .init-action-btn,
html[data-cw="sm"] .inelig-subtab,
html[data-cw="xs"] .inelig-subtab { min-height: 34px; }

/* Native checkboxes render at 22px, which is under the comfortable-tap floor.
   Grow the control and pad the label it sits in so the whole phrase is the
   target, not just the box. */
html[data-cw="sm"] :is(input[type="checkbox"], input[type="radio"]),
html[data-cw="xs"] :is(input[type="checkbox"], input[type="radio"]) {
  width: 24px; height: 24px;
}
html[data-cw="sm"] label:has(> input[type="checkbox"]),
html[data-cw="xs"] label:has(> input[type="checkbox"]) { padding: 4px 0; }


/* ══════════════════════════════════════════════════════════════════════════
   11. LEGIBILITY — nothing under 10px
   ══════════════════════════════════════════════════════════════════════════

   9px uppercase labels are readable on a 1440px desktop panel at arm's length
   and not on a phone. Below 640px of content they go to 10px, which is the
   floor iOS stops shrinking text at anyway. */
html[data-cw="sm"] .init-kpi .k-label,
html[data-cw="xs"] .init-kpi .k-label,
html[data-cw="sm"] .init-gkpi .gk-label,
html[data-cw="xs"] .init-gkpi .gk-label { font-size: 10px; }

/* Same for the Top/Bottom-3 ranking panel, whose chips and micro-labels are
   9.5px. Fine on a desktop panel, not on a phone held at arm's length. */
html[data-cw="sm"] :is(.ov-seg-lbl, .ov-rank-band, .ov-rank-name i, .ov-rank-chip),
html[data-cw="xs"] :is(.ov-seg-lbl, .ov-rank-band, .ov-rank-name i, .ov-rank-chip),
html[data-cw="sm"] .cpl-stat .lbl,
html[data-cw="xs"] .cpl-stat .lbl { font-size: 10.5px; }
html[data-cw="sm"] :is(.pc-code, .kc-label, .k-label, .gk-label),
html[data-cw="xs"] :is(.pc-code, .kc-label, .k-label, .gk-label) { font-size: 10px; }
