/* ==========================================================================
   BridgeBI — MOBILE LAYER
   --------------------------------------------------------------------------
   Everything in this file lives inside @media (max-width: 768px) or a
   feature/standalone query. Desktop is provably untouched: delete the two
   <link>/<script> tags in index.html and the app reverts exactly.

   Load order matters — this file is linked LAST in <head> so it wins on
   cascade order without needing !important wars against the 57 existing
   scattered media queries.

   Design basis (see MOBILE-PWA-PLAN.md §2):
     - thumb zone      -> primary nav + sheet actions at the BOTTOM
     - progressive disclosure -> headline number first, detail on tap
     - preattentive    -> direction encoded 3x (arrow + color + sign)
     - data-ink        -> no hover lifts / glow washes on a 375px budget
   ========================================================================== */

/* ==========================================================================
   0. INJECTED CHROME IS INERT OUTSIDE THE BREAKPOINT
   --------------------------------------------------------------------------
   mobile.js creates the tab bar, wordmark, install card and drawer tray. Those
   nodes PERSIST if the viewport later grows past 768px — an iPad rotating, or a
   desktop window dragged wider after loading narrow. All of their styling lives
   inside the media query below, so outside it they must be explicitly hidden:
   a <nav> with no rules defaults to display:block and the tab bar would render
   as a stray row of buttons at the bottom of the desktop document.
   Each one re-enables its own display inside the media query.
   ========================================================================== */
.m-tabbar,
.m-header-title,
.m-install-card,
.m-drawer-actions,
.m-swipe-hint { display: none; }

