/* ============================================================
   mobile.css — the phone layer.

   Loaded LAST of every stylesheet, deliberately: source order breaks specificity ties, so
   a rule here overrides the same selector in mystyle.css without `!important` and without
   editing a file the owner is also working in. mystyle.css alone carries 114 media queries
   across six breakpoints (767, 768, 833, 900, 640, 560); keeping the phone work in its own
   layer is what keeps the diff readable.

   🔴 THE ONE RULE: every block in this file sits inside a max-width media query. Nothing at
   the top level, ever. That is what makes "the desktop is untouched" a fact instead of a
   claim, and scripts/rewrite-coverage.mjs fails the build if a rule ever escapes.

   Breakpoints: 767px is the phone layer. 390px exists only for tightening type and padding.
   768–900px keeps today's tablet behaviour, bottom tab bar included — that seam is
   deliberate, not an oversight.
   ============================================================ */

@media (max-width: 767px){

  /* --- The black bar, on every view ------------------------------------------------
     Spec §1: the global navigation stays at the top of EVERY mobile view, not just home.
     mystyle.css:2831 hides it outright under `body.dashboard-mode`, which is every hub and
     every detail route, so on a phone those pages had no top chrome at all — content began
     at pixel zero and the only navigation was the bottom bar.

     Restoring it costs one line, because the pill is already phone-tuned at ≤833px
     (mystyle.css:3503 drops the link row, tightens the inset to 12px and keeps the
     wordmark). `display: block` rather than flex: `.nav` is a block element (app.css:153)
     and `.nav-inner` is the flex container inside it. The breadcrumb that lives in the
     brand group is already switched on for this mode at mystyle.css:2812, which is exactly
     the "fixed nav → breadcrumb" order §27 asks for.

     🔴 Reusing the pill brought its GEOMETRY too, and that turned out to be wrong here —
     see "the side-panel bar is FLAT" below, which flattens it. This line still does the
     right thing (the bar belongs on every view); only its shape needed correcting. */
  body.dashboard-mode .nav.nav--floating{ display: block; }

  /* --- The bar is FLAT on every phone view, not a floating pill (refinement §1) -------
     Reusing the home pill above was the cheap way to give hub pages top chrome, and it
     cost one line — but it also gave them the pill's geometry: fixed, inset 12px at the
     sides (mystyle.css:3535), 20px radius. Measured at 390 it renders x=12 w=366 r=20px,
     which is exactly the "separate floating rounded card" with "visible white margins"
     the owner marked. A native app header is flush.

     Insets go to zero rather than the rule being rewritten, because every offset in the
     pill's own block derives from those two variables — zeroing them moves the bar and
     leaves one source of truth. The notch inset moves from OFFSET to PADDING: the bar has
     to reach the top edge and paint its black behind the status area, not start below it.
     `box-sizing: border-box` is global (app.css:88) so the padding sits inside the height.
     That padding REPLACES the old `--nav-inset-y: calc(8px + env(...))` rule that used to
     stand here for the notch (spec §26) — same job, done as height instead of offset.

     🔴 HOME included, owner 2026-09-17. This began scoped to `body.dashboard-mode` because
     §1 is written about side-panel screens, but scrollview_01.png draws home with the same
     full-bleed bar and §12 asks for "ONE coherent native mobile application". One selector
     for every phone view is also the only version of this that cannot drift apart. */
  .nav.nav--floating{
    --nav-inset-x: 0px;
    --nav-inset-y: 0px;
    border-radius: 0;
    height: calc(58px + env(safe-area-inset-top));
    padding-top: env(safe-area-inset-top);
  }

  /* --- Content offset ---------------------------------------------------------------
     The shell already owns the mechanism, so this is a variable change rather than new
     layout. `#app` carries `padding-top: var(--dash-bar-h)` (mystyle.css:2884) and
     `.shell` is `calc(100vh - var(--dash-bar-h))` (2891); dashboard-mode zeroed that
     variable (2830) precisely BECAUSE the bar was hidden. With the bar back, the variable
     goes back to describing what the bar occupies: its 58px height (mystyle.css:2650) and
     the notch inset it now pads itself with.

     `box-sizing: border-box` is global (app.css:88), so the padding sits inside the
     shell's 100vh instead of adding to it, and only `.dash-scroll` moves — which is the
     "fixed nav, content scrolls underneath" contract in §19, and the project's own rule
     that the dash shell is fixed chrome with one scrolling region.

     Refinement §2: the banner must TOUCH the bar. Two things held it 18px away, measured
     at 390 — the 8px "so content does not graze it" gap that used to be the last term
     here, and `--shell-inset` (mystyle.css:3258) which pads .shell on all four sides. The
     gap had a job when the bar was a floating pill with ground showing around it; a flush
     bar has nothing to graze.

     --shell-inset goes to 0 on phones only. Its own comment at mystyle.css:3256 says that
     at this width "the rail is display:none, so the sheet's only job is to inset the
     banner" — and insetting the banner is precisely what is being removed. The banner
     goes full-bleed; CARDS keep their gutter because `.dash-scroll` carries its own 18px
     (mystyle.css:3255), which is untouched. `.side-collapse-fab` reads --shell-inset with
     a 0px fallback (mystyle.css:6249) and the rail is hidden here anyway, so zeroing it
     moves nothing else. */
  body.dashboard-mode{
    --dash-bar-h: calc(58px + env(safe-area-inset-top));
  }
  /* On `.shell`, not on the body: mystyle.css:3258 declares --shell-inset directly on
     `.shell`, and a declaration on the element beats one inherited from an ancestor
     whatever the ancestor's specificity. Same selector, later file, so this wins. */
  body.dashboard-mode .shell{ --shell-inset: 0px; }

  /* ============================================================
     M2 — the floating button and the glass drawer
     ============================================================ */

  /* Both phone openers go. The spec replaces a bottom bar with the floating button (§2),
     and the ☰ opened the same all-sections sheet the drawer now owns. This is display:none
     inside a 767 query rather than a deletion because the tablet contract is deliberately
     unchanged: from 768px up the tab bar and the sheet are still the navigation. */
  body.dashboard-mode .dash-tabbar{ display: none; }
  .nav-menu{ display: none; }

  /* The scroller's 78px bottom reserve (mystyle.css:3170) and the footer's matching 102px
     (:3177) are deliberately LEFT ALONE. They were sized for the tab bar, and they happen to
     be right for the button too: it occupies 66px from the bottom edge (14px inset + 52px),
     so 78px keeps a 12px clearance and the footer maths that stops it surfacing in the first
     viewport still balances at 78 + 24. Re-deriving those numbers for a corner-only obstacle
     is an M9 density question, not something to guess at here. */

  /* --- The floating button (§2) ------------------------------------------------------ */
  .m-fab{
    position: fixed;
    right: calc(14px + env(safe-area-inset-right));
    bottom: calc(14px + env(safe-area-inset-bottom));
    z-index: 112;                      /* above the drawer: it stays the toggle while open */
    width: 52px; height: 52px;         /* inside the spec's 48-56px target */
    display: flex; align-items: center; justify-content: center;
    padding: 0;
    border: .5px solid rgba(255,255,255,.6);
    border-radius: 50%;
    background: rgba(255,255,255,.72);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    backdrop-filter: saturate(180%) blur(20px);
    box-shadow: 0 6px 20px rgba(16,24,40,.18), 0 1px 2px rgba(16,24,40,.10);
    color: var(--ink);
    cursor: pointer;
    transition: transform .18s var(--ease), background .18s var(--ease);
  }
  .m-fab:active{ transform: scale(.94); }
  .m-fab:focus-visible{ outline: 2px solid var(--accent); outline-offset: 3px; }

  /* --- The standalone close control --------------------------------------------------
     Owner 2026-09-16: "disable the bottom browser for iPhone, it adds a close button in the
     left corner". A page cannot remove browser chrome from an ordinary tab — that is a
     browser security rule, not an opt-out — so the answer is standalone mode. Installed to
     the Home Screen the site launches with no chrome at all, and this is the affordance that
     replaces the close button the chrome used to provide.

     assets/mobile.js mounts this ONLY when actually running standalone, so in an ordinary
     tab the element does not exist at all. That guarantee is structural rather than a
     `display:none` some later specificity change could quietly undo — which matters, because
     a stray close button in normal browsing is the obvious way this feature goes wrong.

     Mirrors .m-fab across the screen: same box, same insets, opposite corner. The glass is
     copied deliberately — the phone layer's floating button and drawer are already glass by
     design, so a flat button here would read as a different system. */
  .m-close{
    position: fixed;
    left: calc(14px + env(safe-area-inset-left));
    bottom: calc(14px + env(safe-area-inset-bottom));
    z-index: 112;
    width: 52px; height: 52px;
    display: flex; align-items: center; justify-content: center;
    padding: 0;
    border: .5px solid rgba(255,255,255,.6);
    border-radius: 50%;
    background: rgba(255,255,255,.72);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    backdrop-filter: saturate(180%) blur(20px);
    box-shadow: 0 6px 20px rgba(16,24,40,.18), 0 1px 2px rgba(16,24,40,.10);
    color: var(--ink);
    cursor: pointer;
    transition: transform .18s var(--ease), opacity .18s var(--ease);
  }
  .m-close:active{ transform: scale(.94); }
  .m-close:focus-visible{ outline: 2px solid var(--accent); outline-offset: 3px; }
  /* .m-drawer is fixed top:0 left:0 bottom:0, so an open drawer lands exactly on this
     corner. Reuse the body class the drawer already sets rather than adding a second flag. */
  body.m-drawer-open .m-close{ opacity: 0; pointer-events: none; }

  /* --- The scrim (§3: dim lightly, keep the page readable behind) --------------------- */
  .m-scrim{
    position: fixed; inset: 0; z-index: 110;
    background: rgba(16,24,40,.18);
    opacity: 0;
    transition: opacity .2s var(--ease);
  }
  body.m-drawer-open .m-scrim{ opacity: 1; }
  .m-scrim[hidden]{ display: none; }

  /* --- The drawer -------------------------------------------------------------------- */
  .m-drawer{
    position: fixed;
    top: 0; left: 0; bottom: 0;
    z-index: 111;                      /* over the top bar, as the reference shows */
    width: min(56%, 300px);            /* the spec's 50-60% */
    display: flex; flex-direction: column;
    padding: calc(10px + env(safe-area-inset-top)) 0 calc(10px + env(safe-area-inset-bottom));
    background: rgba(252,253,255,.82);
    -webkit-backdrop-filter: saturate(180%) blur(24px);
    backdrop-filter: saturate(180%) blur(24px);
    border-right: .5px solid rgba(255,255,255,.7);
    border-radius: 0 22px 22px 0;
    box-shadow: 8px 0 34px rgba(16,24,40,.18);
    transform: translateX(-100%);
    transition: transform .3s cubic-bezier(.32,.72,0,1);
    will-change: transform;
  }
  body.m-drawer-open .m-drawer{ transform: translateX(0); }
  .m-drawer[hidden]{ display: none; }

  .m-drawer-scroll{
    flex: 1; min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0 10px;
    overscroll-behavior: contain;      /* the page behind must not scroll with it */
  }

  .m-head{
    display: flex; align-items: center; gap: 9px;
    padding: 2px 12px 14px;
    font-weight: 600; font-size: 15px; letter-spacing: -.01em;
    color: var(--ink);
  }
  .m-head .brand-mark{ width: 26px; height: 26px; border-radius: 6px; }

  .m-sec{ margin-bottom: 14px; }
  .m-lbl{
    font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
    color: var(--text-4); font-weight: 600;
    padding: 0 12px 6px;
  }
  .m-foot{
    margin-top: 2px; padding-top: 10px;
    border-top: .5px solid var(--border);
  }

  /* 🔴 Scoped `body .m-drawer …` on purpose. The dark-rail rules are
     `body.dashboard-mode .side-item` (0,2,1) — mystyle.css:3278 paints them
     rgba(255,255,255,.76), which on this light glass is invisible. A bare
     `.m-drawer .side-item` is (0,2,0) and would lose. `body .m-drawer .side-item` ties at
     (0,2,1) and wins on source order, because mobile.css is last. Same trap the .dash-sheet
     rules document at mystyle.css:3187. */
  body .m-drawer .side-item{
    min-height: 44px;                  /* §20's touch target */
    gap: 12px;
    padding: 10px 12px;
    border-radius: 12px;
    font-size: 15px;
    color: var(--text-2);
    background: transparent;
    box-shadow: none;
    position: relative;
  }
  body .m-drawer .side-item .ico{ color: var(--text-2); opacity: .75; }
  body .m-drawer .side-item:active{ background: rgba(255,255,255,.6); }

  /* Active row: the light glass pill plus a blue accent bar, per the reference. */
  body .m-drawer .side-item.active{
    background: rgba(255,255,255,.78);
    color: var(--ink);
    font-weight: 600;
    box-shadow: 0 1px 2px rgba(16,24,40,.08);
  }
  body .m-drawer .side-item.active .ico{ color: var(--accent); opacity: 1; }
  body .m-drawer .side-item.active::before{
    content: '';
    position: absolute; left: -4px; top: 50%;
    width: 3px; height: 20px; margin-top: -10px;
    border-radius: 2px;
    background: var(--accent);
  }

  /* Scroll chaining is stopped by overscroll-behavior on the scroller above, NOT by locking
     the body. `overflow: hidden` on <body> is banned in this project — it breaks
     position: sticky — and dashboard-mode already runs `overflow: clip` with a fixed height,
     so a second owner of that property is a bug waiting to happen. */

  /* §3 and the architecture: reduced motion gets the state change without the travel. */
  @media (prefers-reduced-motion: reduce){
    .m-drawer, .m-scrim, .m-fab{ transition: none; }
  }

  /* ============================================================
     M3 — the Home hero
     ============================================================ */

  /* §4: "keep the large sphere/background visual". The film IS the sphere on a phone —
     setupHeroLayers swaps in hero-loop.m.mp4 and hands off to setupHeroFilm
     (mystyle.js:776), which is also why the DOM orbit is flagged `is-film` and stays dark.

     🔴 OWNER REVERSAL 2026-09-16 — this reverses M3. The film is the hero BACKGROUND on a
     phone, full-bleed behind the type, exactly as it is above 767px and as the reference
     (Desktop/mobile/scrollview_01.png) shows it. M3 had put it in flow between the sub-copy
     and the buttons as a 16:9 card, to stop a 960x540 film being cover-cropped into a
     390x832 portrait box — that crop throws away roughly three quarters of every frame and
     upscales what survives about 1.6x. The cost is real and it is now accepted: the owner
     wants the film scaled to fill. `object-position` below is the whole of what this layer
     still has to say; a portrait re-encode from the masters is the only thing that would buy
     the sharpness back, and that is its own unit.

     M3's comment here claimed its markup move left the desktop untouched. It did not — the
     move put the film inside `.ms-inner`, which became its containing block AND its stacking
     context, so above 767px the film shrank to the text column and painted over the type.
     See the layer ladder at mystyle.css:210. The film is a direct child of `.ms-hero` again.

     M3 also forced `display: block` here to beat the reduced-motion `display: none` at
     mystyle.css:608. That override is gone: under reduced motion the phone now falls back the
     same way the desktop does, to the `hero-loop.jpg` ground `.ms-hero` already paints at
     mystyle.css:617-623. Same picture, one layer instead of two. */
  .ms-hero-ribbons{
    /* Only the HORIZONTAL axis is cropped here: 960x540 covering 390x844 scales by height
       (844/540 = 1.56) to ~1500px wide, so the full frame height shows and just 26% of its
       width does. A y value would be inert, hence `center` and not a number.

       The x is measured, not guessed. Sampling 16 frames across the 9.46s mobile encode and
       taking the centroid of the dark pixels (the sphere body and lens are the only sustained
       dark mass in an otherwise pale frame) puts the sphere between 16% and 80% of the frame
       width — it travels. Median 37%, mean 42%. With a 26%-wide window, centring on 28% keeps
       the sphere in shot for 9 of those 16 frames; centring on 50% manages 5. So: 28%. */
    object-position: 28% center;
  }
  /* The DOM orbit stays hidden. Painting it as well would put a second sphere on top of the
     one already in the film — which is exactly what U-mobile-3 removed it to avoid. */

  /* --- The two CTAs on ONE row (§4) -------------------------------------------------
     They wrap today because "Run your first agent" and "Read this week's brief" cannot sit
     side by side at 390px. The short labels are the spec's own, and they are a SWAP rather
     than a rewrite: the full wording is still in the markup and is still what the desktop
     renders. Neither button goes full width — each takes half the row with a gap. */
  .ms-hero-ctas{
    flex-wrap: nowrap;
    gap: 10px;
    margin-top: 18px;
  }
  /* Owner 2026-09-16: smaller. They were 170x48 at 390px — about 44% of the screen each,
     which made them compete with the audit pill below, and the pill is the hero's actual
     primary action. Now ~150x44. */
  .ms-hero-ctas .ms-btn--inhero{
    flex: 1 1 0;
    min-width: 0;
    max-width: 150px;
    padding: 10px 16px;
    font-size: 14px;
    /* The 44px tap floor, stated instead of implied. 10px padding around a 14px line box
       lands at ~43px on Inter's metrics — one pixel under, and invisible in a diff that
       only shows the padding change. `min-height` holds the floor even if the font metrics
       move later. a11y-behaviour.mjs:57 and e2e.mjs:2124 are what enforce 44 here. */
    min-height: 44px;
    border-radius: 999px;
    white-space: nowrap;
  }

  /* --- Stats: compressed, not shrunk (§4: "do not make these oversized") ------------- */
  .ms-hero-stats{ margin-top: 22px; }
  .ms-hero-stat{ padding: 0 clamp(10px, 3.6vw, 20px); gap: 4px; }
  /* Owner reference 2026-10-11: the stats are one white card. On a phone four cells across
     leave ~87px each, too narrow for an icon, a number and a label, so it is a 2x2 grid of
     hairline cells. */
  .ms-hero-stats{ display: grid; grid-template-columns: 1fr 1fr; }
  .ms-hero-stat{ padding: 10px 14px; gap: 10px; }
  .ms-hero-stat + .ms-hero-stat{ border-left: 0; }
  .ms-hero-stat:nth-child(even){ border-left: .5px solid var(--border-2); }
  .ms-hero-stat:nth-child(n+3){ border-top: .5px solid var(--border-2); }
  .ms-hero-stat-label, .ms-hero-stat-sub{ white-space: normal; }
  /* The placeholder is already cut short on a phone; the sparkle would take another 28px. */
  .ms-hero-spark{ display: none; }

  /* The hero should "fill the first viewport naturally but not become unnecessarily tall"
     (§4). M3 set `min-height: 0` because the in-flow film made the stacked column tall enough
     by itself. The film is the background again, so the column no longer carries that ~219px
     card and the hero needs an explicit floor — without one the background has no box to fill.

     `svh`, not `vh`: the viewport is `viewport-fit=cover` (index.html:5), so `vh` resolves
     against the LARGE viewport and the film would overshoot the visible area on load, then
     resize under the reader when the iOS URL bar collapses. `svh` is the small viewport, so
     the hero fits on arrival and the film never jumps mid-scroll.

     The margin compressions below stay; each was measured, not guessed: the scroll cue alone
     was 108px of box under 68px of margin, and the empty .ms-audit-result still contributed
     its 24px top margin. */
  /* 🔴 The hero's top offset was the ONLY phone measurement in this file that read the
     DESKTOP --nav-h (104px, app.css:82) instead of the bar this file actually draws:
     `58px + env(safe-area-inset-top)` (:62). At an inset of 0 the two agree by luck —
     104-16 = 88 against a 58px bar, so headless sees 30px of clearance and every CI run
     passes. On a notched iPhone the inset is 47-59px, so the bar stands 105-117px tall
     while content still starts at 88px and the eyebrow renders UNDER the bar. The
     installed PWA (manifest.webmanifest, "display":"standalone") is the worst case: no
     browser chrome absorbs any part of the notch. Owner reported it on iPhone Safari,
     iPhone Chrome and the installed app on 2026-09-23.
     Playwright reports an inset of 0, so CI can never see this — hero-chips-verify.mjs
     asserts the AUTHORED declaration out of the CSSOM rather than the computed value,
     which would be 88px either way and could never fail.
     30px is exactly the clearance the old arithmetic produced at inset 0, kept on purpose
     so this change moves nothing at any width the suite is able to measure. */
  .ms-hero{ min-height: 100svh; padding-top: calc(58px + env(safe-area-inset-top) + 30px); padding-bottom: 20px; }

  /* --- WU-2: the dead space moves to where the sphere is (scrollview_01.png) -----------
     The hero is 100svh, the column runs ~540px, and `align-items: flex-start` (mystyle.css:233)
     left the remaining ~244px sitting BELOW the scroll cue as nothing. The reference spends
     it between the sub-copy and the CTAs, where the film's sphere shows through — the gap is
     the composition, not leftover.

     An auto margin only ever consumes FREE space, so this degrades correctly: on a short
     viewport, or with large text, the space runs out, the margin collapses to zero and the
     column falls back to natural flow. It cannot push the hero past 100svh, which is what
     `e2e.mjs:1664` (height ≤ 1.12 × viewport) is watching.

     🔴 Phone only, and only safe here. mystyle.css:224-227 anchors the stack to the hero's
     TOP because the orbit sphere's path is calibrated in hero-top-relative pixels and a
     centred stack would slide under it — but mystyle.css:633 already sets
     `.ms-hero-orbit{display:none}` below 768px, so on a phone there is no orbit for the
     column to slide under. The constraint that comment protects is not present here. */
  .ms-hero{ align-items: stretch; }
  .ms-hero .ms-inner{ display: flex; flex-direction: column; }

  /* 🔴 AMENDS WU-2 above — owner, 2026-09-23: "cap it, spread the rest".
     WU-2's bare `margin-top:auto` on the CTAs absorbed EVERY free pixel into ONE gap.
     Measured at 390x844 before this change: a 200px void after the sub-copy against a
     16px median everywhere else — which is precisely what "a void on top, a traffic jam
     underneath" was describing, and why the hero read as cramped despite being 100svh.
     WU-2's reasoning is NOT wrong and is left standing above: the gap IS the composition,
     it is where the film's sphere shows through. It was uncapped, not misplaced. So the
     auto margin becomes a spacer that grows exactly the same way and then STOPS. It still
     consumes only FREE space, so it still collapses to zero on a short viewport and still
     cannot push the hero past 100svh — e2e.mjs:1664 (hero <= 1.12x viewport) is unaffected.
     The remainder is spent at the BOTTOM, where the scroll cue is pinned to the fold edge
     (see the cue's own rule below). That is the hero's bottom breathing room rather than a
     gap in the column, which is why hero-chips-verify.mjs measures column rhythm across
     the CONTENT rows and asserts the cue's position separately. */
  .ms-hero-gap{ flex: 1 1 0; min-height: 0; max-height: min(110px, 13vh); }
  /* Owner, 2026-10-05: "close the empty space" above SCROLL (75px at 390x844 once the audit
     heading went). The hero now ends at its content instead of holding 100svh, so the spacer
     above gets a fixed height (with no free space a flex spacer would collapse to 0) and the
     cue follows the readers pill. Supersedes the 2026-09-23 "spend the rest at the bottom". */
  .ms-hero{ min-height: 0; }
  .ms-hero-gap{ flex: 0 0 auto; height: min(110px, 13vh); }
  /* Zeroed, not left at :333's 18px: the spacer above IS the separation here, and stacking
     the two would make the visible gap 128px while the cap says 110 — a ceiling you cannot
     read off the rule that sets it. The cap is now the whole gap. */
  .ms-hero-ctas{ margin-top: 0; }

  .ms-hero-form{ margin-top: 18px; }
  /* The chip arrows are `top:50%` of .ms-chipwrap (mystyle.css:4711), and on a phone that
     wrapper is a flex ITEM of the .ms-inner column above, so it does not let the row's top
     margin collapse through it the way the plain block does at 768-900. With the 14px on
     the row the wrapper was 48px tall and its centre sat 7px above the chips (691 vs 698 at
     390). The gap belongs on the wrapper: the row then fills it and the arrows centre on
     the chips. The result state zeroes the row's margin (mystyle.css:4891); the wrapper
     follows it here so that state does not gain 14px. */
  /* The hint hugs the row it describes, so the row's own top margin stands down when the
     hint is there. Keyed on the sibling so the bare .ms-chipwrap rule above stays as
     measured, and so the result state (which zeroes it) still wins on source order. */
  .ms-swipe-label + .ms-chipwrap{ margin-top: 6px; }
  .ms-chipwrap{ margin-top: 18px; }
  .ms-audit-chips{ margin-top: 0; }
  .ms-hero--result .ms-chipwrap{ margin-top: 0; }
  .ms-audit-result:empty{ margin-top: 0; }
  /* The cue is 112px on a phone, and 56 of those are the pulsing vertical rule under the
     chevron — a desktop flourish that costs more than a fifth of the fold here. The label
     and the chevron carry the same meaning, which is all the reference shows. */
  /* `auto`, not 16px: the cue is a fold-edge affordance, so it takes whatever the capped
     .ms-hero-gap above did not, and sits at the bottom of the 100svh hero where a scroll
     hint belongs. On a short viewport there is no free space, auto collapses to 0 and the
     cue falls back into natural flow directly under the stats. */
  .ms-hero-scroll-cue{ margin-top: 20px; gap: 8px; }
  .ms-hero-scroll-cue::after{ display: none; }
  .ms-hero-scroll-cue span{ gap: 8px; }
  .ms-hero-stats{ margin-top: 20px; }

  /* Owner, 2026-09-17, from a phone screenshot: the live-audit line, the stat captions
     and the SCROLL cue "are not visible" over the film. Ink-on-ink white — the token the
     ink chips and the active crumb already use (app.css:52). scrollview_01.png draws these
     in --text-3 grey on a still frame; the film under them moves, and the owner chose
     white against it. The numbers above the captions stay --ms-ink: not asked for, and
     they already read. The cue's chevron is `currentColor` (mystyle.css:542), so it
     follows without a rule of its own. */
  /* 2026-10-11: the stat captions left this list — they sit on the white stats card now. */
  .ms-hero .ms-swipe-label,
  .ms-hero-scroll-cue{ color: var(--ink-text); }

  /* ============================================================
     M4 — the Build section
     ============================================================ */

  /* §6: keep the desktop composition — instruction cards LEFT, Editor's Choice RIGHT —
     "a scaled-down version of the desktop section, not a completely different layout".

     mystyle.css:5115 collapses this to one column on phones, and its comment records why:
     at 390 the 1.5fr/1fr split left the cards ~226px wide and 266–379px tall, "one word per
     line", and squeezed the tile to 118x985. That measurement was real, but it was taken
     with each card still wearing its DESKTOP internals — a 44% image beside 19px/13.5px type
     inside 28px/30px padding. The column was never the problem; what was in it was. §6 asks
     for exactly the missing half: reduce the card heights, the widths, the internal padding
     and the image dimensions. Scale those and two columns fit, which is what the reference
     shows and what this restores. Every value below is read off that reference. */
  .ms-build-grid{
    grid-template-columns: 1.2fr 1fr;
    gap: 10px;
  }
  .ms-build-left{ gap: 10px; }
  .ms-build-card{
    gap: 9px;
    padding: 10px;
    border-radius: 12px;
    min-height: 0;
  }
  .ms-build-flow-img{
    flex: 0 0 42%;
    width: 42%;
    max-width: 42%;
    border-radius: 7px;
  }
  .ms-build-mdicon{ width: 38px; height: 38px; border-radius: 9px; padding: 7px; }
  .ms-build-copy{ gap: 4px; }
  .ms-build-title{ font-size: 12px; line-height: 1.24; letter-spacing: -.008em; }
  .ms-build-desc{ font-size: 11px; line-height: 1.36; max-width: none; }

  /* The tile is portrait again and stretches to the height of the three cards beside it,
     which is what the grid's default align-items: stretch already wants to do — the phone
     rule's 16/10 aspect and 200px floor were what stopped it. It cannot dominate the screen
     (§6) because it is now one column of two rather than a full-width band. */
  .ms-build-ec{ aspect-ratio: auto; min-height: 0; }
  .ms-build-ec-overlay{ padding: 12px; }
  .ms-build-ec-tag{ font-size: 9px; letter-spacing: .08em; line-height: 1.3; }

  /* ============================================================
     M5 — hub banner and breadcrumb (Learn, and every hub that shares the chrome)
     ============================================================ */

  /* Refinement §3, and the tail of §1. The stage carries --r-xl (24px) from
     mystyle.css:6002, which was right while --shell-inset floated it on the pale sheet: a
     rounded rectangle with ground around it. It is full-bleed now, and a full-bleed element
     with 24px corners is the "floating card" look surviving its own cause — the corners cut
     into the top bar above it and into the page at the sides.

     🔴 The HEIGHT is deliberately not touched. §3 asks for the banner to be "significantly"
     shorter, but the stage is already 120px here (mystyle.css:6338) and the reference
     measures ≈129 logical px, so it is already shorter than the thing it is being compared
     to. What made the upper block feel tall was the 28px of gap and inset above it, which
     Unit A removed. Shrinking a stage that M5 sized to fit a one-line headline plus a
     two-line description would only clip the copy. */
  .dash-hero-stage{ border-radius: 0; }

  /* ---- the banner shrinks WITH the scroll, not at one threshold ----
     assets/dash-hero.js writes `--aad-collapse` on the shell root every scroll frame: 0 at
     the top of the list, 1 at its COLLAPSE_AT (64px), linear between. The banner reads it
     here and interpolates its own height between the open size and the 38px title bar, so
     the picture gives ground as the finger moves. The two-state `.is-collapsed` class keeps
     the things that cannot be interpolated - the floating row, the search field, the
     one-line title layout - and every calc below lands on exactly the value that class asks
     for at 1, so the flip past 64 changes nothing that is on screen.

     THREE SELECTORS, mirroring mystyle.css:7151-7153: the story hero declares its stage
     height at (0,2,0) (mystyle.css:6674, 150px) and again at (0,3,0) through
     :has(.sxh-credit) (mystyle.css:7041, 204px). A media query adds no specificity, so each
     of those has to be met at its own weight; mobile.css is the last sheet, so equal weight
     wins.

     transition: none because this height is driven by the scroll position. The 180ms ease
     at mystyle.css:7194 would make every frame chase the one before it and the banner would
     lag a finger that is still moving. The class-driven step has nothing left to animate. */
  .dash-hero-stage{
    --aad-stage-open: 120px;
    height: calc(var(--aad-stage-open) - (var(--aad-stage-open) - 38px) * var(--aad-collapse, 0));
    transition: none;
  }
  .dash-hero-panel--story .dash-hero-stage{
    --aad-stage-open: 150px;
    height: calc(var(--aad-stage-open) - (var(--aad-stage-open) - 38px) * var(--aad-collapse, 0));
  }
  .dash-hero-panel--story .dash-hero-stage:has(.sxh-credit){
    --aad-stage-open: 204px;
    height: calc(var(--aad-stage-open) - (var(--aad-stage-open) - 38px) * var(--aad-collapse, 0));
  }

  /* ---- and it grows back the same way ----
     The class carries HYSTERESIS: it goes on above 64 and only comes off below 8, so on the
     way back up the whole band from 64 to 8 is still `.is-collapsed`. mystyle.css:7151-7153
     pins the stage at 38px there, at (0,2,0)/(0,3,0)/(0,4,0) against the (0,1,0)/(0,2,0)/
     (0,3,0) calcs above, so the banner used to sit shut all the way home and then snap open
     at the last pixel. Measured before this rule: 38px at y=32 and at y=8 coming up, where
     the height the scroll asks for is 79 and 109.75.

     The same three selectors again, each prefixed with `.is-collapsed` so it meets its
     mystyle.css twin at its own weight. Only the height line is needed: --aad-stage-open is
     a custom property and the rules above already resolved it on these very elements, so the
     150px and the 204px are still in hand. */
  .is-collapsed .dash-hero-stage,
  .is-collapsed .dash-hero-panel--story .dash-hero-stage,
  .is-collapsed .dash-hero-panel--story .dash-hero-stage:has(.sxh-credit){
    height: calc(var(--aad-stage-open) - (var(--aad-stage-open) - 38px) * var(--aad-collapse, 0));
  }

  /* The picture goes with it. It fades over the SECOND half of the shrink, after the text
     has gone, so the two do not cross-fade over each other; at 1 it is fully transparent,
     which shows the same bar of banner ink the zero-height track used to leave behind.
     mystyle.css:7147 zeroes the track's height for the class - that is a step, and a step in
     the middle of a scroll is the pop. Restoring the base height (mystyle.css:6057,
     `height: 100%`) at (0,2,0), later in the cascade, hands the track back to the variable in
     both directions.

     🔴 AND THE `overflow: clip` ON THAT SAME RULE HAS TO GO WITH IT. The track is 100% wide
     and holds one 100%-wide slide per banner, so its children run three viewports past its
     own box and `transform: translateX(-100%)` is how the slider picks one. Clipping to the
     track's box therefore keeps only whichever slide sits at offset 0 - and the transform
     moves the clip box with it, so the slide that is actually on screen is the one thrown
     away. Screenshotted at 390 on /guide, on the way back at y=32 with the track at
     translateX(-100%): opacity 1, height 79, an <img> measured at 0,58,390,79 - and a black
     banner. The clip was only ever there to stop a ZERO-height track painting its pictures
     outside itself; with the height back it has nothing to do, and the stage's own
     overflow:hidden (mystyle.css:6052) is what frames the slider every other moment of its
     life. `visible` is the base value the rest of the time. */
  .dash-hero-track{ opacity: calc((1 - var(--aad-collapse, 0)) * 2); }
  .is-collapsed .dash-hero-track{
    height: 100%;
    overflow: visible;
    opacity: calc((1 - var(--aad-collapse, 0)) * 2);
  }

  /* The headline and the description fade out over the first half of the shrink rather than
     being sliced off by the stage closing over them. */
  .dash-hero-text{ opacity: calc(1 - var(--aad-collapse, 0) * 2); }
  /* ...and the one-line title bar the class lays out fades back IN over the second half, and
     out again on the way down. A flat opacity:1 here was the other half of the snap: the
     collapsed title appeared the instant the class landed and vanished the instant it left.
     (0,2,0) over the (0,1,0) above, so no !important.

     What the class still owns is everything that is a layout, not a quantity: the floating
     row, the search field under the chips, and the title's own one-line treatment - those
     cannot be interpolated, and the hysteresis is what keeps them from thrashing at the
     boundary. The heights, the picture and the text follow the variable in both directions
     now, so at either end of the band the class flip changes nothing on screen.

     The one step left is the class itself: going DOWN it is off for the whole ramp and lands
     in a single frame at y>64, so the calc jumps from 0 to 1 between two frames and the title
     appeared instantly. 180ms of opacity smooths exactly that frame. Coming back up nothing
     changes hands - the calc still drives the value over the second half of the band and the
     transition only trails it by a fifth of a second. Written out rather than through the
     --ms-* tokens, for the reason at mystyle.css:7183-7187: those are declared on
     `body.mystyle` and a hub route's body is `dashboard-mode`, so the token form computes to
     `all 0s`. */
  .is-collapsed .dash-hero-text{
    opacity: calc(var(--aad-collapse, 0) * 2 - 1);
    transition: opacity 180ms cubic-bezier(.16, 1, .3, 1);
  }

  /* The slider controls leave before they collide. The pause pill is anchored to the TOP of
     the stage (mystyle.css:6116, top 12) and the two arrows to its BOTTOM (mystyle.css:6146,
     bottom 10), so as the stage closes the floor rises into the ceiling: measured at 390,
     the next arrow and the pause overlap from y=16 (10px) to y=64 (16px), and the arrow -
     later in the DOM, same right edge - takes the taps meant for the pause. Both used to
     hold display:flex until the class landed past 64, which is the whole of the collision.

     So fade them with the text over the first half and slide them out of the stage in
     opposite directions - the pause up through the ceiling, the arrows down through the
     floor - where the stage's own overflow:hidden clips them: a clipped box is neither
     painted nor hit-tested. 140px clears the tallest open stage. The `translate` property,
     not `transform`: the arrows carry `transform: none` at this width (mystyle.css:6146) and
     the pause carries none at all, but translate is its own longhand either way, so nothing
     here can quietly drop a transform some other rule is using for centring. */
  .dash-hero-pause{
    opacity: calc(1 - var(--aad-collapse, 0) * 2);
    translate: 0 calc(var(--aad-collapse, 0) * -140px);
  }
  .dash-hero-arrow{
    opacity: calc(1 - var(--aad-collapse, 0) * 2);
    translate: 0 calc(var(--aad-collapse, 0) * 140px);
  }

  /* §9 and §18: the headline should "use the available banner width" and not "break into
     unnecessary short lines", with the font reduced so it can reach toward the right edge.
     `.dash-hero-text` reserves the right 34% for the picture (mystyle.css:6021), which at
     390 leaves the copy 220px and wraps a 36-character headline onto two lines. The picture
     is a full-bleed background here, not a column, so the copy can run over it the way the
     reference draws it. 17px fits the Learn headline on one line at this measure.
     2026-10-05: right 118, not 16 - the pause and the two arrows own the stage's right
     110px (14 + 44 + 8 + 44), and copy running under them was covered by them. Longer
     headlines now wrap to two lines (three for /guide at 360); measured, the block still
     fits the 120px stage at 360 and 390. */
  .dash-hero-text{ right: 118px; max-width: none; }
  .dash-hero-text h1{ font-size: 17px; line-height: 1.16; }

  /* The description comes back. U-mobile-2 hid it at ≤900px (mystyle.css:6320) to buy
     vertical space, and §9 asks for it by name — "description should also be compact and use
     more horizontal space" — so it returns clamped to two lines rather than three, which
     costs ~34px in a stage that has 120px to spend on a headline that is now one line. */
  .dash-hero-text .sub,
  .dash-hero-text p{
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 6px;
    font-size: 12px;
    line-height: 1.4;
  }

  /* §17: the trail is a white floating pill that "visually overlaps the bottom of the page
     banner slightly". That component already exists and is already the right shape
     (mystyle.css:6087) — it was only unpinned at ≤900px, which dropped it below the banner.
     Re-pinned here with a smaller overhang than the desktop's -22px, so it straddles the
     edge rather than sitting over the picture, which is what the ≤900 rule was avoiding.
     The shadow is the one piece §17 asks for that the hairline alone does not give. */
  /* The scrim is left-weighted — solid to 34%, gone by 80% (mystyle.css:6009) — because it
     was cut to fit copy that stopped at the 34% mark. Now that the headline and description
     run the full width, the right half of the text sits on bare photograph and loses its
     contrast. The stops move with the text. It stays what it was, a legibility wash over a
     photo rather than one of the decorative background gradients BRAND.md bans, and the
     right edge still fades so the picture is visible rather than veiled. */
  .dash-hero-scrim{
    background: linear-gradient(90deg, currentColor 0%, currentColor 66%, transparent 100%);
  }

  /* Refinement §4 + marker 4: the crumb FLOATS on the banner's bottom edge, and the white
     band it used to sit in stops existing. Owner, on the marked screenshot: that band should
     not be there at all.

     -22px was half the pill's height, read off the element rather than guessed: the crumbs
     were 36px inside 4px of padding, so the pill was 44px and half of it is 22. That is §4's
     "upper half overlaps the banner, lower half sits over the page", literally.
     🔴 The 2026-09-17 height cut below took the segment to 30px and the padding to 3px, so
     the pill measures 38 (30 + 3 + 3 + the .5px hairline, which computes to 1px a side) and
     this overhang is now 22 of 38 rather than half. The number was left where it is because
     the owner asked for shorter rows, not for a different overlap; it is here so the next
     reader knows it is a held value and not a derived one.

     🔴 This reverses the -10px that stood here. That comment argued a deep overlap merged
     the ink active segment into the ink banner — true for as long as the pill had no
     surface of its own. The surface comes from the .dash-float-crumbs rule below, and ONLY
     from there: mystyle.css:6403 (≤900px, U-mobile-2) strips the pill to `border:0;
     background:transparent; padding:0` for the UNPINNED tablet row, where a boxed pill in a
     white band was noise. Re-pinning it here restored the padding and the shadow but shipped
     without the background and hairline — measured on production 2026-09-17: background
     rgba(0,0,0,0), border 0, grey crumb text bare on the photograph, the dark segment gone
     into the dark banner. The owner read it as "the breadcrumb is behind the banner". It
     painted on top the whole time (elementFromPoint said so); it had nothing to be on top
     WITH. Remove the surface or the 4px and this overlap is unreadable again. */
  .dash-float{ margin: -22px 16px 0; z-index: 6; }
  /* WU-G: the overhang is back, so the headline centres above it again (mystyle.css's ≤900
     rule reset it to 50% for the unpinned tablet row). */
  .dash-hero-text{ top: calc(50% - 11px); }
  /* 🔴 NOT the crumbs-only panel. A detail route has no stage for the row to overhang, so
     the overlap becomes a pull up under the fixed bar — measured at 390, the trail landed at
     y=66, exactly the bar's bottom edge. mystyle.css:6206 already makes this distinction for
     the same reason; M5 flattened it by naming both selectors, and this restores it. */
  .dash-hero-panel--crumbs .dash-float{ margin: 10px 16px 0; }
  /* The pill's surface: the same two tokens as the desktop pill (mystyle.css:6160). See the
     comment above for why the ≤900 rule took it away and this has to give it back. */
  .dash-float-crumbs{
    background: var(--surface); border: .5px solid var(--border);
    box-shadow: 0 4px 14px rgba(16,24,40,.10);
  }

  /* ============================================================
     M6 — the guide detail page (player, actions, header)
     ============================================================ */

  /* 🔴 A detail trail is four parts, and the fourth is the item's own title. On
     /guide/cursor-ai-build-an-app-step-by-step that made the pill 588px wide inside a 390
     viewport: its right half, the active segment, was simply off-screen and clipped by an
     ancestor, which is why the page still reported no horizontal overflow and nothing
     caught it. The trail is chrome, not content — the same title is the H1 immediately
     below — so the active segment truncates rather than the pill overflowing. */
  /* 12px, not 11.5. a11y-behaviour.mjs enforces a floor of "no visible text below 12px" on
     the dashboard chrome, and 11.5 tripped it on /tools. The room comes out of the padding
     and the active segment's cap instead, which is where it should have come from first. */
  /* 3px of padding so the ink active segment is framed in white rather than bleeding into
     the banner behind it — §17's "white floating pill" only reads as one if some white shows
     around the dark crumb. It is 0 at this width today, which is why it did not. (It was 4px
     until the owner's 2026-09-17 height cut below; measured at 390, a 30px segment in 4px of
     padding puts the pill at 40 and in 3px at 38, which is the number that was asked for.) */
  /* Refinement §4: "approximately 8–12px radius, NOT 999px / pill radius". It inherits
     --r-lg (16px, app.css:45) from mystyle.css:6106, which is over the band. 12px is the top
     of the range and keeps it visibly a rounded rectangle rather than a capsule. */
  .dash-float-crumbs{ max-width: 100%; padding: 3px; border-radius: 12px; }

  /* §5: "keep the breadcrumb on ONE row… if all items cannot fit, make the container
     horizontally scrollable, hide the scrollbar, preserve one-row layout."

     🔴 At 320 the trail did not overflow — it SHRANK, which looks worse. The crumbs carry
     flex-shrink:1 and min-width:0, so "Home" squeezed to a 32px box while its text kept
     painting at full width over the neighbour: measured container scrollWidth 280 = clientWidth
     280, i.e. nothing to scroll, with three labels drawn on top of each other. A shrink that
     the box never reports is invisible to the overflow sweep for the same reason a clipped
     one is, which is the trap this project has now hit four times.

     Refusing to shrink is what converts it into an honest overflow the row can scroll. The
     active segment keeps its 46vw cap and ellipsis from the rule above, so the one genuinely
     unbounded part still truncates rather than making the row endless. */
  .dash-float-crumbs{ overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; }
  .dash-float-crumbs::-webkit-scrollbar{ display: none; width: 0; height: 0; }
  .dash-float-crumbs > *{ flex: 0 0 auto; }
  .dash-float-crumbs .crumb{ font-size: 12px; padding: 3px 9px; min-width: 0; }
  /* display:block (not the base inline-flex) because text-overflow cannot ellipsize the
     anonymous text item inside a flex box. align-self keeps it centred: as a flex item it
     would otherwise stretch to the pill's full height and sit flush with the outer edge,
     swallowing the 5px padding that gives the active segment its inset. */
  /* line-height, not display, is what centres this one. The base crumb is an inline-flex box
     with align-items:center (mystyle.css:6161); a block box with line-height:1 puts its 12px
     line at the top of a 24px content box instead, which measured 6.5px above centre while
     the segment was 36px tall. 24px = the box's own content height, so the line box fills it
     and the glyphs sit in the middle. 🔴 If the padding or min-height below moves, so does
     this number. */
  .dash-float-crumbs .crumb-current{
    max-width: 46vw;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: block;
    align-self: center;
    line-height: 24px;
  }

  /* §10 puts the player directly under the trail. The header above it spent 178px at 390 —
     a 28px headline over three lines, plus a meta row and a tag row each carrying 16px type
     — which pushed the video to y=362 on an 844 screen.

     It is compacted rather than hidden or reordered. .gd-title is this page's only H1, and
     the production audit asserts exactly one visible H1 per route; trading that structural
     guarantee for ~85px of scroll would be a bad bargain, and "all desktop content remains
     accessible" is the section's own closing line. */
  .gd-header{ margin-bottom: 10px; }
  .gd-title{ font-size: 19px; line-height: 1.2; margin: 6px 0 0; }
  /* Same 12px floor as the crumbs above — these were 11px and 10px on the first pass, which
     the /tools failure made clear is not a size this project ships. */
  .gd-header-meta{ font-size: 12px; gap: 8px; }
  .gd-tags{ font-size: 12px; gap: 6px; margin-top: 8px; }
  .gd-tags > *{ padding: 3px 8px; font-size: 12px; }

  /* §10 lists Like, Comments and Mark as complete as one row. They wrapped because 205px of
     engage plus a 169px button do not fit 334px of column. */
  /* §15: "make Mark as complete compact on narrow screens", and §22 tests down to 320.
     `nowrap` was right when this was written — the column was 334 and the button 169, so the
     row wrapped when it should not have. Both numbers have since changed: the column is 354
     now that --shell-inset is gone, and the button is 148. Measured at 390 the row needs
     153 + 148 + 8 = 309 of 354 and stays on one line without being forced.

     At 320 it needs those same 309 in 284, and `nowrap` did not make that fit — it made
     `.gd-engage` shrink to a 128px box while its 153px of content kept painting, putting
     "0 comments" over the button. Wrapping is self-regulating: one row wherever it fits,
     two where it does not, and nothing drawn on top of anything. */
  .gd-actions{ flex-wrap: wrap; gap: 8px; align-items: center; }
  .gd-engage{ flex: 1 1 auto; min-width: 0; gap: 8px; }
  .gd-complete-btn{ flex: 0 0 auto; padding: 9px 12px; font-size: 12px; white-space: nowrap; }

  /* --- Refinement §13: the player fills the width -------------------------------------
     The player was never narrow in itself — `.gd-video-wrap` is already `width: 100%`
     (mystyle.css:4014). What capped it was `.dash-scroll`'s own gutter, 18px a side at
     ≤900px (mystyle.css:3255), so at 390 the video measured exactly 354. The gutter is
     right for prose and wrong for the picture, which is what a full-bleed is for.

     Same negative-margin pattern `.ms-audit-chips` already uses (mystyle.css:4682), and
     ONLY on the video: the copy around it keeps the gutter, which is the whole point.

     🔴 `width: auto` is load-bearing. With `width: 100%` left in place the box would still
     resolve to 354 and simply shift 18px left, hanging off the screen on one side and
     leaving a gap on the other. Auto lets the margin box fill the container, so the content
     box becomes 354 + 18 + 18 = 390 — exactly the viewport, which is also why this cannot
     trip the overflow sweep. */
  .gd-detail .gd-video-wrap{ margin-inline: -18px; width: auto; }

  /* §15: the sections under the player are still on desktop spacing — mystyle.css:3962-3972
     sets 28px and has no phone override at all, so ~28px of air sits between every heading
     and the copy above it on a 390 screen. */
  .gd-panel{ padding-top: 16px; }
  .gd-sec-h{ margin: 18px 0 8px; }

  /* ============================================================
     M7 — Skills, Tools, Agents
     ============================================================ */

  /* 🔴 The hub action tabs were off the screen entirely. `.dash-float` is a nowrap row
     holding the crumb pill AND the swept-in .page-head controls, so on /agents at 390 the
     actions box measured 644px starting at x=227 — right edge 871 in a 390 viewport — and
     `.agents-view-pills` began at x=527. "Agents", "Editor's picks" and "Templates" were not
     merely cramped, they were unreachable. An ancestor clips the row, so the page reported
     no horizontal overflow and nothing failed. Same shape as the M6 crumb pill: a clipped
     overflow is invisible to an overflow test.

     §27's hierarchy puts the breadcrumb and the tab row on separate lines, and §13 says the
     action tabs "may horizontally scroll on mobile instead of wrapping" — so the row becomes
     a column and the tab strip scrolls. */
  .dash-float{ flex-direction: column; align-items: flex-start; gap: 8px; }
  .dash-float-actions{
    width: 100%;
    max-width: 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .dash-float-actions::-webkit-scrollbar{ width: 0; height: 0; }
  /* The swept-in strips must not re-wrap inside the scroller, or the scroll has nothing to
     scroll and the tabs stack instead. */
  .dash-float-actions .agents-view-pills,
  .dash-float-actions .intg-tabs,
  .dash-float-actions .row{ flex-wrap: nowrap; }
  .dash-float-actions > *{ flex: 0 0 auto; }

  /* --- Refinement §7 + §8: the rows tighten -------------------------------------------
     §8 asks for a 36–42px search field; it measured 51.5 at 320. The height is padding, not
     a tap floor — 13px of block padding around a 16px line. 10px lands it at ~40px, inside
     the band, and the field is still far above 44px of tappable area because it is a full
     width input.

     🔴 The 16px font-size is NOT touched. mystyle.css:635 sets it deliberately: anything
     smaller and iOS zooms the page when the field takes focus. That is also why the room
     comes out of the padding rather than the type.

     The selector has to carry mystyle.css:3399's weight — `.hub-controls.intg-filters
     .hub-search--strip .hub-search-input` is (0,4,0) and a bare `.hub-search-input` loses to
     it however late it is declared. `.intg-search` is a different field (integrations) that
     sets a flat height rather than padding, so it is named separately. */
  .hub-controls.intg-filters .hub-search--strip .hub-search-input{
    padding-top: 8px; padding-bottom: 8px;
  }
  .intg-search{ height: 40px; }

  /* §7's rhythm. The wrap's own 6px (app.css:1937) plus the row gap put ~19px between the
     crumb and the strip and ~15px between the strip and the search, measured at 320. The
     strip's chrome is transparent on the hubs already (mystyle.css:6130), so its padding is
     buying nothing here. */
  .dash-float-actions .intg-tabwrap{ padding: 0; }
  .dash-float{ gap: 10px; }

  /* §6's 30–36px, and §4's "compact" breadcrumb. Both controls sat at exactly 44px, from
     two different tap-floor rules: `mystyle.css:7014` for the crumbs, and `.intg-tab,
     .nh-pill{min-height:44px}` in app.css under its own `@media (max-width:900px)` for the
     filter chips. The hub strip is NOT inside `.dash-float-actions` — it renders under
     `.hub-controls.intg-filters` in `.dash-subhead` — so the floated-row rules at
     mystyle.css:7017 never applied to it and are not what has to be beaten here.

     🔴 This lowers a real accessibility floor, on purpose. The owner was asked directly
     (2026-09-17), told that 36px is 8px under the 44px touch-target guideline on controls
     people tap repeatedly, and chose the smaller size for both rows anyway. Later the same
     day, having seen it on the phone, the owner asked for a further cut to 30px — so these
     are 14px under the guideline now, and the strip's arrows come down to 36px with them.
     Recording it because a future reader will otherwise assume it was an oversight and
     "fix" it. The scoping is deliberate too: only these two rows drop: the compare selects
     and the standalone view pills keep 44px, because nothing asked for them to shrink.

     Same specificity as the rules being beaten, later file, so no !important. */
  .dash-float-crumbs .crumb{ min-height: 30px; }
  .intg-tab,
  .dash-float-actions .btn{ min-height: 30px; }
  /* 30px is under what the chip's own 8px padding round a 20px line already builds, so the
     padding has to come down with the floor or the number is decorative. 5+20+5 = 30. */
  .intg-tab{ padding-top: 5px; padding-bottom: 5px; }
  /* The strip's corner arrows, 44px square from app.css:2077. They are the tallest thing in
     the strip, so the strip's height is theirs: they set it, the 30px chips do not. */
  /* 🔴 SCOPED to the hub strips on 2026-09-23. This was a bare .intg-chev, and the
     hero's chip arrows also carry .intg-chev (mystyle.js) -- so it beat
     .ms-chip-chev{width:44px} on the cascade (same specificity, this file loads last) and
     silently rendered them at 36px too. That was collateral: 3a704b0 records the owner's
     36px decision, and it was about the six HUB strips, which is what this rule was written
     for. The owner was shown both comments and ruled 44 in the hero, 36 on the hubs.
     Still (0,2,0), so it still beats app.css:2077's 44px floor for the hub strips and
     e2e.mjs:2126-2138 still measures 36x36 there. */
  .intg-tabwrap .intg-chev{ width: 36px; height: 36px; min-width: 36px; min-height: 36px; }
  /* 4px round a 36px arrow plus the hairline = a 45px strip, down from 58. Scoped by weight
     to the subhead's strip only — the floated copy is already at 0 padding above (0,2,0). */
  .intg-tabwrap{ padding: 4px; }

  /* §12: "reduce image/logo container height significantly", "do not allow huge empty logo
     panels". Tool cards inherit `.aad-card-media`'s 3/2 photo aspect, which at a 162px card
     is a 95px box — holding a logo 38px tall. Fifty-seven of those pixels were empty. The
     panel drops to 5/2 (65px) and the mark grows its share of it, so the logo ends the same
     physical size in a box that is a third shorter. Photo cards on Skills and Agents keep
     3/2: this is scoped to the --logo variant, which is the one §12 is about.

     The two-up grid §11–§13 all ask for was already in place at every phone width — verified
     at 390 AND at 700, where auto-fill resolves to two columns on its own. Nothing to do. */
  .aad-card-media--logo{ aspect-ratio: 5 / 2; }
  /* 66%, sized so the mark keeps the ~38px height it had in the taller box. The empty space
     is what this unit is removing, not the logo. */
  .aad-card-media--logo > img{ width: 66%; height: 66%; }

  /* ============================================================
     M8 — the weekly brief and digest
     ============================================================ */

  /* §14 asks for the editorial layout "scaled and reorganized", explicitly NOT rebuilt as
     generic cards — and measured against the committed edition fixture at 390, it already is
     exactly that: the lead runs full width with its own headline, deck, picture, credit,
     "why it matters" and sources; the workflows keep their numbered steps; the supporting
     stories, the tiles and the jobs section are all present. Two-column-when-space-permits
     is already handled too — at 700 `.wde-tiles` resolves to 316px x 2 and `.wde-steps` to
     three tracks, on its own, while 390 keeps one. There was nothing to rebuild and nothing
     to force, so this unit does not pretend otherwise.

     What §14 DID ask for and was missing is one line: "avoid extremely tiny desktop text."
     Three editorial elements ship at 11px — the story eyebrow, the numbered step markers and
     the workflow tool chips — which is under the 12px floor a11y-behaviour enforces on the
     dashboard chrome and which M6 established as this build's minimum. They are 11px at
     every width, so this is deliberately phone-scoped: the desktop's editorial type is the
     owner's and is not this unit's to restyle. */
  .wde-eyebrow{ font-size: 12px; }
  .wde-step-n{ font-size: 12px; }
  .wde-tools li{ font-size: 12px; }

  /* ============================================================
     M9 — the footer, and the sweep
     ============================================================ */

  /* The footer needed nothing, and that is a measurement rather than an assumption. §16's
     real test is "do not remove desktop footer links": counted at 1200 and at 390, both
     render the same 18 links and the same six groups — aiagentsdaily, Learn, Build, Account,
     Company, Legal — with the newsletter strip and its input visible on the phone. It is
     already the compact two-column reflow §16 asks for (U-mobile-3's work, 579px in a hub),
     carries no text under 12px and nothing escapes the viewport. Three columns at 390 would
     put ~110px under each heading, which is how you turn a link list into a word puzzle.

     The sweep: all 33 routes in the harness registry, at 390.

     🔴 ZERO routes have unreachable overflow. That is the headline, because it is the bug
     this build hit twice — the M6 crumb pill and the M7 action tabs — and both times the
     document reported no overflow because an ancestor clipped it. The sweep checks each
     element that extends past the viewport for a genuinely scrollable ancestor, so a chip
     strip you can swipe passes and a clipped strip you cannot reach fails. Every remaining
     off-viewport element on every route is inside a real scroller.

     What it did find was text under 12px on two routes, which this closes. Two of them are
     debts from this build's own earlier units and were recorded as such: .ms-build-desc at
     11px and .ms-build-ec-tag at 9px, both set by M4 before M6 established the floor. The
     rest are 11px at every width and are raised on the phone only — the desktop's type is
     not this sweep's to change. */
  .ms-hero-scroll-cue{ font-size: 12px; }
  .ms-build-desc{ font-size: 12px; }
  .ms-build-ec-tag{ font-size: 12px; }
  .ms-vcard-pill{ font-size: 12px; }
  .cmpm-cl{ font-size: 12px; }
  .aad-vs-x{ font-size: 12px; }
  /* The drawer's group headings, set by M2. The sweep walks main and footer, so a component
     appended to <body> never appeared in it — found by re-reading M2 rather than by the
     tool, which is worth remembering about the tool. */
  .m-lbl{ font-size: 12px; }

  /* ============================================================
     M10 — QA: what four widths and a wider selector turned up
     ============================================================ */

  /* 🔴 The bell and the avatar were off the screen on /playground. Found at 375, 390 and 430
     (not at 767), by a sweep that walks `body *` rather than `main *` — M9's did not look
     here, which is exactly the blind spot M9 recorded about itself.

     Cause, and it is M1's: before M1 the whole bar was `display: none` in dashboard-mode, so
     nothing in it could overflow. With the bar back, the trail that lives inside it competes
     for 366px. On most dash routes that trail is hidden because the panel below carries the
     pill instead — but /playground, /settings and /notifications have NO panel pill, so the
     nav copy is the only breadcrumb they have and cannot simply be hidden. /playground's is
     the longest ("Home Workspace Dashboard", 194px), which pushed .nav-right to x=364 and its
     avatar's right edge to 448 in a 390 viewport: notifications and account unreachable.

     Fix follows a pattern the codebase already uses — app.css:679 drops the middle of the
     trail under 640px in the dash bar — so here the middle crumbs and their separators go,
     leaving Home and the current page, which is the pair that carries the meaning. The
     min-width/overflow pair underneath is the backstop: whatever the current label turns out
     to be, it can shrink, and .nav-right cannot be pushed out again. */
  .nav.nav--floating .nav-inner{ min-width: 0; }
  .nav.nav--floating .nav-brand-group{ min-width: 0; flex: 0 1 auto; }
  .nav.nav--floating .dash-crumbs{ min-width: 0; overflow: hidden; }
  .nav.nav--floating .dash-crumbs .crumb:not(.crumb-home):not(.crumb-current),
  .nav.nav--floating .dash-crumbs .crumb-sep{ display: none; }
  .nav.nav--floating .nav-right{ flex: 0 0 auto; }

  /* 10.5px on /news, in the story source line. M9's sweep missed it for the same reason it
     missed the drawer: `.sxh-src` renders outside main. */
  .sxh-src{ font-size: 12px; }

  /* ============================================================
     M11 — /playground (the rebuilt bare route only)
     ============================================================
     Scope note: every ?agent=<id> path still renders agent-builder.js and is not
     touched by anything in this block. The selectors below all sit under
     `body.route-playground` and a `.pg-*` class, neither of which that surface has.

     Two rules carry the layer. First, the playground becomes its own fixed layer
     under the bar: `body.dashboard-mode #app` and `.shell` are 100vh
     (mystyle.css:2915/2924), and a composer pinned to the bottom of a 100vh box
     sits UNDER Safari's toolbar. `inset` with no height tracks the visible
     viewport instead, which is the only honest fix here. z-index 20 stays below
     the bar (101) and below .m-fab (112). */
  body.route-playground #pg-mount{ display: block; }
  body.route-playground .pg{
    position: fixed;
    inset: var(--dash-bar-h) 0 0 0;
    z-index: 20;
    display: flex;
    border: 0; border-radius: 0;
    padding-bottom: 0;
  }

  /* Second: .m-fab (and .m-close in an installed app) is SITE NAVIGATION and keeps
     its corner; the docked box steps aside for it instead, so the two never overlap
     however tall the textarea grows. */
  body.route-playground .pg-dock .pg-composer{ padding-right: calc(76px + env(safe-area-inset-right)); }
  body.route-playground:has(#m-close) .pg-dock .pg-composer{ padding-left: calc(76px + env(safe-area-inset-left)); }

  /* 16px or iOS zooms the page on focus. */
  body.route-playground .pg-ta{ font-size: 16px; }
  body.route-playground .pg-composer{ padding: 10px 14px calc(12px + env(safe-area-inset-bottom)); }
  body.route-playground .pg-gen{ padding: 16px; }

  /* FULL SCREEN covers the bar too; Exit full screen under the box is the way out. */
  body.route-playground .pg.pg--full{ inset: 0; z-index: 150; }

  /* Popovers cannot exceed the viewport at 390. */
  body.route-playground .pg-pop{ max-width: calc(100vw - 24px); }

  /* ============================================================
     M11 - the footer's height at 390, and where the savings are
     ============================================================ */

  /* MEASURED, not assumed: at 390 the footer is 703px on / and 725px in a hub, against a
     620px budget and an 80%-of-viewport ceiling (675px at 844). Composition on /:
     50px own padding + 552px .cols + 20px margin + 80px .foot-bottom.

     Root cause is NOT the column count. M9 measured 579px here and app.css:1258 already buys
     the second track; 1cfae1d then added five links to Learn and Build and nothing revisited
     the height. Three columns stays refused for M9's reason - ~110px under each heading turns
     a link list into a word puzzle.

     The link rhythm is deliberately untouched. Footer links are 27px tall, already under the
     44px tap floor this build enforces on .intg-chev, so tightening `.foot ul` gap to buy
     pixels would trade an accessibility floor for a test threshold. Every saving below is
     chrome, or free.

     THE FREE 59px - row pairing. .cols is a 2-track grid, so each row costs the TALLER of its
     two sections. DOM order pairs Learn (8 links, 258px) with the 3-link brand column (112px)
     and burns 146px of slack:
         AI Agents Daily 112 | Learn   258  -> 258
         Build           171 | Account 112  -> 171
         Company          83 | Legal    83  ->  83    = 512 + 40 gap = 552
     Pairing by size instead costs 112 + 258 + 83 = 453. `order` does that in CSS, so
     index.html is untouched and no link changes section - Account rises beside the brand
     column, Learn and Build pair up. Both are defensible groupings.
     e2e's `columns === 2` and `sections === 6` still hold: order changes neither track count
     nor child count. */
  .foot .cols > :nth-child(1){ order: 1; }  /* AI Agents Daily - 3 links */
  .foot .cols > :nth-child(4){ order: 2; }  /* Account         - 3 */
  .foot .cols > :nth-child(2){ order: 3; }  /* Learn           - 8 */
  .foot .cols > :nth-child(3){ order: 4; }  /* Build           - 5 */
  .foot .cols > :nth-child(5){ order: 5; }  /* Company         - 3 */
  .foot .cols > :nth-child(6){ order: 6; }  /* Legal           - 2 */

  /* Chrome, not content: the row gap and the heading's own bottom margin. */
  .foot .cols{ row-gap: 12px; }
  .foot .foot-h{ margin-bottom: 6px; }

  /* .foot-bottom is 80px tall, of which 20px is its own top padding, plus a 20px margin. */
  .foot-bottom{ margin-top: 12px; padding-top: 14px; }

  /* A hub footer is inset by mystyle.css:2970, which sits OUTSIDE any media query and so
     reinstates 40/32 over app.css:1257's phone padding - that is the entire 22px difference
     between / at 703 and /agents at 725. Same selector, and mobile.css loads last. */
  body.dashboard-mode .dash-scroll > .foot{ padding: 28px 0 22px; }


  /* ---------- the edition surfaces on a phone (U-daily) ----------
     /briefings, /weekly and the standalone all paint the same markup, so one set of rules
     covers the three. Everything here sits inside this file's single 767px query.
     tools/build-digest.mjs copies the rules between the two edition-phone markers into
     the standalone page, so keep every edition rule between them. */
  /* @edition-phone:start */
  /* One column, picture first, copy under it: no text laid over a photograph. */
  .wke-hero,
  .wke-hero--brand{ grid-template-columns: 1fr; min-height: 0; }
  .wke-hero-media{ order: -1; min-height: 0; aspect-ratio: 16 / 9; }
  .wke-hero-copy,
  .wke-hero--brand .wke-hero-copy{ padding: 20px 16px 22px; gap: 12px; }
  .wke-hero-h{ font-size: 28px; }
  .wke-hero-tag{ font-size: 34px; }
  .wke-hero-dek{ font-size: 15px; }
  .wke-crumbs{ overflow-x: auto; white-space: nowrap; scrollbar-width: none; }
  .wke-crumbs::-webkit-scrollbar{ display: none; }
  .wke-crumbs ol{ flex-wrap: nowrap; }
  /* The weekly's bleed hero keeps its picture behind the copy on a phone: the absolute
     media must not take the one-column order / 16:9 box above, and the scrim runs top-down. */
  .wke-hero--bleed{ min-height: 300px; }
  .wke-hero--bleed .wke-hero-media{ order: 0; aspect-ratio: auto; }
  .wke-hero--bleed .wke-hero-media::after{
    background: linear-gradient(180deg, rgba(28,28,31,.94) 0%, rgba(28,28,31,.76) 50%, rgba(28,28,31,.22) 100%);
  }
  .wke-hero--bleed .wke-hero-copy{ padding: 20px 16px 44px; max-width: none; }
  /* edition.css sets the daily's bleed copy box at (0,3,0); the phone padding must match it. */
  .wke-hero--brand.wke-hero--bleed .wke-hero-copy{ padding: 20px 16px 44px; max-width: none; }
  /* The weekly's hero on a phone (owner, 2026-09-30): the eyebrow and the headline only,
     set at the bottom of the picture, so the scrim darkens from the bottom up. */
  .wke-hero--bleed:not(.wke-hero--brand) .wke-hero-dek{ display: none; }
  .wke-hero--bleed:not(.wke-hero--brand) .wke-hero-copy{ min-height: 300px; display: flex; flex-direction: column; justify-content: flex-end; }
  .wke-hero--bleed:not(.wke-hero--brand) .wke-hero-media::after{
    background: linear-gradient(0deg, rgba(28,28,31,.94) 0%, rgba(28,28,31,.7) 45%, rgba(28,28,31,.15) 100%);
  }
  /* Workflow section: its h2 is enough on a phone; the eyebrow, the lead and the intro go. */
  .wke-wf-intro{ display: none; }
  .wke-crumbs.wke-crumbs--float{ margin: -18px 0 18px 12px; max-width: calc(100% - 24px); }
  .wke-sech{ flex-wrap: wrap; align-items: flex-end; }
  .wke-minis{ grid-template-columns: 1fr; gap: 12px; }
  .wke-story{ padding: 14px 14px 16px; }
  .wke-story .wke-card-fig{ margin: -14px -14px 14px; }   /* follows the card's phone padding */
  .wke-mini.wke-story{ padding: 12px 12px 14px; }
  .wke-mini.wke-story .wke-card-fig{ margin: -12px -12px 12px; }
  .wke-close{ padding: 24px 16px; }
  .wke-assign{ padding: 18px 16px; }
  .wke-tut-card{ grid-template-columns: 1fr; gap: 16px; padding: 12px; }
  .wke-tut-copy{ padding: 0 4px 6px; }
  .wke-head{ align-items: flex-start; gap: 8px; padding: 16px 2px 12px; }
  /* The byline drops under the masthead instead of squeezing it. */
  .wke-head{ flex-wrap: wrap; }
  .wke-by-photo{ width: 48px; height: 48px; }
  /* The heading plus four circles wrap at 390; start-aligned, the heading never hangs alone at the right. */
  .wke-share{ justify-content: flex-start; }
  /* The signed-out comments prompt stacks; the textarea's 16px comes from mystyle.css .cmty-input at <=768px. */
  .wke-cmts-in--out{ flex-direction: column; align-items: flex-start; }
  .wke-mast{ font-size: 34px; }
  .wke-grid{ grid-template-columns: 1fr; }
  /* The open card stacks on a phone: picture back on top, bled to the card's edges. */
  .wke-story[data-open="true"]{ display: block; }
  .wke-story[data-open="true"] .wke-card-fig{
    margin: -14px -14px 14px; border-radius: calc(var(--r-md) - 1px) calc(var(--r-md) - 1px) 0 0;
  }
  .wke-card-h{ font-size: 21px; }
  .wke-sech h3{ font-size: 18px; }

  /* The diagram keeps its desktop shape on a phone (owner, 2026-09-30): the same grids,
     cards and wires as edition.css, on narrower tracks, smaller gaps and 12px type, with
     every card's description line hidden so only titles, icons and the short labels stay.
     The node placement in edition.css is reused as is; only the templates change here. */
  .wfg-stage{ padding: 14px 10px; }
  .wfg-title{ font-size: 24px; }
  .wfg-sub{ font-size: 16px; margin-bottom: 20px; }
  .wfg-hint{ font-size: 13px; }
  .wfg-node{ padding: 8px 8px 7px; }
  .wfg-node--hub{ padding: 9px 8px 8px; }
  .wfg-ico{ width: 18px; height: 18px; margin-bottom: 6px; }
  .wfg-node-row{ gap: 6px; margin-bottom: 6px; }
  .wfg-node-h,
  .wfg-node--hub p.wfg-node-h{ font-size: 12px; overflow-wrap: anywhere; }
  .wfg-node p, .wfg-check li, .wfg-win-rows li, .wfg-chip, .wfg-fork-l, .wfg-metric{ font-size: 12px; overflow-wrap: anywhere; }
  .wfg-graph .wfg-node > p:not(.wfg-node-h){ display: none; }   /* descs and the lane sub-title */
  .wfg-graph--lanes .wfg-node--hub .wfg-check{ display: none; }   /* the same tools are the hint line */
  .wfg-check{ margin-top: 6px; gap: 4px; }
  .wfg-check li .wfg-check-ico{ width: 14px; height: 14px; }
  .wfg-or{ width: 22px; height: 22px; }
  /* lanes: start, wires, the two lanes with the OR, wires, hub, wire, finish */
  .wfg-graph{
    grid-template-columns: minmax(0,.9fr) 8px minmax(0,1fr) 8px minmax(0,.95fr) 8px minmax(0,.9fr);
    grid-template-rows: 1fr 22px 1fr;
  }
  .wfg-graph > .wfg-node:not(.wfg-node--hub){ padding-left: 6px; padding-right: 6px; }
  .wfg-graph:not(.wfg-graph--fan):not(.wfg-graph--gate):not(.wfg-graph--inputs) .wfg-node-h{ hyphens: auto; overflow-wrap: normal; }
  .wfg-graph--nohub{ grid-template-columns: minmax(0,.9fr) 10px minmax(0,1fr) 10px minmax(0,.9fr); }
  /* fan: three narrow branches under one job */
  .wfg-graph.wfg-graph--fan{ grid-template-rows: auto 24px auto 24px auto; }
  .wfg-graph--fan .wfg-node--br{ margin: 0 3px; }
  .wfg-chip{ padding: 5px 8px; }
  .wfg-pipe-box{ width: 30px; }
  /* gate: input, hub with the fork under it, output */
  .wfg-graph.wfg-graph--gate{
    grid-template-columns: minmax(0,.85fr) 12px minmax(0,1.7fr) 12px minmax(0,.85fr);
    row-gap: 12px;
  }
  .wfg-fork::before{ top: -12px; height: 12px; }
  .wfg-fork-opts{ gap: 6px; }
  .wfg-fork-l{ gap: 6px; line-height: 16px; }
  .wfg-fork-l::before, .wfg-fork-l::after{ margin-top: 8px; height: 8px; min-width: 8px; }
  /* inputs: three sources, the tool, the result and the compare card under it */
  .wfg-graph.wfg-graph--inputs{
    grid-template-columns: minmax(0,.9fr) 10px minmax(0,1.3fr) 10px minmax(0,1fr);
    row-gap: 12px;
  }
  /* Rows sized by the three source cards, not stretched to the tall compare card: the
     compare card moves to its own row under the whole diagram, full width, its arrow
     coming down from the tool. The result card sits at its own height beside the tool. */
  .wfg-graph.wfg-graph--inputs{ grid-template-rows: auto auto auto auto; }
  .wfg-graph--inputs .wfg-node--out{ grid-row: 1/4; align-self: center; }
  .wfg-graph--inputs .wfg-w--iout{ grid-row: 1/4; }
  .wfg-graph--inputs .wfg-node--compare{ grid-column: 1/-1; grid-row: 4; }
  .wfg-graph--inputs .wfg-node--compare::before{ top: -12px; height: 11px; left: 48.5%; }
  .wfg-graph--inputs .wfg-node--compare::after{ top: -6px; left: 48.5%; }
  .wfg-win{ margin-top: 8px; }
  .wfg-win-rows{ padding: 6px 8px; gap: 5px; }
  .wfg-metric{ padding: 2px 4px; gap: 4px; }
  /* The numbered strip turns vertical: numeral left (its swipe left-aligned under it),
     title and line to its right. */
  .wfg-stepwrap{ margin-top: 20px; padding-top: 18px; }
  .wfg-steps{ grid-template-columns: 1fr; gap: 14px; text-align: left; }
  .wfg-step{ display: grid; grid-template-columns: 44px minmax(0,1fr); column-gap: 12px; }
  .wfg-step--i{ grid-template-columns: 72px minmax(0,1fr); }   /* icon + numeral */
  .wfg-step-top{ grid-row: 1 / span 2; justify-content: flex-start; gap: 6px; margin: 0; }
  .wfg-step-i{ width: 22px; height: 22px; }
  .wfg-step-n{ margin: 0; font-size: 24px; }
  .wfg-step-n::after{ margin: 4px 0 0; }
  .wfg-step > p{ grid-column: 2; }
  .wfg-step-h{ padding-top: 5px; }
  .wfg-step:not(:last-child)::after{ display: none; }
  .wfg-prompt{ grid-template-columns: 1fr; }
  .wfg-copy{ justify-self: start; }
  .wke-tut-h{ font-size: 22px; }
  /* @edition-phone:end */

  /* Install bar (WU-J): full width under the flush 58px phone bar every phone view keeps. */
  .inst-bar, body.dashboard-mode .inst-bar{
    top: calc(58px + env(safe-area-inset-top) + 10px); left: 12px; right: 12px; max-width: none;
    transform: translateY(-200%); min-height: 52px; padding: 4px 4px 4px 14px; font-size: 13px;
  }
  .inst-bar.is-in, body.dashboard-mode .inst-bar.is-in{ transform: none; }
  .inst-bar-text{ flex: 1; min-width: 0; white-space: normal; }
  .inst-bar-x{ width: 44px; height: 44px; }
  .inst-dlg-card{ padding: 22px 20px 18px; }

  /* /editors-choice MCP shortlist (2026-10-07): one column on a phone. */
  .mcp-picks{ grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .mcp-picks-sec{ margin-top: 28px; }

  /* /mcp cards (2026-10-02): at two-up (≤600px) the Copy install button is the full foot
     width, which squeezed the downloads metric to 0px. The metric takes its own line. */
  @media (max-width: 600px){
    .aad-card-foot--stack{ flex-wrap: wrap; row-gap: 6px; }
    .aad-card-foot--stack .aad-card-metric{ flex: 1 1 100%; }
  }

  @media (prefers-reduced-motion: reduce){
  }
}

/* Home redesign (brief feature 2, 2026-10-11): one column; the brief's mobile order is the
   DOM order. The card CTAs keep a 44px touch target. */
@media (max-width:767px){
  .ms-cat, .ms-meth{ grid-template-columns: 1fr; gap: 12px; }
  .ms-track{ gap: 12px; }
  .ms-track > *{ flex-basis: 84%; }
  .ms-track-chev{ display: none; }
  /* The journey swipes on a phone, like the job paths. */
  .ms-journey.ms-track{ display: flex; overflow-x: auto; margin: 0 calc(var(--ms-pad-x) * -1); padding: 2px var(--ms-pad-x) 4px; scroll-snap-type: x mandatory; gap: 12px; }
  .ms-dashes.ms-journey-dashes{ display: flex; }
  .ms-journey-section, .ms-jp-section, .ms-meth-section, .ms-cat-section, .ms-fresh-section{ padding-top: 48px; }
  /* Owner 2026-10-12 (goal A4): no empty band under the hero's Scroll cue; the space moves
     into the spacer above the headline instead (.ms-hero-gap below). */
  .ms-ex-section{ padding-top: 16px; }
  .ms-ex-cols{ grid-template-columns: 1fr; }
  .ms-journey-media{ aspect-ratio: 16 / 8; }
  .ms-jp-cta, .ms-cat-cta, .ms-cat-cta2{ display: inline-flex; align-items: center; min-height: 44px; }
  /* Catalogue bento on a phone (2026-10-12): one column, the Agents picture above its copy. */
  .ms-cat-card--ec{ min-height: 300px; }
  .ms-cat-card--agent{ grid-column: auto; flex-direction: column; gap: 14px; }
  .ms-cat-card--agent .ms-cat-media{ order: 0; flex: none; aspect-ratio: 16 / 9; }
  .ms-meth-art, .ms-meth-head{ height: 128px; }
  /* Owner, 2026-10-12 (phone screenshot): each headline line is ONE line, so the type scales
     with the width (6.6vw = ~26px at 390); the support copy is about half its height. */
  .ms-hero .ms-hero-display{ font-size: 6.6vw; }
  .ms-hero-l1, .ms-hero-l2{ white-space: nowrap; }
  /* Owner 2026-10-12 (goal A1): line 2 bold like line 1; it keeps its grey, the second tone. */
  .ms-hero-l2{ font-weight: 700; letter-spacing: -0.03em; }
  .ms-hero .ms-hero-cycling{ font-size: 13px; margin-top: 10px; }
  /* Owner, 2026-10-12: "remove space and bring down the top elements". The spacer moves to the
     top of the column, so the copy sits on the audit pill and the film shows above it. */
  .ms-hero-gap{ order: -1; }
  /* Owner, 2026-10-12 (A3): the 2x2 hairline grid above stays; four across cannot hold a
     one-line "117 MCP servers" (~96px against 70-97px cells). */
  .ms-hero-stat-label{ font-size: 12px; }   /* the file's 12px phone floor (e2e) */
  /* Owner, 2026-10-12, second phone pass (320 screenshot, marks 1-5). */
  /* 1. Support copy as short as the 12px floor allows: the full measure, two lines at 390. */
  .ms-hero .ms-hero-cycling{ font-size: 12px; max-width: none; letter-spacing: -0.005em; }
  /* Owner 2026-10-12 (goal A2, copy approved): the short line, one line at 12px down to 320. */
  .ms-hero .ms-hero-cycling .ms-tryit--desk{ display: none; }
  .ms-hero .ms-hero-cycling .ms-tryit--phone{ display: inline; }
  /* 2. The audit pill at ~44px, down from ~60 (44 is the tap floor; the input stays 16px so
     iOS does not zoom on focus). */
  .ms-hero-form{ margin-top: 14px; padding: 3px 3px 3px 16px; }
  .ms-hero-form input{ padding: 7px 0; }
  .ms-hero-form .ms-btn{ padding: 9px 14px; font-size: 14px; }
  /* 3. Chips and their arrows ~40% smaller, so more job titles show at once. */
  .ms-hero .ms-chipwrap{ --ms-chev-w: 28px; }
  .ms-hero .ms-chip-chev{ min-width: 28px; min-height: 28px; font-size: 14px; }   /* app.css .intg-chev carries a 44px floor */
  .ms-audit-chips button{ padding: 4px 9px; }
  .ms-swipe-label + .ms-chipwrap{ margin-top: 4px; }
  .ms-chipwrap{ margin-top: 12px; }
  /* 4. See Example Result at about half its size. */
  .ms-hero-ctas{ margin-top: 12px; }
  .ms-hero-ctas .ms-btn--inhero{ min-height: 0; padding: 7px 14px; font-size: 13px; }
  .ms-hero-trust{ margin-top: 8px; font-size: 12px; }
  /* 5. Stats card, owner 2026-10-12 (A3): each cell is ONE line, icon then "47 tools", the
     label inline with the figure; the tools cell reads "tools" only (the freshness line
     stays on desktop). */
  .ms-hero-stats{ margin-top: 14px; }
  .ms-hero-stat{ flex-direction: row; justify-content: flex-start; align-items: center; gap: 8px; padding: 8px clamp(6px, 2vw, 10px); }
  .ms-hero-stat-txt{ flex-direction: row; align-items: baseline; gap: 4px; }
  .ms-hero-stat-ico svg{ width: 18px; height: 18px; }   /* owner 2026-10-12: line art ~20% bigger, grey from mystyle.css */
  .ms-hero-stat-num{ font-size: 14px; }                  /* 1px smaller; the 12px label is the floor */
  .ms-hero-stat-label{ white-space: nowrap; line-height: 1.15; }
  .ms-hero-stat-sub{ display: none; }
  /* The CTA now follows the chips, so the capped spacer sits between the copy and the audit
     pill; shorter, so the primary action is closer to the headline. */
  .ms-hero-gap{ height: min(124px, 15.5vh); }   /* owner 2026-10-12 (A4): the top elements sit lower */
  .ms-hero-ctas{ margin-top: 14px; }

  /* Profession page (F4): the ring card drops under the answer and its CTA. */
  .wr-hero{ grid-template-columns: minmax(0,1fr); }
  .wr-answer{ font-size: 19px; }
  .wr-cards{ grid-template-columns: repeat(2, minmax(0,1fr)); gap: 10px; }
  .wr-cards > .card{ padding: 12px 14px; font-size: 14px; }
  .wr-weeks{ grid-template-columns: minmax(0,1fr); }
  .wr-sec{ margin-top: 28px; }

  /* Agent loader: a smaller sheet and less air on a phone. */
  .al-root{ min-height: 50vh; padding: 32px 16px; }
  .al-sheet{ max-width: 300px; }
  .al-title{ font-size: 16px; }

  /* Audit result (F3): a task row is two lines - name and status, then meter and number. */
  .audit-report .ar-row{ grid-template-columns: minmax(0,1fr) auto; row-gap: 6px; column-gap: 12px; }
  .audit-report .ar-row .badge{ justify-self: end; }
}

/* Owner 2026-10-14 (phone screenshots of the audit result): buttons were too tall and too
   round. On phones a button is a shorter, wider rounded rectangle: 12px corners instead of a
   full pill, 38-40px tall (still well above the 24px WCAG 2.2 target minimum), wider sides.
   The score toggle sits on one line instead of wrapping into two tall circles. */
@media (max-width: 767px){
  .btn{ border-radius: 12px; padding: 8px 20px; }
  .btn-sm{ padding: 7px 16px; }
  .agents-view-pill,
  .dash-float-actions .agents-view-pill{ min-height: 38px; border-radius: 12px; padding: 0 20px; }
  .audit-report .ar-toggle{ display: flex; width: 100%; }
  .audit-report .ar-tpill{ flex: 1 1 0; min-width: 0; white-space: nowrap; border-radius: 12px; padding: 9px 8px; font-size: 12.5px; }
  .audit-report .ar-card-lg .btn.btn-outline{ border-radius: 12px; padding: 10px 20px; }
}

/* Owner 2026-10-14 (phone goals, F9c). Phones only, like every rule in this file. */
@media (max-width: 767px){
  /* 1. The banner is a STATIC panel on a phone (owner 2026-10-14): no slideshow, no arrows,
     no Pause, and no shrink on scroll (dash-hero.js skips the collapse at this width), so the
     crumb pill always floats on its bottom edge. */
  .dash-hero-arrow,
  .dash-hero-pause{ display: none; }

  /* 2. A shorter panel: 120 -> 84 on every hub, the story pages included (their story
     moves into .sxh-card below the bar, dash-hero.js). The title is one line in a regular
     weight near the top, so the crumb pill's 22px overhang never sits on the words. */
  .dash-hero-stage,
  .dash-hero-panel--story .dash-hero-stage,
  .dash-hero-panel--story .dash-hero-stage:has(.sxh-credit){ --aad-stage-open: 84px; }
  .dash-hero-panel .dash-hero-text{ top: 12px; bottom: auto; right: 16px; transform: none; }
  .dash-hero-panel .dash-hero-text h1{
    font-size: 15px; font-weight: 400; line-height: 1.25;
    display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .dash-hero-text .sub,
  .dash-hero-text p{ -webkit-line-clamp: 1; line-clamp: 1; margin-top: 4px; }
  /* A detail page's badges (/tool/:id "Coding", /compare/:id "Claude vs ChatGPT") share the
     title's line, so badge, title and description all fit above the crumb pill instead of
     the description sliding under it. The title's inline margin (hubHead) has to yield. */
  .dash-hero-panel .dash-hero-text > div{ display: flex; flex-wrap: wrap; align-items: center; column-gap: 8px; }
  .dash-hero-panel .dash-hero-text > div > .row{ flex: none; gap: 4px; margin: 0; }
  .dash-hero-panel .dash-hero-text > div > .row .badge{ padding: 1px 8px; font-size: 12px; }
  .dash-hero-panel .dash-hero-text > div > h1{ flex: 1 1 0; min-width: 0; margin: 0; }
  .dash-hero-panel .dash-hero-text > div > .sub,
  .dash-hero-panel .dash-hero-text > div > p,
  .dash-hero-panel .dash-hero-text > div > .eyebrow{ flex: 0 0 100%; }
  /* The story card under the bar: everything shown, nothing clamped. */
  .sxh-card{ background: var(--ink, #111); color: #fff; border-radius: 16px; padding: 18px 16px; margin: 0 0 16px; }
  .sxh-card .dash-hero-text{ position: static; inset: auto; transform: none; opacity: 1; max-width: none; }
  .sxh-card .dash-hero-text h1{ font-size: 19px; line-height: 1.25; color: #fff; }
  .sxh-card .dash-hero-text p{ display: block; -webkit-line-clamp: unset; line-clamp: none; overflow: visible; }

  /* The editions (/briefings, /weekly) show the same static bar on a phone (edition.css:98
     hides it elsewhere) and its crumbs replace the pill that sat on the photo card. The
     photo card is shorter and its words sit at its bottom edge. */
  body:has(.wke-hero) .dash-hero-panel:not(.dash-hero-panel--crumbs){ display: block; }
  body:has(.dash-hero-panel .dash-float-crumbs) .wke-crumbs.wke-crumbs--float{ display: none; }
  .wke-hero--bleed{ min-height: 220px; display: flex; flex-direction: column; justify-content: flex-end; margin-bottom: 18px; }
  .wke-hero--brand.wke-hero--bleed .wke-hero-copy,
  .wke-hero--bleed:not(.wke-hero--brand) .wke-hero-copy{ min-height: 0; padding: 18px 16px 44px; }

  /* 2b. The category strip under the crumbs: one 38px row, tabs centred. /news carried the
     standalone .nh-pills' 24px bottom margin into the strip (a 64px row with the tabs at
     its top), the arrows keep their decided 36px (e2e), so the row is 44px. */
  .intg-tabwrap{ align-items: center; }
  .intg-tabwrap .intg-tabs,
  .intg-tabwrap .nh-pills{ margin: 0; align-items: center; }

  /* 3. Every button shorter and wider: 40px (44 for the large one, which was 50). */
  .btn{ min-height: 40px; padding: 8px 22px; }
  .btn-sm{ min-height: 34px; padding: 6px 18px; }
  .btn-lg{ min-height: 44px; padding: 9px 26px; }
  .ms-btn{ border-radius: 12px; }
  .ms-btn--ghost:not(.ms-btn--inhero){ padding: 10px 26px; }
  .ms-hero-ctas .ms-btn--inhero{ border-radius: 12px; }
  .ms-hero-ctas .ms-btn--inhero{ padding: 7px 20px; }

  /* 4. Home hero: the headline is three regular-weight lines ("See how AI" / "changes your
     job" / "then learn how to use it."); the form and what follows sit a little lower; the
     four figures are one line in a floating card. */
  .ms-hero .ms-hero-display{ font-weight: 700; font-size: 7.8vw; line-height: 1.08; text-align: left; }
  .ms-hero .ms-hero-cycling{ text-align: left; width: 100%; }
  .ms-hero-l1a, .ms-hero-l1b{ display: block; }
  .ms-hero-l2{ font-weight: 700; letter-spacing: -0.03em; }
  /* Owner 2026-10-14 (second pass): headline bold again; See Example Result and its line
     come up under the support copy, the job chips follow, and the audit bar sits below
     them; no "Swipe departments" label on a phone. */
  .ms-hero-ctas{ order: 1; margin-top: 18px; }
  .ms-hero-trust{ order: 2; }
  .ms-swipe-label{ display: none; }
  .ms-hero .ms-chipwrap{ order: 3; margin-top: 20px; }
  .ms-hero-form{ order: 4; margin-top: 20px; }
  .ms-audit-result{ order: 5; }
  .ms-hero-stats{ order: 6; }
  .ms-hero-subpill{ order: 7; }
  .ms-hero-scroll-cue{ order: 8; }
  .ms-hero-stats{
    display: flex; flex-wrap: nowrap; width: max-content; max-width: 100%;
    margin: 18px auto 0; border-radius: 999px; background: var(--surface);
    box-shadow: 0 6px 18px rgba(16,24,40,.12);
  }
  /* Four figures at the 12px floor are ~250px of text; with their icons the row is ~405px
     against 348 at 390, so the icons stay on desktop only. */
  .ms-hero-stat{ flex: 0 0 auto; padding: 8px clamp(5px, 2vw, 12px); gap: 0; }
  .ms-hero-stat + .ms-hero-stat{ border-left: .5px solid var(--border-2); border-top: 0; }
  .ms-hero-stat:nth-child(n+3){ border-top: 0; }
  .ms-hero-stat-ico{ display: none; }
  .ms-hero-stat-num{ font-size: 12.5px; }
}
@media (max-width: 359px){
  .ms-hero-stats{ overflow-x: auto; scrollbar-width: none; }
  .ms-hero-stat{ padding: 8px 4px; }
  .ms-hero-stat-txt{ gap: 2px; }
}
