/* mobile.css — gedeelde telefoon-laag voor alle Brands Den dashboards.

   ALLES zit achter @media (max-width:640px). Desktop verandert dus niet, punt.
   Geladen via dashboard-frame.js (die hem als <link> in de <head> hangt, ná het
   inline <style>-blok van de pagina, zodat gelijke specificiteit hier wint) en
   via een handmatige <link> in de 7 pagina's die de frame niet laden:
   index, ai-team, data-catalog, map, po-pac, sop, system.

   SCOPING. Veel klassenamen bestaan in meerdere dashboards met een andere
   betekenis (.slide in ads én ai-team, .side in po én promo, .agent-card in
   index én ai-team, .drow in cs én reviews). Regels die niet overal kloppen
   zijn daarom vastgepind op een selector die maar in één dashboard bestaat:

     aside.side                   po + po-pac   (promo's nav.side blijft ongemoeid)
     #sidebar                     cs/product/marketing/compliance/outreach (off-canvas)
     aside.sidebar:not(#sidebar)  inventory     (vaste kolom naast de content)
     body:has(.stage)             ads
     body:has(.slide-inner)       ai-team
     body:has(.sbtoggle)          compliance
     body:has(.tree/.under/.promptbox/.how)   system / map / sop / data-catalog

   Kleuren en fonts komen uit shared/BRANDING.md en worden hier niet aangeraakt —
   dit bestand doet alleen layout. Elk blok noemt het dashboard waar het
   probleem zat, zodat een volgende lezer weet waarom de regel bestaat. */