@media (max-width: 768px) {

  /* ---------------------------------------------------------------------
     1. STOP THE BLEEDING
     The live audit measured a 935px document on a 375px viewport. Nothing
     below matters until the page stops scrolling sideways, because the
     browser lets content spill instead of reflowing it.
     --------------------------------------------------------------------- */
  html, body {
    max-width: 100%;
    overflow-x: hidden;
  }

  /* THE fix for the guillotined KPI numbers. Grid and flex children default
     to min-width:auto, so they refuse to shrink below their content width and
     shove the whole row past the viewport. */
  .container *,
  .grid > *,
  .stat,
  .card > * {
    min-width: 0;
  }

  /* Long unbroken strings (campaign names like NUCG_Meta_PR_Pro_AD_Nursing_DS,
     account IDs, URLs) are a primary overflow source in this app. */
  .card, .stat, .modal, .cmp-camp-row, .alert-item, td, th {
    overflow-wrap: anywhere;
    word-break: break-word;
  }

  .container {
    padding: 14px 12px calc(72px + env(safe-area-inset-bottom, 0px)) !important;
    max-width: 100%;
  }

  /* ---------------------------------------------------------------------
     2. GRIDS — the missing small breakpoints
     .grid-6 collapsed 6->3 at 1100px and then had NO rule below that, so at
     375px six KPI tiles shared 3 columns of 82px.
     --------------------------------------------------------------------- */
  .grid { gap: 12px; }
  .grid-3, .grid-4, .grid-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* ---------------------------------------------------------------------
     3. KPI STAT TILES
     Was: overflow:hidden + fixed 32px type inside an 82px box -> "16,6".
     Now: 2-up grid, no clipping, fluid type, number always fully readable.
     --------------------------------------------------------------------- */
  .stat {
    overflow: visible;          /* was hidden — this is what cut the digits */
    padding: 13px 13px 12px;
    border-radius: 12px;
  }
  .stat:hover { transform: none; box-shadow: var(--sh-1); }
  .stat::after { display: none; }   /* gold corner wash: pure chrome, costs legibility */

  .stat-label {
    font-size: 10.5px;
    letter-spacing: .5px;
    margin-bottom: 6px;
    line-height: 1.25;
  }
  .stat-value {
    /* scales 21px -> 30px with viewport instead of clipping at a fixed 32px */
    font-size: clamp(21px, 6.4vw, 30px);
    line-height: 1.05;
    white-space: nowrap;
    letter-spacing: -.5px;
  }
  .stat-delta { font-size: 11.5px; margin-top: 6px; }
  .stat-note  { font-size: 10px; margin-top: 6px; }

  /* breakdown was a flex row with a 14px gap — stacks instead of clipping */
  .stat-breakdown {
    flex-direction: column;
    gap: 7px;
    margin-top: 8px;
    padding-top: 8px;
  }
  .stat-breakdown-item {
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
  }
  .stat-breakdown-label { font-size: 9.5px; }
  .stat-breakdown-value { font-size: 12.5px; }

  /* Von Restorff: exactly one card per section should shout. Damp the rest
     so the genuinely bad number is the one that gets remembered. */
  .stat::before { height: 2.5px; }

  /* ---------------------------------------------------------------------
     4. TABLES — 39 of them, only 12 had scroll wrappers.
     #channelTable measured 894px inside a 341px card with overflow:visible,
     so columns past "INELIGIBLE %" were invisible AND unreachable.
     mobile.js wraps every unwrapped <table> in .m-scroll.
     --------------------------------------------------------------------- */
  .m-scroll {
    position: relative;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
    /* right-edge fade = the affordance that tells you more columns exist */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent 100%);
            mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent 100%);
  }
  .m-scroll.at-end {
    -webkit-mask-image: none;
            mask-image: none;
  }
  .m-scroll > table {
    min-width: max-content;   /* let it be as wide as it needs INSIDE the scroller */
    width: auto;
    font-size: 12.5px;
  }
  .m-scroll th, .m-scroll td { padding: 9px 10px; white-space: nowrap; }
  .m-scroll th { font-size: 9.5px; letter-spacing: .5px; }

  /* first column pinned so you never lose the row label while scrolling right */
  .m-scroll th:first-child,
  .m-scroll td:first-child {
    position: sticky;
    left: 0;
    z-index: 2;
    background: #fff;
    box-shadow: 1px 0 0 var(--border);
  }
  .m-scroll th:first-child { background: var(--off-white); z-index: 3; }

  .m-swipe-hint {
    display: block;
    font-size: 10px;
    color: var(--text-muted);
    letter-spacing: .4px;
    text-transform: uppercase;
    margin: 0 0 6px;
    font-weight: 600;
  }
  .m-scroll.at-end + .m-swipe-hint,
  .m-swipe-hint.is-done { display: none; }

  /* ---------------------------------------------------------------------
     5. HEADER — .header-actions-wrap measured clientWidth 34 / scrollWidth 348.
     Five controls were stacked on top of each other. Upload + settings +
     avatar move into the drawer footer; the bell moves to the bottom bar.
     Header keeps only: hamburger | wordmark | search.
     --------------------------------------------------------------------- */
  /* The desktop header is a fixed `height: 92px` COLUMN with
     `justify-content: space-between` (it was built for two stacked rows). On a
     phone that combination is what broke the top bar: adding safe-area padding
     for the iOS status bar left a ~17px content box, so the 44px search button
     could not fit and overflowed upward into the status bar. It has to become an
     auto-height single ROW before any padding math works. */
  header {
    height: auto !important;
    min-height: 56px;
    flex-direction: row !important;
    align-items: center !important;
    justify-content: flex-start !important;
    padding: 6px 12px !important;
    overflow: visible;
  }
  .header-row {
    gap: 8px;
    align-items: center;
    flex-wrap: nowrap;
    /* the row shipped its own `padding: 10px 32px 0` — 32px side gutters on a
       390px screen, and a top-only pad that pushed it off centre */
    padding: 0 !important;
    min-height: 44px;
  }
  .header-left  { flex: 0 0 auto; align-items: center; }
  .header-center { flex: 1 1 auto; min-width: 0; justify-content: flex-end; align-items: center; }
  .header-right { flex: 0 0 auto; gap: 4px; align-items: center; }

  /* Two lonely icons with a void between them read as an unfinished page. The
     wordmark (injected by mobile.js) makes it an app bar. */
  .m-header-title {
    display: inline-block;
    font-family: 'Outfit', sans-serif;
    font-size: 16.5px;
    font-weight: 700;
    letter-spacing: -.2px;
    color: #fff;
    white-space: nowrap;
    margin-left: 2px;
  }

  .nav-toggle {
    width: 44px; height: 44px;
    display: inline-flex !important;
    align-items: center; justify-content: center;
  }

  /* search collapses to an icon-only tap target — the placeholder sentence
     ("Busca lo que sea — programa, campaign, lead source…") was 300px of the
     591px header overflow on its own */
  .header-search {
    width: 40px;
    height: 40px;
    min-width: 40px;
    padding: 0 !important;
    justify-content: center;
    align-items: center;
    gap: 0;
    margin-left: auto;
    flex: 0 0 auto;
    border-radius: 11px;
    background: rgba(255,255,255,.13);
    border: 1px solid rgba(255,255,255,.16);
  }
  .header-search:active { background: rgba(255,255,255,.22); }
  .header-search svg { width: 18px; height: 18px; flex: 0 0 auto; }
  .header-search span,
  .header-search kbd { display: none; }

  /* These relocate to the drawer (mobile.js MOVES the nodes, not clones).
     Scoped to `header` on purpose: once moved into the sidebar they stop
     matching, so the tray needs no specificity fight with `#openSettings`
     (an ID beats any class selector, even with !important on both). It also
     fails safe — if mobile.js never runs, the controls stay visible and
     cramped rather than becoming invisible and unreachable. */
  header .header-actions-wrap,
  header #openSettings,
  header .header-avatar { display: none !important; }

  .m-drawer-actions {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    padding: 14px 16px calc(18px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid rgba(255,255,255,.12);
    margin-top: auto;
  }
  .m-drawer-actions .header-btn,
  .m-drawer-actions .header-avatar {
    display: inline-flex !important;
    position: relative;
    width: 100%;
    min-height: 48px;
    border-radius: 11px;
    align-items: center;
    justify-content: center;
    background: rgba(255,255,255,.10);
    color: rgba(255,255,255,.92);
  }
  /* badges were absolutely positioned against the old header button box */
  .m-drawer-actions .notification-badge {
    position: absolute;
    top: 4px; right: 6px;
    left: auto; bottom: auto;
    transform: none;
  }

  /* ---------------------------------------------------------------------
     6. NOTIFICATION DROPDOWN — overflowed the viewport by 232px.
     Becomes a bottom sheet: thumb-reachable, clamped, and scrollable.
     --------------------------------------------------------------------- */
  .notification-dropdown {
    position: fixed !important;
    left: 8px; right: 8px;
    top: auto !important;
    bottom: calc(66px + env(safe-area-inset-bottom, 0px));
    width: auto !important;
    max-width: none !important;
    max-height: 62dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: 16px;
    box-shadow: 0 -8px 40px rgba(13,42,94,.28);
    z-index: 1200;
  }
  .notification-dropdown-header {
    position: sticky; top: 0;
    background: inherit;
    z-index: 1;
  }
  .alert-item { padding: 12px 13px 11px 14px; margin: 4px 8px; }
  .alert-item .ai-detail { font-size: 12.5px; line-height: 1.45; }
  /* Touch targets: the jump affordance is always visible on a phone (no hover). */
  .alert-item .ai-go { opacity: .8; }

  /* ---------------------------------------------------------------------
     7. BOTTOM TAB BAR (injected by mobile.js)
     Thumb-zone research: ~75% of users drive one-handed; top-anchored primary
     nav forces a grip change on every navigation. Each tab just clicks the
     existing [data-page] sidebar button — no duplicated routing.
     --------------------------------------------------------------------- */
  .m-tabbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 1100;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    background: rgba(255,255,255,.94);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    border-top: 1px solid rgba(13,42,94,.10);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    box-shadow: 0 -2px 18px rgba(13,42,94,.07);
  }
  .m-tab {
    appearance: none;
    -webkit-appearance: none;
    background: none;
    border: 0;
    padding: 8px 2px 7px;
    min-height: 56px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    color: var(--text-muted);
    font-family: 'Outfit', sans-serif;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: .25px;
    text-transform: uppercase;
    cursor: pointer;
    position: relative;
    -webkit-tap-highlight-color: transparent;
    transition: color .15s;
  }
  .m-tab svg { width: 21px; height: 21px; stroke-width: 2; }
  .m-tab.active { color: var(--navy); }
  .m-tab.active::before {
    content: '';
    position: absolute;
    top: 0; left: 50%;
    transform: translateX(-50%);
    width: 26px; height: 3px;
    border-radius: 0 0 3px 3px;
    background: linear-gradient(90deg, var(--gold-deep, #C9A43F), var(--gold-2, #E3C978));
  }
  .m-tab:active { color: var(--navy); }

  /* The bar's background is a hard-coded white wash, so in dark mode a phone
     showed a bright slab across the bottom of an otherwise dark app — most
     obvious on the Agent page, where it sat directly under the composer. The
     active-tab colour has the same problem: `--navy` on a dark bar is invisible.
     Tokens, not literals, from here on. */
  html[data-bb-theme="dark"] .m-tabbar {
    background: color-mix(in srgb, var(--bb-surface, #101B2E) 94%, transparent);
    border-top-color: var(--bb-line, rgba(255, 255, 255, .10));
    box-shadow: 0 -2px 18px rgba(0, 0, 0, .45);
  }
  html[data-bb-theme="dark"] .m-tab { color: var(--bb-ink-3, #8A93A6); }
  html[data-bb-theme="dark"] .m-tab.active,
  html[data-bb-theme="dark"] .m-tab:active { color: var(--bb-ink, #E7ECF5); }

  /* The loader is a full-screen navy takeover; a light tab bar floating over it
     reads as a half-rendered page. hidePageLoader() sets both an inline
     display:none and aria-hidden="true", so either one lets the bar back in. */
  body:has(#pageLoader:not([aria-hidden="true"]):not([style*="display: none"])) .m-tabbar,
  body:has(#pageLoader:not([aria-hidden="true"]):not([style*="display: none"])) .m-install-card {
    display: none;
  }

  .m-tab-badge {
    position: absolute;
    top: 5px; left: 58%;
    min-width: 16px; height: 16px;
    padding: 0 4px;
    border-radius: 8px;
    background: var(--red, #C8364A);
    color: #fff;
    font-size: 9px;
    font-weight: 800;
    line-height: 16px;
    text-align: center;
    font-family: 'JetBrains Mono', monospace;
  }

  /* ---------------------------------------------------------------------
     8. MODALS -> BOTTOM SHEETS
     10 modal backdrops existed; only .program-detail-modal had any mobile
     rule. Actions land under the thumb instead of at the top of the screen.
     --------------------------------------------------------------------- */
  .modal-backdrop { align-items: flex-end !important; padding: 0 !important; }
  .modal,
  .modal.settings-modal,
  .modal.wl-builder-modal,
  .modal.initiative-builder-modal,
  .modal.program-detail-modal,
  .ci-modal {
    width: 100% !important;
    max-width: 100% !important;
    max-height: 93dvh !important;
    margin: 0 !important;
    border-radius: 18px 18px 0 0 !important;
    display: flex;
    flex-direction: column;
    animation: mSheetUp .26s cubic-bezier(.32,.72,0,1);
  }
  @keyframes mSheetUp { from { transform: translateY(100%); } to { transform: translateY(0); } }

  .modal::before {
    /* grab handle — the universal "drag me / I'm a sheet" signal */
    content: '';
    position: absolute;
    top: 7px; left: 50%;
    transform: translateX(-50%);
    width: 36px; height: 4px;
    border-radius: 4px;
    background: rgba(13,42,94,.18);
    z-index: 5;
  }
  .modal-header {
    padding-top: 20px !important;
    position: sticky; top: 0;
    background: #fff;
    z-index: 4;
  }
  .modal-header h2, .modal-header h3 { font-size: 16px !important; }
  .modal-body {
    flex: 1 1 auto;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding: 14px 16px !important;
  }
  .modal-footer {
    position: sticky; bottom: 0;
    background: #fff;
    border-top: 1px solid var(--border);
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px)) !important;
    display: flex;
    gap: 8px;
  }
  .modal-footer > button { flex: 1 1 auto; min-height: 46px; }
  .modal-close {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }

  /* modal-internal grids were the second overflow source once the shell fit */
  .modal .grid-2, .modal .grid-3, .modal .grid-4, .modal .grid-6,
  .modal [style*="grid-template-columns"] {
    grid-template-columns: minmax(0, 1fr) !important;
  }

  /* ---------------------------------------------------------------------
     9. CAMPAIGN ROWS -> STACKED CARDS
     Measured: .cmp-camp-row-actions sat 489px past the right edge, so
     "Targeting" and "+ Initiative" were literally unreachable, and spend was
     printing on top of the campaign name.
     --------------------------------------------------------------------- */
  .cmp-camp-row {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding: 13px 14px;
  }
  .cmp-camp-row:hover { box-shadow: none; }
  /* Desktop pins this column to a drag-resizable --cmp-name-w; on a stacked card the name owns
     the full width, so the fixed basis has to go or the card gets a 320px-wide dead column. */
  /* max-width: calc(100% - 460px) goes NEGATIVE on a phone, which clamps to 0 and collapses the
     name entirely. Must be cleared, not just overridden by width. */
  .cmp-camp-row-info { width: 100%; flex: 1 1 auto; max-width: none; }
  .cmp-camp-row-name {
    white-space: normal;          /* was nowrap + ellipsis + overflow hidden */
    overflow: visible;
    text-overflow: clip;
    max-height: none;             /* desktop clamps to 2 lines; a phone card can afford all of it */
    font-size: 12.5px;
    line-height: 1.35;
    overflow-wrap: anywhere;
  }
  /* No mouse = no hover to reveal, and nothing to drag. */
  .camp-rename-btn { opacity: 1 !important; }
  .cmp-name-resizer { display: none; }
  .cmp-camp-row-meta { flex-wrap: wrap; gap: 6px; font-size: 10.5px; }

  .cmp-camp-kpis {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px 10px;
    width: 100%;
    padding-top: 9px;
    border-top: 1px dashed var(--border);
  }
  .cmp-kpi-chip {
    align-items: flex-start;
    flex-direction: column-reverse;   /* label above value reads better stacked */
    gap: 2px;
  }
  .cmp-kpi-chip .kc-val { font-size: 14px; }
  .cmp-kpi-chip .kc-label { font-size: 9px; }

  .cmp-camp-row-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    width: 100%;
    flex-shrink: 1;
  }
  .cmp-camp-row-actions > * { width: 100%; min-height: 42px; }
  .cmp-pin-wrap { width: 100%; }
  .cmp-camp-row-arrow { display: none; }   /* the whole card is the tap target */

  .cmp-creative-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }

  /* The campaign summary pills are a wrapping flex row, so each tile sized to
     its own content: 13 pills came out 79-166px wide and the rows read as a
     ragged staircase. A 2-up grid gives one alignment edge to scan down, which
     is the whole point of putting numbers in tiles. */
  .cmp-stats-bar, .ia-stats-bar {
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }
  .cmp-stats-bar > .ia-stat-pill,
  .ia-stats-bar > .ia-stat-pill {
    width: 100%;
    min-width: 0;
  }

  /* ---------------------------------------------------------------------
     10. TOUCH TARGETS — every interactive element measured 13–34px.
     44px is the Apple HIG minimum; 48dp is Material's. Nothing below 44.
     --------------------------------------------------------------------- */
  button,
  a.btn, .btn,
  .chip, .pill, .tab,
  select,
  .header-btn,
  .kpi-customize-btn,
  .apply-period-btn,
  .byprogram-metric-tab,
  .settings-tab,
  .hm-rc,
  .cmp-pin-btn {
    min-height: 44px;
  }

  .chip, .pill {
    padding: 11px 15px !important;
    font-size: 12.5px;
    border-radius: 9px;
  }
  select {
    font-size: 15px;            /* <16px makes iOS Safari zoom the whole page on focus */
    padding: 10px 12px;
    min-height: 44px;
    border-radius: 9px;
    max-width: 100%;
  }
  input[type="text"], input[type="email"], input[type="password"],
  input[type="number"], input[type="date"], input[type="search"], textarea {
    font-size: 16px;            /* same reason — 16px is the iOS no-zoom threshold */
    min-height: 44px;
    max-width: 100%;
    border-radius: 9px;
  }

  /* Checkboxes were 13x13. The box itself grows to 22px (WCAG 2.2 AA wants
     >=24px; 22px + the label wrapper below clears the real 44px requirement).
     NOTE: a ::after hit-area expander does NOT work here — inputs are replaced
     elements and most engines never render their pseudo-elements. The reliable
     target is the wrapping <label>, which is what actually receives the tap. */
  input[type="checkbox"], input[type="radio"] {
    width: 22px; height: 22px;
    accent-color: var(--navy);
    flex: 0 0 auto;
  }

  /* Any label that owns a control becomes the 44px tap target. */
  label.toggle,
  label:has(input[type="checkbox"]),
  label:has(input[type="radio"]),
  .kpi-picker-item {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
  }

  /* small text links that were 16px tall */
  .recon-toggle, .hm-rc {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 4px;
  }
  .hm-rc { min-width: 44px; justify-content: center; }

  /* ---------------------------------------------------------------------
     11. DATA-INK — kill chrome that costs legibility at 375px.
     There is no hover on a phone; hover-lift transforms only cause jank and
     stuck :hover states after a tap.
     --------------------------------------------------------------------- */
  .card:hover, .stat:hover, .cmp-camp-row:hover, tr:hover td {
    transform: none !important;
    box-shadow: var(--sh-1) !important;
  }
  .card {
    padding: 15px 14px;
    border-radius: 13px;
  }
  .card h2, .card h3 { font-size: 15px; letter-spacing: .2px; }

  h1 { font-size: 21px; }
  h2 { font-size: 17px; }
  h3 { font-size: 15px; }

  /* preattentive: direction gets color AND weight AND the existing arrow/sign,
     so a glance survives colorblindness and sunlight */
  .positive, .negative { font-weight: 700; }

  /* charts need a floor height or they collapse to unreadable slivers */
  canvas, svg.chart, .chart-wrap { max-width: 100%; }
  .chart-wrap, .trend-chart { min-height: 180px; }

  /* filter/date panels: single column instead of clipped rows */
  .kpi-picker { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 12px; }
  .recon-grid, .calc-grid, .ps-body, .init-report-grid,
  .cg-grid, .cpl-off-grid, .is-plat-grid { grid-template-columns: minmax(0, 1fr) !important; }
  .cpl-stats, .ibd-stats-bar, .is-kpis, .tracker-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }

  /* horizontal chip/tab strips scroll instead of wrapping into a tall mess */
  .m-hscroll {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    gap: 7px;
    padding-bottom: 3px;
    scrollbar-width: none;
  }
  .m-hscroll::-webkit-scrollbar { display: none; }
  .m-hscroll > * { scroll-snap-align: start; flex: 0 0 auto; }

  /* The settings tab rail already becomes a horizontal flex row + overflow-x
     auto at 860px, but the tabs kept flex-shrink:1 and squeezed themselves down
     to "Da / Bu / Imp / Pla…" instead of scrolling. */
  .settings-sidebar {
    flex-wrap: nowrap !important;
    scroll-snap-type: x proximity;
    gap: 6px !important;
    padding: 10px 12px !important;
    scrollbar-width: none;
  }
  .settings-sidebar::-webkit-scrollbar { display: none; }
  .settings-sidebar > .settings-tab {
    flex: 0 0 auto;
    scroll-snap-align: start;
    padding: 11px 14px;
  }

  /* the busy bar measured 150px wide overflowing by 183px */
  .bridge-busy { max-width: 100vw; overflow: hidden; }
  .bridge-busy-bar { max-width: 100%; }

  /* ---------------------------------------------------------------------
     12. PWA install coach card + push prompt
     --------------------------------------------------------------------- */
  .m-install-card {
    display: block;
    position: fixed;
    left: 10px; right: 10px;
    bottom: calc(68px + env(safe-area-inset-bottom, 0px));
    z-index: 1300;
    /* subtle navy gradient + gold hairline reads as "our app", not a cookie banner */
    background:
      radial-gradient(120% 140% at 100% 0%, rgba(201,164,63,.20), transparent 58%),
      linear-gradient(165deg, #16386F 0%, #0B2350 100%);
    background-color: #0D2A5E;
    color: #fff;
    border: 1px solid rgba(201,164,63,.32);
    border-radius: 18px;
    padding: 15px 16px 14px;
    box-shadow: 0 14px 46px rgba(6,20,45,.46);
    animation: mCardIn .34s cubic-bezier(.34,1.36,.64,1);
  }
  @keyframes mCardIn {
    from { transform: translateY(24px) scale(.965); opacity: 0; }
    to   { transform: translateY(0) scale(1); opacity: 1; }
  }
  .m-install-card.m-install-out {
    animation: mCardOut .2s ease forwards;
  }
  @keyframes mCardOut {
    to { transform: translateY(16px) scale(.97); opacity: 0; }
  }

  /* iPad's Share button lives in the TOP toolbar, so the card moves up there and
     the arrow flips — pointing at the wrong edge is worse than no arrow. */
  .m-install-card.m-install-top {
    top: calc(64px + env(safe-area-inset-top, 0px));
    bottom: auto;
  }

  .m-install-head {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 12px;
  }
  .m-install-icon {
    width: 52px; height: 52px;
    border-radius: 13px;
    flex: 0 0 auto;
    background: #FAF9F6;
    box-shadow: 0 3px 12px rgba(0,0,0,.28);
  }
  .m-install-card h4 {
    margin: 1px 0 3px;
    font-size: 15px;
    font-weight: 700;
    color: #fff;
    letter-spacing: -.1px;
  }
  .m-install-card p,
  .m-install-card .m-install-sub {
    margin: 0;
    font-size: 12px;
    line-height: 1.42;
    color: rgba(255,255,255,.78);
  }
  .m-install-card > p { margin: 0 0 12px; }

  /* numbered steps — the whole point of the card is the gesture, so it gets the
     visual weight rather than the prose */
  .m-install-steps {
    list-style: none;
    margin: 0 0 13px;
    padding: 11px 12px;
    background: rgba(255,255,255,.07);
    border: 1px solid rgba(255,255,255,.10);
    border-radius: 12px;
    display: flex;
    flex-direction: column;
    gap: 9px;
  }
  .m-install-steps li {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 12.5px;
    line-height: 1.3;
    color: rgba(255,255,255,.92);
  }
  .m-install-steps b { color: #fff; font-weight: 700; }
  .m-step-n {
    flex: 0 0 auto;
    width: 19px; height: 19px;
    border-radius: 50%;
    background: var(--gold-deep, #C9A43F);
    color: #10233f;
    font-family: 'JetBrains Mono', monospace;
    font-size: 10.5px;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-right: 2px;
  }
  .m-inline-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px; height: 26px;
    border-radius: 7px;
    background: rgba(255,255,255,.13);
    color: #EBD9A0;
    flex: 0 0 auto;
  }
  .m-inline-icon svg { width: 15px; height: 15px; }

  .m-install-card .m-install-row { display: flex; gap: 8px; }
  .m-install-card button {
    flex: 1 1 auto;
    min-height: 44px;
    border-radius: 11px;
    border: 0;
    font-family: 'Outfit', sans-serif;
    font-size: 12.5px;
    font-weight: 700;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .m-install-primary { background: var(--gold-deep, #C9A43F); color: #10233f; }
  .m-install-primary:disabled { opacity: .75; }
  .m-install-dismiss { background: rgba(255,255,255,.15); color: #fff; }
  .m-install-never {
    background: none !important;
    color: rgba(255,255,255,.5) !important;
    font-weight: 600 !important;
    font-size: 11.5px !important;
    flex: 0 0 auto !important;
    padding: 0 10px;
  }

  /* small × so dismissing never requires reading the button labels */
  .m-install-x {
    position: absolute;
    top: 8px; right: 8px;
    width: 30px !important; height: 30px;
    min-height: 30px !important;
    flex: 0 0 auto !important;
    border-radius: 50% !important;
    background: rgba(255,255,255,.12) !important;
    color: rgba(255,255,255,.72) !important;
    font-size: 17px !important;
    line-height: 1;
    display: grid;
    place-items: center;
    padding: 0;
  }

  /* the arrow points at Safari's real Share button and bobs so the eye finds it */
  .m-install-arrow {
    position: absolute;
    left: 50%;
    bottom: -31px;
    transform: translateX(-50%);
    color: var(--gold-2, #E3C978);
    filter: drop-shadow(0 3px 8px rgba(6,20,45,.5));
    animation: mBob 1.5s ease-in-out infinite;
  }
  .m-install-arrow svg { width: 26px; height: 26px; }
  .m-install-card.m-install-top .m-install-arrow {
    bottom: auto;
    top: -30px;
    right: 22px; left: auto;
    transform: rotate(180deg);
  }
  @keyframes mBob {
    0%, 100% { transform: translateX(-50%) translateY(0); }
    50%      { transform: translateX(-50%) translateY(5px); }
  }

  /* On iPhone, Safari's own toolbar sits directly below the page viewport — which
     is exactly where our tab bar is. An arrow pointing "down" at Safari's Share
     button would be aiming straight through our own nav. So while the iOS coach
     card is up, the tab bar fades out and the arrow has a clear line to the real
     button. It comes back the moment the card closes. */
  /* 46px of clearance so the arrow (-31px, plus a 5px bob) stays fully on screen */
  .m-install-card.m-install-bottom {
    bottom: calc(46px + env(safe-area-inset-bottom, 0px));
  }
  .m-tabbar { transition: opacity .25s ease; }
  body:has(.m-install-card.m-install-bottom:not(.m-install-out)) .m-tabbar {
    opacity: 0;
    pointer-events: none;
  }

  .m-install-wrongbrowser { border-color: rgba(201,164,63,.5); }

  /* The sidebar drawer was the ONE thing already working — only widen the rows
     to a comfortable 52px, make it a scrollable column, and reserve the bottom
     tab bar's height so the relocated action tray isn't stranded behind it. */
  .app-sidebar {
    width: min(80vw, 300px);
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-bottom: calc(58px + env(safe-area-inset-bottom, 0px));
  }
  .app-sidebar .sidebar-nav-tab { min-height: 52px; }

  /* the tray already carries its own safe-area pad; the sidebar reserves the
     tab-bar height above, so zero it out here to avoid double padding */
  .m-drawer-actions { padding-bottom: 14px; }

  /* ------------------------------------------------------------------------
     Overview: division scope pills + Top/Bottom 3 leaderboard
     The pills are the primary control on this card, so they get a horizontal
     scroll strip instead of wrapping into a four-line block that pushes the
     hero KPI off screen.
     ------------------------------------------------------------------------ */
  .ov-scope {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin: 0 -4px 14px;
    padding: 2px 4px 6px;
    scroll-snap-type: x proximity;
  }
  .ov-scope::-webkit-scrollbar { display: none; }
  .ov-scope-lbl { display: none; }
  .ov-pill { flex: 0 0 auto; padding: 8px 12px; scroll-snap-align: start; }
  .ov-pill:hover { transform: none; }

  .ov-rank { margin-top: 18px; padding-top: 14px; }
  .ov-rank-head { gap: 8px; }
  .ov-rank-title { width: 100%; margin-right: 0; }
  .ov-seg button { padding: 8px 12px; }
  .ov-rank-grid { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .ov-rank-col { padding: 12px 12px 13px; }
  /* A short list already fit in the top band, so the "Peores 3" heading plus its
     filler line is pure vertical cost on a phone. Drop the pair and keep the
     real rows. The "Sin datos suficientes" case under the TOP band survives —
     that one is the only explanation the column has. */
  .ov-rank-band.bot:has(+ .ov-rank-empty) { display: none; }
  .ov-rank-band.bot + .ov-rank-empty { display: none; }
  .ov-rank-row { padding: 8px 10px; }
  .ov-rank-row[data-drill]:hover { transform: none; }
  .ov-rank-val > b { font-size: 16px; }

  /* ---------------------------------------------------------------------
     BRIDGEBI AGENT — the tab bar owns the bottom 57px
     The panel is fixed at `bottom: 14px` and the FAB at `bottom: 20px`, both
     at a z-index far above `.m-tabbar` (1100). So on a phone the panel's
     composer sat exactly on top of the nav — the tab labels ghosted through
     the frosted glass under the input — and the FAB parked itself over the
     middle tab. Neither was reachable-by-accident; both were tappable by
     accident, which is worse.

     Everything with a fixed bottom edge now ends ABOVE the bar. The bar's own
     height (57px) plus the home-indicator inset is the only offset needed.

     `!important` is load-bearing: index.html loads agent.css AFTER mobile.css,
     so `#bbAgentPanel { bottom: 14px }` there beats an equal-specificity rule
     here. Without it these two declarations are inert — measured, they were.
     --------------------------------------------------------------------- */
  #bbAgentPanel {
    top: 10px !important;
    bottom: calc(57px + 10px + env(safe-area-inset-bottom, 0px)) !important;
  }
  #bbAgentFab {
    bottom: calc(57px + 14px + env(safe-area-inset-bottom, 0px)) !important;
  }
  /* The in-page column is measured and sized by mobile.js (fitAgentColumn) —
     CSS cannot express "the space below wherever I start". This only keeps the
     log from fighting the column for the last pixel. */
  #bbAgentChat.in-page .bb-agent-form { padding-bottom: 12px; }
}

/* ==========================================================================
   Extra-tight phones (iPhone SE 1st gen / 320–390px)
   ========================================================================== */
@media (max-width: 400px) {
  .grid-3, .grid-4, .grid-6 { grid-template-columns: minmax(0, 1fr); }
  .stat-value { font-size: clamp(24px, 8vw, 32px); }
  .cmp-camp-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .m-tab { font-size: 9px; }
  .m-tab svg { width: 20px; height: 20px; }
}

/* ==========================================================================
   Landscape phones — short viewports need the vertical budget back
   ========================================================================== */
@media (max-width: 900px) and (orientation: landscape) and (max-height: 500px) {
  .m-tabbar { grid-template-columns: repeat(5, 1fr); }
  .m-tab { min-height: 46px; padding: 5px 2px; font-size: 8.5px; }
  .m-tab svg { width: 17px; height: 17px; }
  .modal { max-height: 96dvh !important; }
}

/* ==========================================================================
   Installed / standalone mode (Home Screen app)
   The status bar area belongs to us once installed, so the header has to pay
   for it. Also hides the install coach card — already installed.
   ========================================================================== */
@media (display-mode: standalone) {
  /* apple-mobile-web-app-status-bar-style is black-translucent, so the web view
     extends UNDER the status bar and the status bar area is ours to pay for.
     min-height has to grow by the same inset as the padding — padding alone
     against a fixed height is what crushed the row into the status bar. */
  header {
    padding-top: calc(6px + env(safe-area-inset-top, 0px)) !important;
    min-height: calc(56px + env(safe-area-inset-top, 0px));
  }
  .m-install-card.m-install-ios { display: none; }
  body { overscroll-behavior-y: contain; }   /* no rubber-band "reload" pull */
}

/* iOS 11-14 shipped constant() before the standard env(); harmless elsewhere. */
@supports (padding-top: constant(safe-area-inset-top)) {
  @media (display-mode: standalone) {
    header { padding-top: calc(6px + constant(safe-area-inset-top)) !important; }
  }
}

/* Respect reduced-motion for every animation this layer introduces */
@media (prefers-reduced-motion: reduce) {
  .modal, .m-install-card { animation: none !important; }
}