@media (max-width: 640px) {

  /* ── 0. vangnet: de pagina scrollt nooit horizontaal ─────────────────────
     playbook had als enige geen overflow-guard; de rest wel. Brede content
     krijgt verderop z'n eigen scroll-container in plaats van afgeknepen te worden. */
  html, body { overflow-x: hidden; max-width: 100%; }

  /* iOS zoomt bij focus in op inputs onder 16px en verspringt dan de layout.
     Raakt inventory (.toolbar input), promo (add-promo-modal) en ads. */
  input, select, textarea { font-size: 16px; }


  /* ── 1. po + po-pac: vaste 222px-sidebar → scrollbare strip bovenaan ─────
     BLOKKEREND. .side stond fixed over 57% van een 390px-scherm en is de enige
     navigatie (de secties zijn display:none tot je een navknop indrukt), dus
     verbergen mocht niet — hij wordt een horizontale chiprij. */
  aside.side {
    position: static; inset: auto; width: auto; height: auto;
    flex-direction: row; align-items: center; gap: 8px;
    overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
    padding: 8px 12px;
    border-right: 0; border-bottom: 1px solid var(--line);
  }
  aside.side > * { flex: 0 0 auto; }
  aside.side .brand { padding: 0; }
  aside.side .nav { margin-top: 0; flex-direction: row; gap: 6px; }
  aside.side .nav .grp,
  aside.side .nav .divide,
  aside.side .ft { display: none; }
  aside.side .nav .sub { padding-left: 0; }
  aside.side .nav button { width: auto; white-space: nowrap; min-height: 44px; padding: 8px 12px; }

  /* BLOKKEREND. .wrap had margin-left:222px + 2x34px padding = ~100px leesbreedte
     op 390px. Dit is de wijziging die de meeste vervolgproblemen oplost. */
  aside.side ~ .wrap { margin-left: 0; padding: 16px 14px 80px; }

  /* meerkoloms rijen die nergens terugvielen (po/po-pac). .prow bestaat óók in
     inventory als 5-koloms prioriteitenrij en heeft daar dezelfde fix nodig. */
  .prow, .txrow, .tlrow,
  details.porow > summary { grid-template-columns: minmax(0,1fr); gap: 6px; }
  details.porow > summary::before { position: static; margin-left: 0; }

  /* po/po-pac: blokken met een min-width groter dan hun eigen kaart */
  .invchip { min-width: 0; flex: 1 1 100%; }
  .tn-t { min-width: 0; }

  /* po/po-pac: 13-weekse cashflow-matrix — 8px koppen en een sticky labelkolom
     van 150px lieten geen enkele weekkolom over. */
  .cfm th { font-size: 10px; }
  .cfm th.cfl, .cfm td.cfl { min-width: 110px; max-width: 130px; }

  /* po/po-pac: modal op 100vh telt de iOS-adresbalk mee, waardoor de sticky
     footer met de opslaan-knop eronder viel. */
  .mo-wrap { padding: 0; }
  .mo { width: 100%; max-width: 100%; border-radius: 0; max-height: 100vh; max-height: 100dvh; }
  .mo-foot { padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
  .npform .nprow { grid-template-columns: 1fr; }

  /* po/po-pac: tikdoelen van ~24px */
  .pr-open, .mfilter button { min-height: 40px; display: inline-flex; align-items: center; }

  /* po-pac: de read-only badge lag fixed over de onderste tabelrijen heen. */
  .pac-badge { position: static; display: block; margin: 14px; text-align: center; }


  /* ── 2. inventory: de zijbalk stond naast de content ─────────────────────
     BLOKKEREND. body was display:flex met een 248px kolom ernaast; er bleef
     66px leesbreedte over, waardoor elke andere fix zinloos zou zijn. */
  body:has(> aside.sidebar:not(#sidebar)) { display: block; }
  aside.sidebar:not(#sidebar) {
    position: static; width: auto; height: auto;
    flex-direction: row; align-items: center; gap: 6px;
    overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
    padding: 10px 12px;
    border-right: 0; border-bottom: 1px solid var(--stroke2);
  }
  aside.sidebar:not(#sidebar) > * { flex: 0 0 auto; }
  aside.sidebar:not(#sidebar) .brand { margin-bottom: 0; }
  aside.sidebar:not(#sidebar) .navlabel,
  aside.sidebar:not(#sidebar) .hubtoggle,
  aside.sidebar:not(#sidebar) .hubpanel,
  aside.sidebar:not(#sidebar) .side-foot { display: none; }
  aside.sidebar:not(#sidebar) .navitem { white-space: nowrap; min-height: 44px; margin-bottom: 0; }
  /* main is elders (outreach) een kale wrapper zonder eigen padding — scopen. */
  aside.sidebar:not(#sidebar) ~ main { padding: 16px 14px 48px; max-width: 100%; }
  /* inventory: SVG's schaalden naar ~4px aslabels; op ware grootte horizontaal
     scrollen binnen .chartbox is leesbaar. */
  .chartbox svg { min-width: 520px; max-width: none; }
  .toolbar input { width: 100%; min-width: 0; }


  /* ── 3. off-canvas sidebars: cs, product, marketing, compliance, outreach ─
     BLOKKEREND. De hamburger stond fixed op top:56px/left:14px, precies over de
     brandrow, de back-link en de h1 (dashboard-frame.js duwt de body al 46px
     omlaag). Naar linksonder: rechtsonder zit in product de chatbubbel. */
  .sb-toggle, .sbtoggle {
    top: auto; right: auto; left: 14px;
    bottom: calc(14px + env(safe-area-inset-bottom));
    width: 46px; height: 46px;
  }
  /* navlinks waren ~37px hoog */
  .sb-nav a { min-height: 44px; display: flex; align-items: center; padding: 11px 20px; }


  /* ── 4. tabellen: brede content scrollt in zijn eigen kader ──────────────
     Zes namen voor dezelfde scroll-container, plus twee tabellen die er géén
     hadden (playbook en outreach's kleine tabellen). */
  .tablewrap, .tscroll, .tbl-wrap, .dtwrap, .pk-wrap, .prodscroll, .ptable-wrap,
  .panel > div[style*="overflow-x"] {
    overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
  }
  table { font-size: 12px; }
  table th, table td { padding-left: 8px; padding-right: 8px; }

  /* outreach: dataTable() geeft bij minder dan 3 rijen de kale <table> terug
     zonder .dtwrap, waarna overflow-x:hidden op body de rechterkolommen
     definitief wegknipte — niet scrollbaar, niet bereikbaar. */
  table.dt { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%; }
  .dtwrap > table.dt { display: table; }

  /* playbook: de lessen-tabel is de enige zonder container én zonder min-width.
     Twee kolommen lopende tekst in 346px = ~18 tekens per regel, dus stapelen. */
  .panel > table:not([class]),
  .panel > table:not([class]) tbody,
  .panel > table:not([class]) tr,
  .panel > table:not([class]) th,
  .panel > table:not([class]) td { display: block; width: auto; }
  .panel > table:not([class]) tr { border-bottom: 1px solid var(--line); padding: 8px 0; }
  .panel > table:not([class]) th,
  .panel > table:not([class]) td { border-bottom: 0; padding: 3px 0; }

  /* outreach: de CSV- en heatmapknoppen stonden op opacity:0 tot :hover — op
     touch bestaat hover niet, dus was de export onbereikbaar. */
  .dt-export { position: static; opacity: 1; margin: 8px 8px 0 0; min-height: 34px; }
  .dt-export.dt-heat { right: auto; }


  /* ── 5. tegelrasters ─────────────────────────────────────────────────────
     .tiles/.kpis/.kpirow/.kpi-row zijn overal hetzelfde ding onder een andere
     naam. funnel viel pas op 360px terug naar één kolom, dus 390px-toestellen
     hielden twee kolommen met labels die over drie regels braken; po/po-pac
     duwden met minmax(150px) de body breder dan de viewport. */
  .tiles, .kpis, .kpirow, .kpi-row, .reqgrid, .repgrid, .mailtiles {
    grid-template-columns: repeat(2, minmax(0,1fr)); gap: 10px;
  }
  .toolgrid, .groups, .ideaflow, .sheets { grid-template-columns: minmax(0,1fr); }
  /* bronlabels op 9,5px met .13em letter-spacing braken over drie regels */
  .tile .src, .tile .l, .kpi .src { letter-spacing: .06em; font-size: 10px; }


  /* ── 6. paneel- en paginapadding ─────────────────────────────────────────
     18-20px paneelpadding bovenop 20-34px paginapadding kostte tot een kwart
     van de 390px. Raakt cs, product, marketing, marketplace, funnel, rankings,
     reviews, outreach, playbook en (via .card) compliance. */
  .panel { padding: 14px 12px; }
  body:has(.sbtoggle) .card { padding: 14px 12px; }
  .wrap { max-width: 100%; padding-left: 14px; padding-right: 14px; }
  /* map, system, sop en data-catalog zetten hun paginapadding op body i.p.v.
     op .wrap; zonder dit zou de regel hierboven er 14px bovenop stapelen. */
  body:has(.tree), body:has(.under), body:has(.promptbox), body:has(.how) {
    padding-left: 0; padding-right: 0;
  }


  /* ── 7. mono-blokken met niet-afbreekbare paden en URL's ─────────────────
     sop, data-catalog, system, map, ai-team en playbook bevatten elk een
     mono-blok met een URL of pad dat breder is dan zijn kaart. In data-catalog
     gaf dat horizontale scroll op de hele pagina, in sop viel het einde van de
     te kopiëren URL onzichtbaar weg achter overflow:hidden. */
  pre, code, .code, .bp-code, .tree, .eg, .how .prompt, .promptbox pre {
    overflow-wrap: anywhere; word-break: break-word;
  }

  /* sop: de Copy-knop lag bovenop de eerste twee regels van de prompt en was
     31px hoog. Hij staat in de DOM vóór de <pre>, dus static = erboven. */
  .promptbox { overflow: visible; }
  .copybtn { position: static; display: block; margin: 14px 14px 0; min-height: 44px; }
  /* sop: het pad zweefde met float:right over de naam heen */
  .assign span { float: none; display: block; margin-top: 2px; }

  /* map: een label van minimaal 130px naast de uitleg liet ~150px tekst over */
  .under .row { flex-direction: column; gap: 4px; }
  .under .k { min-width: 0; }

  /* map, system, data-catalog en playbook: 44-52px witruimte boven elke kop */
  .wrap > h2 { margin-top: 26px; }
  /* outreach, reviews en playbook hebben als enige geen clamp op h1 */
  h1 { font-size: clamp(21px, 6vw, 28px); }
  /* de back-link is een kale 13px-link; op map/sop/system/data-catalog blijft
     hij staan (daar draait dashboard-frame.js niet, die verbergt hem elders). */
  a.back { display: inline-flex; align-items: center; min-height: 44px; }


  /* ── 8. cs: de returns-verdeling klapte in elkaar ────────────────────────
     BLOKKEREND. Het grid was `auto 1fr 34px` met white-space:nowrap op het
     label; 'product-does-not-meet-expectations' (~225px) at de balkkolom op
     tot ~35px. #returnsPanel bestaat alleen in cs — reviews heeft ook .drow,
     maar met korte sterlabels en die mag deze stapel-layout niet krijgen. */
  #returnsPanel .drow {
    grid-template-columns: minmax(0,1fr) auto;
    grid-template-areas: "label value" "bar bar";
    row-gap: 3px;
  }
  #returnsPanel .drow .dl { grid-area: label; }
  #returnsPanel .drow .dbarwrap { grid-area: bar; }
  #returnsPanel .drow .dval { grid-area: value; }
  .drow .dl { white-space: normal; overflow-wrap: anywhere; }


  /* ── 9. marketing: drie iframes naar /ads/ met vaste hoogte ──────────────
     BLOKKEREND. /ads/ is een desktop-deck; in een frame van 358px breed en
     520-1200px hoog is het niet te bedienen. De link-fallback staat er al in
     de HTML, die zetten we aan. Zelfde patroon in product (launch one-pager
     in een 900px hoog iframe met witte achtergrond). */
  .hubframe, details.embedwrap iframe { display: none; }
  .hubfallback { display: block !important; }
  .embedhead { flex-wrap: wrap; }


  /* ── 10. promo: modal, filterknoppen en navigatie ────────────────────────
     In de add-promo-modal kwam het productzoekveld op 130px uit en stonden de
     twee datumvelden naast elkaar op 150px — de ondergrens waarop iOS de
     datumnotatie nog toont. */
  .modal-bg { padding: 24px 10px; }
  .modal { padding: 18px 14px; }
  .prod-row { grid-template-columns: 1fr auto; grid-template-areas: "pick pick" "price del"; }
  .prod-row > :nth-child(1) { grid-area: pick; }
  .prod-row > :nth-child(2) { grid-area: price; }
  .prod-row > :nth-child(3) { grid-area: del; }
  .f-grid { grid-template-columns: 1fr; }
  /* de enige filterbediening boven de zwaarste tabel van het cluster (27-31px) */
  .chanpill, .cbtn, .modetog button { min-height: 40px; font-size: 12px; }
  /* de wrap-navigatie besloeg drie regels; één scrollbare regel scheelt ~100px */
  nav.side { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  nav.side .navlink { flex: none; min-height: 44px; }


  /* ── 11. rankings: bronfilter met verborgen scrollbar ────────────────────
     Vijf chips van samen ~410px in 350px, met scrollbar-width:none — de vijfde
     ('Geo') viel onzichtbaar buiten beeld op een dashboard dat op dat filter draait. */
  .srcfilter { flex-wrap: wrap; overflow-x: visible; }
  .srcfilter .chipbtn { min-height: 40px; }


  /* ── 12. index.html: tikdoelen ───────────────────────────────────────────
     Die pagina is al bewust mobiel gemaakt; alleen de icoonknoppen (34x34) en
     de sluitknop van het Gandalf-paneel (26x26) bleven onder de 44px. */
  .iconbtn { width: 44px; height: 44px; }
  .gclose { width: 34px; height: 34px; }


  /* ── 13. ads ─────────────────────────────────────────────────────────────
     Gescoped op body:has(.stage): .slide, #deck, .panel en .dot bestaan ook in
     ai-team en index met een compleet andere anatomie. */

  /* BLOKKEREND. body overflow:hidden + #deck height:100vh, terwijl
     dashboard-frame.js de body 46px omlaag duwt — de onderste 46px van de deck
     (de hele navigatiebalk) viel permanent buiten beeld en de pagina kon niet
     scrollen. dvh vangt de dynamische iOS-adresbalk op. */
  body:has(.stage) #deck { height: calc(100vh - 46px); height: calc(100dvh - 46px); width: 100%; }
  body:has(.stage) .slide { padding: 10px 12px 6px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  body:has(.stage) .two-col { height: auto; }

  /* BLOKKEREND. De topbalk was één flexrij zonder wrap met ruim 900px inhoud
     in 390px; Refresh, periodekiezer, focus-mode en fullscreen werden niet
     scrollbaar maar simpelweg afgekapt. */
  body:has(.stage) .topbar { flex-wrap: wrap; row-gap: 8px; gap: 10px; padding: 8px 12px 0; }
  body:has(.stage) .tabs { width: 100%; margin-left: 0; flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  body:has(.stage) .navbar { padding: 8px 12px 12px; }

  /* BLOKKEREND. De Product-KPI-tabel — het hoofdscherm — bevroor 588px aan
     sticky linkerkolommen op een 390px-scherm: geen enkele KPI-kolom in beeld. */
  body:has(.stage) table.pk th[data-key="rank"],   body:has(.stage) table.pk td[data-k="rank"],
  body:has(.stage) table.pk th.pkprod,             body:has(.stage) table.pk td[data-k="product"],
  body:has(.stage) table.pk th[data-key="brand"],  body:has(.stage) table.pk td[data-k="brand"],
  body:has(.stage) table.pk th[data-key="strategy"], body:has(.stage) table.pk td[data-k="strategy"],
  body:has(.stage) table.pk th[data-key="price"],  body:has(.stage) table.pk td[data-k="price"] {
    position: static; width: auto; min-width: 0; max-width: none;
  }
  /* campagnetabellen: sticky eerste kolom van 300px in een venster van ~330px */
  body:has(.stage) thead th:first-child,
  body:has(.stage) tbody td:first-child { max-width: 150px; }

  /* grid met een minimumkolom van 420px — breder dan het scherm zelf */
  body:has(.stage) .ov-grid { grid-template-columns: minmax(0,1fr); }
  /* een waarde van 34px in een tegel met overflow:hidden kapte bedragen af */
  body:has(.stage) .kpi { padding: 14px; }
  body:has(.stage) .kpi .val { font-size: 24px; }
  /* 9px sleepgreep met touch-action:none blokkeerde het scrollen in die band */
  body:has(.stage) .col-resizer { display: none; }
  /* vier geanimeerde blobs onder blur(60px) geven jank bij het swipen */
  body:has(.stage) .aurora { display: none; }


  /* ── 14. ai-team ─────────────────────────────────────────────────────────
     Gescoped op body:has(.slide-inner) om dezelfde reden als ads hierboven. */

  /* BLOKKEREND. body en #deck stonden op 100vh met overflow:hidden en .slide op
     position:absolute — alles onder de vouw (o.a. de org chart van ~1850px) was
     met geen enkel gebaar te bereiken. De actieve slide komt in de flow en mag
     meegroeien; de inactieve blijven absolute en dus onzichtbaar. */
  body:has(.slide-inner) { width: auto; height: auto; min-height: 100dvh; overflow-y: auto; }
  body:has(.slide-inner) #deck { width: 100%; height: auto; min-height: 100dvh; }
  body:has(.slide-inner) .slide { padding: 24px 16px 116px; justify-content: flex-start; }
  body:has(.slide-inner) .slide.active {
    position: relative; inset: auto; height: auto; min-height: calc(100dvh - 116px);
  }

  /* BLOKKEREND. De best-practices- en FAQ-grids staan als INLINE style vast op
     3 resp. 2 kolommen. Inline wint van een externe stylesheet, dus dit is de
     enige plek in dit bestand waar !important echt nodig is — zonder dat blijven
     de commando-blokken op ~100px staan en breken ze op elk teken af. */
  body:has(.slide-inner) .slide div[style*="grid-template-columns:repeat("] {
    grid-template-columns: 1fr !important;
  }

  /* De navigatiechrome lag over de inhoud: prev/next op top:50% terwijl de
     slide maar 31px zijpadding heeft, teller en fullscreen over de kop. Alles
     naar onderen. #progress heeft op regel 277 een kapotte transform-declaratie
     (een HTML-fragment staat middenin de waarde, de parser gooit hem weg);
     left:0 + right:0 + centreren omzeilt dat zonder desktop aan te raken. */
  body:has(.slide-inner) #fullscreen-btn { display: none; }
  body:has(.slide-inner) .nav-btn {
    top: auto; bottom: calc(10px + env(safe-area-inset-bottom));
    transform: none; width: 44px; height: 44px;
  }
  body:has(.slide-inner) #prev-btn { left: 12px; }
  body:has(.slide-inner) #next-btn { right: 12px; }
  body:has(.slide-inner) #counter {
    top: auto; bottom: calc(22px + env(safe-area-inset-bottom)); left: 0; right: 0; text-align: center;
  }
  body:has(.slide-inner) #progress {
    left: 0; right: 0; bottom: calc(64px + env(safe-area-inset-bottom));
    justify-content: center; flex-wrap: wrap; padding: 0 14px;
  }
  body:has(.slide-inner) .dot { width: 11px; height: 11px; }

  /* 3rem zijpadding binnen een slide met 8vw padding liet ~230px tekst over
     voor bullets van 20px en een quote van 22px. */
  body:has(.slide-inner) .agent-card { padding: 1.2rem 1rem; }
  body:has(.slide-inner) .agent-bullets li { font-size: 16px; }
  body:has(.slide-inner) .agent-quote { font-size: 17px; }


  /* ── 15. kpi-cluster (marketing, product, promo, cs): naijling op blok 9 ──
     Blok 9 verbergt de drie /ads/-iframes in marketing en dat blijft zo — een
     desktop-deck in 358px is niet te bedienen. Maar het liet drie dingen achter
     die op de telefoon niet kloppen. Dit blok vult alleen aan; blok 9 zelf is
     ongewijzigd. .hubfallback, .fsbtn, .hublink en .embedpanel bestaan alleen
     in marketing, dus scoping is hier geen risico.

     1. .hubfallback is de FOUTmelding ("Couldn't load the live hub"). Niets in
        de pagina zet hem ooit aan — blok 9 was de enige plek. Op de telefoon is
        er geen laadfout, dus die tekst liegt tegen de lezer. Weer uit dus, en
        in plaats daarvan één neutrale regel als generated content: de echte
        tekst staat in de HTML en die raakt deze branch niet aan.
     2. .fsbtn (fullscreen) richt zich op een iframe dat er niet meer is — een
        dode knop, pal naast de enige knop die wél werkt.
     3. .hublink is daarmee de enige route van marketing naar de ads-hub en was
        18px hoog, terwijl dit bestand elders consequent naar 44px tilt. */
  .hubfallback { display: none !important; }
  .panel.embedpanel .fsbtn { display: none; }
  .panel.embedpanel::after {
    content: "Too wide for a phone. Use the link above to open the full hub.";
    display: block; padding: 0 20px 16px;
    font-size: 12.5px; line-height: 1.5; color: var(--dim);
  }
  .hublink { min-height: 44px; display: inline-flex; align-items: center; }

  /* De terug-link in de gedeelde bovenbalk uit dashboard-frame.js was 20px hoog
     en daarmee het kleinste tikdoel op promo, product en cs. Blok 7 regelt wel
     a.back (de eigen back-link van de pagina's zónder frame), maar niet deze.
     De balk is 46px en centreert zijn inhoud, dus 44px past er zonder mee te
     groeien. Gedeelde chrome: raakt elk dashboard dat de frame laadt. */
  #bd-frame a.bd-home { min-height: 44px; }


  /* ── 16. po + po-pac: navigatie-cluster (sticky strip + bereikbaarheid) ───
     Vervolg op blok 1, dat de 222px-sidebar al tot een horizontale strip maakte.
     Blok 1 blijft ongewijzigd; alles hieronder komt er alleen bovenop.

     a) STICKY. De strip scrollde volledig uit beeld — weg vanaf 110px scroll in
        po, vanaf 65px in po-pac — terwijl hij op desktop een altijd zichtbare
        vaste kolom is én de enige manier om van sectie te wisselen (de secties
        staan op display:none tot je een navknop indrukt) op een pagina van
        ~3800px. position:sticky vraagt wel dat geen enkele voorouder een
        scroll-box is: de overflow-x:hidden uit blok 0 maakt body er wél een
        (hidden op één as zet de andere op auto), waardoor de nav gewoon zou
        blijven meescrollen. Daarom op déze twee pagina's clip in plaats van
        hidden: clip knipt hetzelfde weg maar maakt géén scroll-container, dus
        sticky hangt weer aan de viewport. De guard uit blok 0 blijft voor alle
        andere dashboards precies zoals hij was; browsers zonder clip (iOS < 16)
        vallen terug op hidden en dus op het oude gedrag.
     b) dashboard-frame.js zet een fixed balk van 46px bovenaan (po laadt die
        wel, po-pac niet). De strip moet daar precies onder blijven hangen;
        #bd-frame wordt als eerste kind van de body ingevoegd, dus de
        sibling-selector dekt beide gevallen zonder dat die JS iets hoeft door
        te geven.
     c) BEREIKBAARHEID. Van de 997px navinhoud stond 607px (61%) buiten beeld
        zonder fade, pijl of zichtbare scrollbar, en het derde item begon op
        x=388 bij een viewport van 390 — het leek alsof de nav bij twee items
        ophield. Het brand-blok at daarvan de eerste 101px en is op de telefoon
        dubbelop: elke sectie opent zelf met de eyebrow "THE BRANDS DEN · SUPPLY
        CHAIN" en een h1. Dat blok gaat dus weg, en een fade rechts laat zien
        dat er meer volgt. De fade is een sticky flex-item-pseudo met negatieve
        marge: hij plakt aan de rechterrand van de scrollstrip en neemt zelf
        geen ruimte in de rij in.
     z-index 20 = boven de kaarten (1-4), onder de modal (.mo-wrap, 50). */
  body:has(> aside.side) { overflow-x: clip; }
  aside.side { position: sticky; top: 0; z-index: 20; }
  #bd-frame ~ aside.side { top: 46px; }
  aside.side .brand { display: none; }
  aside.side::after {
    content: ""; position: sticky; right: 0; left: auto;
    flex: 0 0 28px; align-self: stretch; margin-left: -28px;
    pointer-events: none;
    background: linear-gradient(90deg, rgba(6, 8, 12, 0), var(--bg) 78%);
  }

  /* De guild-chatbubbel in po ligt over de horizontaal te slepen tabel. Bewust
     niet aangeraakt: het is bestaand gedrag dat ook op desktop bestaat, geen
     regressie van deze mobiel-laag, en guild-chat.js injecteert zijn eigen
     <style> ná deze stylesheet — een override zou dus !important vragen voor
     een puur cosmetische winst. Beslissing aan Jim. */


  /* ── 17. grote-datadashboards: inventory-sectiekop + ads-pager ───────────
     Twee restpunten uit de meting op 390px van de dashboards met de zwaarste
     datasets. Beide blokken vullen alleen aan; blok 2 en blok 13 blijven zoals
     ze waren.

     a) INVENTORY. .head is één flexrij zonder wrap en zonder gap. De subtitel
        pakte 201-259px, waardoor de h1 in 103-161px geperst werd en over drie
        regels brak ("Demand" / "&" / "Forecast"); de afstand tussen kop en
        subtitel was in alle acht secties exact 0px, dus het las als
        "InventoryStock by warehouse and shipment pipeline". Onder elkaar
        krijgt de kop de volle regelbreedte. Gescoped op
        aside.sidebar:not(#sidebar) — die selector bestaat alleen in inventory
        (zie blok 2). Raakt ook de overzichtskop, waar rechts geen .sub maar
        het blokje "Updated" staat; dat zakt mee naar onderen en dat is op een
        telefoon precies goed.
     b) ADS. De pagerbalk onderaan groeit mee met het aantal slides: bij de
        markt-tab Bol (8 bolletjes) was de navbar 404px breed in een viewport
        van 390, waardoor de ✕ — de knop die de navigatie wegklikt — volledig
        buiten beeld viel en '‹ Previous' er 14px links af werd gesneden. Dat
        bestond al vóór deze stylesheet, maar het vangnet uit blok 0 maakt de
        balk niet smaller: het maakt die knop alleen onbereikbaar in plaats van
        weg te scrollen. Laten wrappen zet de ✕ op een tweede regel. De
        bolletjes blijven staan; ze zijn de enige plek waar je ziet hoeveel
        slides een markt heeft. */
  aside.sidebar:not(#sidebar) ~ main .head {
    flex-direction: column; align-items: flex-start; gap: 4px;
  }
  body:has(.stage) .navbar { flex-wrap: wrap; row-gap: 6px; }

  /* Het derde gemeten dashboard, bol-ranks, staat hier niet: die pagina laadde
     deze stylesheet helemaal niet. De HTML wordt op de box gegenereerd door
     jobs/bol-ranks/bol-rank-render.mjs en dáár ontbrak de <link>. Die staat er
     nu in (zelfde twee regels als in de andere pagina's zonder frame), waarmee
     blok 6 en 7 de tabel in de 📖-overname-sectie vanzelf binnen de viewport
     brengen — een regel hier zou niets hebben gedaan zolang het bestand niet
     geladen wordt. */


  /* ── 18. ai-team: naijling op blok 14 (pagina's zonder frame) ─────────────
     Drie restpunten uit de meting op 390px. Dit blok vult alleen aan; blok 14
     zelf blijft ongewijzigd.

     a) BLOKKEREND, regressie van blok 14 zelf. In ai-team/index.html sluit een
        verdwaalde </div> #deck al na slide 3, dus maar 3 van de 16 .slide-
        secties zitten er daadwerkelijk in — de andere 13 zijn siblings ná
        #deck. (Die HTML-fout staat er al vóór deze branch; hij hoort bij de
        eigenaar van dat dashboard.) Zolang élke slide position:absolute was
        viel dat niet op. Blok 14 geeft #deck min-height:100dvh en zet de
        actieve slide op position:relative, en daarmee landt slide 4 t/m 16 in
        de flow ónder een lege deck van 844px: je drukt op ›, de teller en de
        bolletjes springen mee, maar het scherm blijft leeg op scrollY 0. De
        min-height mag hier weg omdat de actieve slide zijn eigen
        min-height:calc(100dvh - 116px) uit blok 14 al meebrengt — het scherm
        blijft dus gevuld, of de slide nu in #deck zit of erna.
     b) De demo-URL op slide 11 staat als inline font-size:32px in een .mono en
        kan nergens breken; het paneel werd 429,5px breed in 390, waarop Chrome
        de layout-viewport oprekt en de héle deck op paginaschaal 0,88 rendert
        terwijl de URL zelf rechts wordt afgekapt. Blok 7 dekt pre/code/
        .bp-code/.tree maar niet .mono, en verbreden naar álle .mono zou de
        tabulaire uitlijning uit BRANDING.md in elk ander dashboard raken —
        vandaar gescoped op ai-team.
     c) De navigatiechrome (bolletjes, teller, prev/next) is fixed en had geen
        achtergrond; zodra een slide hoger is dan het scherm liep de tekst er
        zichtbaar onderdoor ('11 / 16' viel over het woord 'launches'). Een
        fade naar var(--bg) onder de balk dekt dat af. z-index 49 = precies
        onder de chrome zelf (die staat in de pagina op 50) en boven de slide-
        inhoud; de slide houdt 116px onderpadding uit blok 14, dus de fade
        verbergt onderaan niets. */
  body:has(.slide-inner) #deck { min-height: 0; }
  body:has(.slide-inner) .mono { overflow-wrap: anywhere; }
  body:has(.slide-inner)::after {
    content: ""; position: fixed; left: 0; right: 0; z-index: 49;
    bottom: 0; height: calc(120px + env(safe-area-inset-bottom));
    pointer-events: none;
    background: linear-gradient(180deg, rgba(6, 8, 12, 0), var(--bg) 28%);
  }


  /* ── 19. overige dashboards: funnel, rankings, compliance, reviews, outreach ─
     Vier restpunten uit de meting op 390px. Dit blok vult alleen aan; blok 0,
     4, 5 en 6 blijven ongewijzigd.

     a) BLOKKEREND (funnel + rankings). Beide pagina's zetten hun tweekolomsgrid
        onder een eigen breakpoint al terug naar één kolom, maar met `1fr` in
        plaats van `minmax(0,1fr)`. Een 1fr-track zakt nooit onder de min-content
        van zijn inhoud, en die min-content is hier groot: in funnel staat in elk
        kanaalpaneel een tabel met min-width:460px, in rankings een balkenrij met
        white-space:nowrap op het label. Het paneel werd zo 560px (funnel) resp.
        523px (rankings) breed in een viewport van 390. Vóór deze stylesheet rekte
        Chrome de layout-viewport dan op (innerWidth 611) en was de inhoud nog met
        zoomen te bereiken; met de overflow-guard uit blok 0 knipt body het weg,
        en dan is het definitief onbereikbaar — in funnel de kolommen Orders,
        Conv. en WoW, in rankings de waardelabels rechts van elke balk. De
        .tablewrap uit blok 4 kon dat niet opvangen: die groeide gewoon mee, dus
        scrollWidth == clientWidth en er viel niets te scrollen. Met minmax(0,1fr)
        + min-width:0 krimpt de kolom wél tot de viewport en pakt de scroll-
        container uit blok 4 zijn werk weer op.
        Andere dashboards met een .grid2: po, po-pac en inventory stonden op deze
        breedte al op één kolom (identiek resultaat, alleen nu krimpbaar); ads
        houdt zijn twee kolommen omdat zijn eigen regel `.type-card .grid2`
        specifieker is. .grid3 bestaat alleen in rankings.

     b) tapdoelen die onder de 44px bleven die dit bestand elders wel aanhoudt.
        Niet blokkerend — alles is aanraakbaar, alleen krap. Alle drie zijn
        gescoped op een selector die maar in één dashboard bestaat: .chiprow
        (compliance; index.html heeft .chip-row mét streepje), .tabbar (reviews)
        en button.sb-top (outreach — in cs en product is .sb-top een div, geen
        knop). De losse .chip-badges in rankings, promo, cs en outreach zijn
        labels, geen knoppen, en blijven dus met opzet buiten schot. */
  .grid2, .grid3 { grid-template-columns: minmax(0, 1fr); }
  .grid2 > *, .grid3 > * { min-width: 0; }

  /* compliance: severity-filter (29px) — de enige bediening boven de tabel */
  .chiprow .chip { min-height: 40px; }
  /* reviews: bronfilter boven 'Recent reviews' (33px) */
  .tabbar button { min-height: 40px; }
  /* outreach: de drie hoofdsecties in de sidebar (32px), pal boven .sb-nav a
     die in blok 3 al op 44px staat */
  button.sb-top { min-height: 44px; display: flex; align-items: center; }
}
