/* ============================================================
   MOBILE.CSS — the phone pass
   ------------------------------------------------------------
   Everything here is under one breakpoint so there is a single place to tune
   how the app behaves on a phone.

   The headline fix is the menu button. The mobile drawer already worked —
   toggleSidebar() has handled .mobile-open and the backdrop for a long time —
   but the ONLY control that called it lived inside the sidebar, which is
   translated fully off-screen on a phone. So the button sat at x = -244 and
   there was no way to reach navigation at all. It is now a fixed control that
   is not part of the sidebar and cannot travel with it.
   ============================================================ */

/* ── The menu button ──────────────────────────────────────────
   Desktop never sees it: there the sidebar is always on screen and its own
   toggle is reachable.
   ------------------------------------------------------------ */
.mobile-menu-btn { display: none; }

@media (max-width: 640px) {
  .mobile-menu-btn {
    position: fixed;
    top: 10px;
    left: 10px;
    /* Under the sidebar's 200 on purpose: when the drawer is open it slides
       over the button, and the backdrop behind it is what closes it. Two
       controls fighting over the same corner would be worse. */
    z-index: 190;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: 12px;
    border: 1px solid var(--border-color);
    background: color-mix(in srgb, var(--bg-nav-solid) 88%, transparent);
    backdrop-filter: blur(10px);
    color: var(--text-primary);
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
  }
  .mobile-menu-btn:active { transform: scale(0.94); }

  /* The button floats over the page, so the first thing on each screen needs
     room to clear it rather than sitting underneath. */
  /* Exactly one element per screen carries this. .messenger-pane-1 used to be
     in the list as well as .pane-1-header, which sits INSIDE it — so a library
     got 58px twice and opened on 116px of nothing. */
  .home-scroll,
  .pane-1-header,
  .lib-hub { padding-top: 58px; }

  /* ── Type and spacing ───────────────────────────────────────
     The desktop scale was being used unchanged on a 375px screen, which is
     what "everything is too large" was. Headings come down hardest because
     they were sized for a column three times this wide. */
  /* .hero-greeting, not .hero-title — the visible heading on the dashboard is
     the greeting, and the only h1 is screen-reader-only. Sizing the h1 changed
     nothing anyone could see. */
  .hero-greeting,
  .lib-hub-title { font-size: 1.5rem; line-height: 1.2; }
  .hero-greeting .miside-char { font-size: inherit; }

  .hero-sub,
  .hero-verse,
  .lib-hub-subtitle { font-size: 0.9rem; }

  .home-hero,
  .hero-section,
  .lib-hub-hero { padding: 1.1rem; }

  .section-header-title { font-size: 1.05rem; }
  .browse-folder-title { font-size: 1.15rem; }

  /* Stat tiles: two up rather than one, and the numbers no longer set at a
     size meant to be read across a room. */
  .home-stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.6rem; }
  .home-stat-card { padding: 0.85rem 0.7rem; }
  .stat-value { font-size: 1.5rem; }
  /* 0.62rem works out at 9.92px, which I set in the last pass and which is
     smaller than anything else in the app. Phone text has a floor. */
  .stat-label { font-size: 0.7rem; letter-spacing: 0.05em; }
  .stat-icon { width: 34px; height: 34px; }

  /* Cards stack; a two-column grid at this width leaves nothing readable. */
  .card-grid,
  .lib-hub-grid { grid-template-columns: 1fr; gap: 0.7rem; }
  .card, .lib-card { padding: 0.9rem; }

  /* ── The two-pane layouts ───────────────────────────────────
     Every library is a sidebar tree beside a detail pane. At 375px there is
     no room for both, so the tree sits above the detail and the drag handle
     between them goes away — there is nothing left to drag. */
  .messenger-layout {
    flex-direction: column;
    /* The route scrolls rather than clipping. Everything below depends on
       this: the panes are allowed to be as tall as they need, so something
       has to be able to scroll past them. */
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    min-height: 0;
  }
  .messenger-pane-1,
  .messenger-pane-2 {
    width: 100% !important;
    min-width: 0 !important;
    max-width: none !important;
    flex: none;
    /* layout.css gives both panes height:50% under max-width:768px. That is a
       hard half-the-viewport split whatever is inside them, and pane 1 has a
       294px header - title, search and four stat chips - inside its 398px.
       About 100px reached the folder tree, which is two rows of it. Sizing to
       content is what lets the header be as tall as it is AND leaves the tree
       a list worth scrolling. */
    height: auto !important;
  }
  /* So the detail pane is still a pane when it is only holding an empty
     state, rather than a caption under the tree. */
  .messenger-pane-2 { min-height: 55vh; }
  /* Cap the scrolling list, never the pane around it. Capping the pane at 42vh
     left 341px for a header that is 294px tall on its own, so the folder tree
     was handed a 32px window — the rows were all there, with 117px of content,
     inside a slot too small to show one of them. The header now takes the room
     it needs and the tree scrolls in what is left. */
  .messenger-pane-1 { max-height: none; }
  .pane-1-content {
    /* A FLOOR as well as a ceiling. The pane is flex:none on mobile so it has
       no definite height, which leaves this element's flex:1 with nothing to
       divide — it collapsed to 102px and stayed there even with 737px of
       folders inside it. A min-height gives the list a real size of its own
       instead of asking flex for a share of nothing. */
    min-height: 34vh;
    max-height: 44vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .resizer-divider { display: none; }

  /* ── Things that were simply too wide ───────────────────────
     A year is 53 columns wide however small the squares get, so this scrolls
     inside its own box rather than pretending to fit. Smaller cells just mean
     less of that scrolling: 844px down to about 480. The page itself never
     overflows — the box clips it. */
  .home-heatmap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .heatmap-grid { gap: 1px; }
  .heatmap-grid .heatmap-cell { width: 8px; height: 8px; }

  /* Tables and code blocks scroll inside themselves rather than pushing the
     page sideways. */
  .wing-body pre,
  .practice-desc-body pre,
  table { max-width: 100%; overflow-x: auto; display: block; }

  /* ── Touch targets ──────────────────────────────────────────
     Anything you tap wants to be reachable with a thumb rather than a
     fingernail. */
  .btn, .btn-sm { min-height: 38px; }
  .practice-icon-btn { width: 36px; height: 36px; }
  .tree-node-row { padding-top: 0.5rem; padding-bottom: 0.5rem; }

  /* The floating settings gear and the agenda flag both sit in corners the
     thumb reaches for; keep them clear of the menu button. */
  /* .ag-flag-root was the id, not a class -- this rule matched nothing and the
     flag kept its desktop 26px inset on a phone. */
  .ag-root { right: 10px; }

  /* ── THE ATTEMPT SCREEN ─────────────────────────────────────
     Two things here were not "cramped", they were unusable.

     The topbar carries ten controls and needs 557px to lay them out. In a
     373px bar with no overflow rule, everything past the fifth simply was not
     there: Check Code sat at x=417 with no way to reach it. It scrolls
     sideways now, which is the honest answer for a strip of controls that
     genuinely does not fit — the alternative, wrapping, pushes the editor off
     the bottom of the screen.

     And the test panel was display:none, so an attempt on a phone could be
     run but never checked — the results existed and could not be seen. It
     sits under the editor now. */
  /* TWO ROWS, which reverses the call made here earlier.

     One scrolling strip was the wrong answer, and the measurements say why:
     the right-hand group alone needs 549px in a 412px bar, so the title was
     squeezed to ZERO width and parked at x=676 — off the side of the phone.
     You could not see which problem you were solving. Wrapping costs about
     40px of a 915px screen; an invisible title costs the context for the
     whole screen.

     Row one is Back and the title. Row two is every control, and only that
     row scrolls, so the strip that genuinely does not fit is the only thing
     that behaves like a strip. */
  .practice-topbar {
    flex-wrap: wrap;
    row-gap: 0.3rem;
    gap: 0.35rem;
    overflow: visible;
    /* No blanket 58px any more. The floating menu button only overlaps the
       FIRST row, so padding the whole bar left an empty 58px strip beside
       every control on the second one. Row one clears the button itself. */
    padding-left: 0.5rem;
  }
  .practice-topbar-left {
    order: 1;
    flex-shrink: 0;
    margin-left: 50px;           /* this row, and only this row, clears it */
  }
  .practice-topbar-center { order: 2; }
  .practice-topbar-right {
    order: 3;
    flex: 1 0 100%;              /* its own row */
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    min-width: 0;
  }
  .practice-topbar-right::-webkit-scrollbar { display: none; }
  .practice-topbar-right > * { flex-shrink: 0; }

  /* Check Code rides along at the right edge instead of scrolling away with
     everything else. A strip that scrolls is a fair answer for ten controls
     that do not fit; making you scroll 300px to reach the primary action is
     not, and it is the one button an attempt is actually for. */
  /* Check Code sits in the row, next to Retry, and scrolls with it.

     It used to be pinned to the right edge so it could not scroll away. That
     was written when every control shared ONE row with the title and reaching
     it meant scrolling 300px. Now that the controls have a row to themselves
     the button is a short scroll away, and pinning it only bought a panel
     parked on top of whatever you were trying to scroll to. */
  .practice-topbar .pp-check-btn {
    position: static;
    box-shadow: none;
  }
  /* The timer wanted 146px of the control row for hundredths of a second
     nobody reads mid-attempt. Minutes and seconds still fit. */
  .practice-topbar-right .timer-display { font-size: 0.75rem; padding: 0.2rem 0.4rem; }

  /* MEASURED, 375px: the strip wanted 547px of a 357px row, which put Retry
     at x=320 and Check Code at x=416 -- the button the whole attempt is for,
     entirely off screen, behind a 190px swipe with nothing to suggest it was
     there. The strip still scrolls; these just stop the primary action from
     starting past the edge, and say that there is more to the right. */

  /* Full screen is a desktop affordance. On a phone the browser chrome owns
     that, and the button is 36px of a row that has none to spare. */
  .practice-topbar-right #fullscreen-toggle-btn { display: none; }

  /* Retry keeps its icon and drops its label: it is the one action here that
     is recognisable without words, and the label cost 56px. */
  .practice-topbar-right .practice-action-btn:not(.pp-check-btn) {
    font-size: 0;
    gap: 0;
    padding: 0.3rem 0.45rem;
  }
  .practice-topbar-right .practice-action-btn:not(.pp-check-btn) svg {
    font-size: 1rem;
    width: 16px;
    height: 16px;
  }

  /* The edge of a strip that has more in it. Painted over the topbar rather
     than inside the scroller, so it stays put while the contents move. */
  .practice-topbar { position: relative; }
  .practice-topbar::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: 0;
    width: 26px;
    height: 40px;
    pointer-events: none;
    background: linear-gradient(to right, rgba(22, 27, 34, 0), #161b22);
  }

  /* The boss bar is the exception. It is hidden by default and costs nothing
     then, but switched ON it measured 307px — most of a 375px screen — and
     pushed the action buttons that far further out of reach. It shrinks here
     rather than being hidden, because it is a feature and not clutter; it
     just does not get to outrank the button that runs your tests. */
  /* The title and boss bar share row one with Back, so they get the width
     that row has rather than the 34vw they were rationed when everything was
     fighting over a single line. */
  .practice-topbar-center {
    /* basis 0, not auto. On auto it asks for its content's full 344px, which
       will not sit beside Back in the 352px left after the menu button, so it
       wrapped to a row of its own and the bar became three rows deep. From
       zero it takes what is left of row one instead. */
    flex: 1 1 0;
    min-width: 0;
    max-width: none;
    overflow: hidden;
  }

  /* Inside that row the split was wrong: the health track took 135px and the
     problem's NAME got 63, so the one piece of text telling you what you are
     solving read "Hello,...". The bar is a gauge and stays legible at any
     width; a truncated name tells you nothing. */
  .practice-topbar-center .sao-plate-name {
    flex: 1 1 auto;
    min-width: 0;
    /* The cap, not the flex, was what held this at 63px: the base rule sets
       max-width:30%, so freeing space beside it changed nothing and the room
       went to empty plate. On a wide screen 30% is a sensible share; on a
       phone it is three words of a title. */
    max-width: 72%;
  }
  .practice-topbar-center .sao-track {
    flex: 0 1 auto;
    max-width: 64px;
  }
  /* The level chip is "LV. —" until a level exists; it is not worth 46px of a
     row that cannot fit the title. */
  .practice-topbar-center .sao-lv.unset { display: none; }

  /* ── RESULTS AS A BOTTOM SHEET ──────────────────────────────
     At rest the panel is nothing but its tab row and its button bar,
     sitting on the floor of the screen: Tests / Executions / Run Code /
     Finish read as a footer and the results are below the fold. Scrolling
     the sheet lifts it half a screen and the rows rise into view under the
     bar, which is pinned and stays put. Scrolling back to the top drops it
     to a footer again.

     Absolutely positioned rather than in flow, because expanding in flow
     would have to take the height from the editor, which has a floor of its
     own -- the two cannot both be satisfied. Out of flow, the sheet simply
     lies over the editor, and .practice-body reserves the collapsed height
     so the last lines of code are never behind the bar.

     --pp-collapsed-h is measured and set by ppSheetInit() in
     practice-panel.js: the button bar wraps on narrow phones, so its height
     is not a constant worth hard-coding. The 94px is only a first paint
     fallback. */
  .practice-body {
    position: relative;
    padding-bottom: var(--pp-collapsed-h, 94px);
  }

  .practice-panel {
    /* Flex, not block: the children are reordered below, and order only
       applies to flex items. */
    display: flex !important;
    flex-direction: column;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto !important;
    flex-basis: auto !important;
    height: var(--pp-collapsed-h, 94px);
    min-height: 0;
    max-height: none;
    overflow-y: auto;
    border-left: none;
    border-top: 1px solid var(--border-color);
    /* Over .run-code-overlay, which is a body-level fixed layer at 9999. */
    z-index: 10000;
    transition: height 260ms cubic-bezier(0.4, 0, 0.2, 1);
  }
  .practice-panel.pp-open {
    height: calc(var(--pp-collapsed-h, 94px) + 50vh);
  }
  @media (prefers-reduced-motion: reduce) {
    .practice-panel { transition: none; }
  }

  /* The four controls in one pinned block at the top of the sheet: the tab
     row first, the buttons directly under it, results below both. The tab
     row keeps its height on purpose -- the buttons stick at exactly that
     height, so shrinking one without the other opens a gap between them. */
  .practice-panel .pp-tabs { order: 1; }
  .practice-panel .pp-footer {
    order: 2;
    margin-top: 0;
    top: calc(var(--ed-toolbar-h, 38px) + 1px);   /* directly under .pp-tabs */
    bottom: auto;
    border-top: none;
    border-bottom: 1px solid #21262d;
    z-index: 3;
  }
  .practice-panel .pp-tab-body { order: 3; }

  .practice-sidebar { max-height: 34vh; overflow-y: auto; }
  .practice-editor-area { min-height: 46vh; }

  /* ── THE NOTEBOOK ATTEMPT ───────────────────────────────────
     It reuses .practice-layout / .practice-body / .practice-sidebar from the
     coding attempt, so it inherited a phone layout written for a screen the
     coding attempt has and this one does not. Measured at 375x812 before this
     block: 94px reserved at the bottom for a panel that is not in the DOM, a
     55px dead strip down the right, and the question -- the only thing on the
     screen that matters -- left with 347px of 812.

     Scoped on the route rather than on :has(.practice-panel), because the
     router already stamps document.body.dataset.route and that is a fact
     rather than an inference. */

  /* 1. No bottom sheet here, so no room held for one. .practice-body reserves
        --pp-collapsed-h for the coding attempt's results panel; this route has
        no .practice-panel at all, and the padding was simply 94px of nothing. */
  body[data-route="notes-practice"] .practice-body { padding-bottom: 0; }

  /* 2. Full width. The sidebar carries inline width:320px; min-width:320px --
        right for a 320px rail beside an editor, wrong for a stacked column on a
        375px phone, where it left 55px of the row empty. Inline styles only
        yield to !important. */
  body[data-route="notes-practice"] .practice-sidebar {
    width: auto !important;
    min-width: 0 !important;
    /* Content height, not 380px of it. The desktop rule is `flex: 0 1 380px`,
       which is a WIDTH for a rail standing beside the question -- stacked into a
       column it becomes a height request, and the panel held 243px to show
       about 145px of chips and numbers. The max-height stays as the ceiling for
       a notebook with many sections. */
    flex: 0 0 auto;
    /* ONE SCROLLING NAV BOX, capped. The section strip and the question grid
       are the two things you navigate with, and together they are allowed a
       fifth of the screen; past that they scroll as a pair and the answers keep
       the rest. Before this the panel simply grew, and a notebook with several
       sections would have pushed the question down the screen. */
    max-height: 22vh;
    overflow-y: auto;
    /* No track down the edge, the same call as the library trees: a bar beside
       a 179px box of chips and numbers is more seam than affordance. Wheel,
       trackpad, touch and keyboard all still scroll it. */
    scrollbar-width: none;
    -ms-overflow-style: none;
    padding: 0.75rem 1rem;
    /* 1.5rem between blocks is right beside an editor and expensive stacked:
       two gaps alone were 48px of a panel meant to be about 180. */
    gap: 0.5rem;
  }

  /* The "Sections" heading names what is directly under it -- the chips say it
     themselves, and stacked it cost 32px plus a gap. */
  body[data-route="notes-practice"] .practice-sidebar > div:first-child { display: none; }

  /* The divider block above the question grid, tightened to match. Its inline
     margin-bottom is spacing for a block that had the keyboard legend under it;
     with that gone it is the last thing in the panel and the margin is padding
     the panel out from the inside. */
  body[data-route="notes-practice"] #np-current-section-title { margin-bottom: 0.5rem !important; }
  body[data-route="notes-practice"] .practice-sidebar > div:nth-child(3) {
    padding-top: 0.625rem !important;
    margin-bottom: 0 !important;
  }

  /* 3. The section chips run across, not down. Stacked, two sections already
        cost 110px of a panel that had 275px to give. */
  body[data-route="notes-practice"] #np-sections-tabs {
    flex-direction: row !important;
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 0.25rem;
    margin-bottom: 0.75rem !important;
  }
  body[data-route="notes-practice"] #np-sections-tabs::-webkit-scrollbar,
  body[data-route="notes-practice"] .practice-sidebar::-webkit-scrollbar { width: 0; height: 0; display: none; }
  /* The chips are built for a vertical stack -- .np-sidebar-tab-dark is
     `width: 100%`, so laid in a row every one of them still asked for the full
     width and the first was stretched across the strip and clipped to 22px,
     cutting off its own second line. Across, they size to their text. */
  body[data-route="notes-practice"] #np-sections-tabs {
    align-items: flex-start;
  }
  body[data-route="notes-practice"] #np-sections-tabs > * {
    flex: 0 0 auto;
    width: auto !important;
    min-width: 4.5rem;
  }

  /* 4. The keyboard legend goes. It reads "Keys: 1-9/A-E answer, N/P or arrows
        move, F flag, H hint" and cost 63px on a device with no keyboard to
        press any of them on. */
  body[data-route="notes-practice"] .practice-footer { display: none; }

  /* 5. SUBMIT WAS OFF THE SCREEN. The topbar's right group is one nowrap row in
        an overflow-x:auto strip: at 375px its seven controls measured 476px, so
        the timer was cut in half and the submit button sat at x=391 -- entirely
        past the edge, reachable only by discovering a sideways scroll on a strip
        that shows no scrollbar. The attempt could not be submitted on a phone.

        It wraps now, so no control can ever be pushed out of reach no matter how
        many the topbar grows. The gap comes down to buy back a row's worth of
        width first. Both are !important because the group carries its display,
        alignment and gap inline. */
  body[data-route="notes-practice"] .practice-topbar-right {
    flex-wrap: wrap !important;
    gap: 0.5rem !important;
    justify-content: flex-end;
    overflow-x: visible;
  }

  /* Two controls stand down on a phone so the other seven, the timer included,
     fit one line -- 385px of controls plus 64px of gaps against a 359px row
     wrapped to two, and the topbar went from 95px to 141px the moment the OST,
     Feel and full-screen buttons arrived.

     FULL SCREEN, because it does nothing here: iOS Safari does not implement
     requestFullscreen on anything but a <video>, and a phone browser is already
     as full-screen as it gets. THE THEME SELECT, because at 75px it is the
     widest control on the strip and the only one that is a standing preference
     rather than a thing you do to this attempt -- it is set once, from
     Settings, and stays set. AND THE TEXT-ALIGN TOGGLE, which centres or
     left-aligns the question -- also a preference, and the least essential of
     the row once reading aloud earned a place in it. All three remain on every
     wider screen. */
  body[data-route="notes-practice"] #np-fullscreen-btn,
  body[data-route="notes-practice"] #np-align-btn,
  body[data-route="notes-practice"] #theme-selector { display: none; }

  /* ── The action bar ─────────────────────────────────────────
     Previous, Next and Submit in a bar pinned to the bottom, the way the coding
     attempt keeps Run Code and Finish attempt in one. npSyncMobileChrome moves
     Submit in here on phones.

     This is what unclutters the screen rather than merely shrinking it. The
     three controls that ACT on the attempt are in one place at thumb height,
     the topbar goes back to holding only what it describes, and the question
     pane scrolls nothing but the question and its answers -- Previous and Next
     used to scroll away with the content they were meant to move through. */
  body[data-route="notes-practice"] #np-nav-row {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0 !important;
    padding: 0.5rem 0.75rem !important;
    padding-bottom: calc(0.5rem + env(safe-area-inset-bottom, 0px)) !important;
    border-top: 1px solid var(--border-color);
    background: var(--bg-surface, #0d1117);
  }
  body[data-route="notes-practice"] #np-nav-row > * {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    /* The 34px floor the TOUCH TARGETS block below sets for far less important
       buttons; Submit measured 29px. These end and move through the attempt. */
    min-height: 40px;
  }
  /* Submit is the one that finishes, so it reads as the primary of the three. */
  body[data-route="notes-practice"] #np-nav-row #np-submit-btn { flex: 1.2 1 0; }

  /* Nothing hides behind the bar. */
  body[data-route="notes-practice"] .np-question-pane {
    padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px));
  }

  /* 6. Give the reclaimed space to the question rather than to padding. */
  body[data-route="notes-practice"] .np-question-pane > div {
    padding: 1.25rem 1rem !important;
  }

  /* ── DETAIL PANES: NOTEBOOK AND PROGRAM ─────────────────────
     Both are built from the same .prog-* blocks, and both were laid out for a
     pane about twice this wide. Measured on the notebook detail at 375px.

     THE SECTION ROW WAS EATING ITSELF. .nb-section-stat carries a 120px
     min-width for its progress bar; with the number column and the gaps that
     left .prog-variant-info exactly 90px of a 293px row, so "3 questions" and
     "4 choices (A-D)" -- one comfortable line at any sane width -- broke onto
     two, the row grew to 121px, and the vertically centred "01" ended up level
     with the second line instead of the title. It read as overlapping text.

     The status moves to its own line under the title, indented to align with
     it, and the min-width goes: on a phone there is no width to reserve. */
  .prog-variant-row {
    flex-wrap: wrap;
    /* The row's 0.9rem gap is a COLUMN gap by intent; once the row wraps it
       applies to both axes and charges 14px between the title and the status
       line on top of the margin below. */
    row-gap: 0;
  }
  .nb-section-stat {
    min-width: 0;
    flex: 1 1 100%;
    align-items: flex-start;
    /* Aligned under the title rather than under the number: the number column
       is 20px and the row's gap is 0.9rem. */
    padding-left: calc(20px + 0.9rem);
    margin-top: 0.25rem;
  }

  /* 1.9rem over 209px of column set "Quick Start Quiz" on two lines and gave
     the header 201px before a word of the notebook had been read. */
  .prog-detail-title { font-size: 1.4rem; }
  .prog-detail-icon { width: 52px; height: 52px; min-width: 52px; }
  .prog-detail-header { gap: 0.875rem; }

  /* The breadcrumb runs the full content width, and the agenda flag is pinned
     over its right end -- measured overlapping from x=319 while the breadcrumb
     reached 334. Keeping clear of it is cheaper than moving the flag, which is
     deliberately in that corner on every route. */
  .breadcrumb-nav {
    padding-right: 52px;
    row-gap: 0.25rem;
  }

  /* The settings button floats over the bottom-right corner, so the last card
     in a pane scrolled underneath it and could not be read or tapped. */
  .messenger-pane-2 .pane-2-content,
  .messenger-pane-2 { scroll-padding-bottom: 72px; }
  .prog-variant-list { padding-bottom: 56px; }

  /* ── TOUCH TARGETS ──────────────────────────────────────────
     Measured across every route: the back button in each library was 64x23,
     and a handful of others sat between 20 and 26px tall. Thumbs are about
     9mm; these were being aimed at with a fingernail. */
  .btn-back-dark,
  .hero-verse-btn,
  .hc-go,
  .activity-action,
  .lang-pill {
    min-height: 34px;
    padding-top: 0.4rem;
    padding-bottom: 0.4rem;
  }
  .hc-dismiss,
  .lang-swap-btn,
  .hero-verse-btn,
  .lang-pill { min-width: 34px; min-height: 34px; }

  /* ── TEXT FLOORS ────────────────────────────────────────────
     Anything under 11px measured across the routes, lifted to a size that
     survives a phone screen at arm's length. */
  .stat-goal-txt,
  .hc-sub,
  .lang-pair-label,
  .lang-pill,
  .lang-board-hint,
  .prog-stat-body em { font-size: 0.72rem; }

  /* A keyboard shortcut is not advice a phone can act on. */
  .search-shortcut-hint { display: none; }


  /* ── THE REST OF THE APP, AUDITED AT 412px ──────────────────
     Every route was measured rather than eyeballed, and these are what came
     back. Two were structural and the rest were the same two faults over and
     over: text under the readable floor, and controls too small to hit.
     ------------------------------------------------------------ */

  /* THE VISUALIZATION TOPBAR WAS UNREACHABLE. It wants 761px, it has 412, and
     its overflow-x was `visible` — so the strip was not clipped, not
     scrollable, just drawn off the side of the phone. The "Notebooks" and
     "General" tabs sat 345px past the edge with no way to reach them. */
  .viz-topbar {
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .viz-topbar::-webkit-scrollbar { display: none; }
  .viz-topbar > * { flex-shrink: 0; }
  .viz-module-tabs-wrapper { overflow: visible; }

  /* The wing admin's folder filter was squeezed to 30px — a dropdown you
     cannot read the current value of, let alone aim at. The New button and
     the search box take the first line; this gets the second. */
  .awx-bar .awx-folder-filter {
    flex: 1 1 100%;
    min-width: 140px;
    max-width: none;
  }

  /* ── The 11px floor, applied where the audit found it broken ──
     9px on an analytics label, 9.6px on a badge and on the quest status
     title, and a scatter of 10-10.9px through the trees, tabs and tables. */
  .analytics-mini-label,
  .an-sn-legend,
  .system-status-title,
  .badge,
  .admin-group-count,
  .admin-panel-count,
  .lang-card-chip,
  .tree-node-badge,
  .tree-node-count,
  .awx-mini-tag,
  .awx-tab-n,
  .awx-table th,
  .awx-actions,
  .wing-goal-more { font-size: 0.7rem; }

  /* The stragglers, which needed naming individually because a more specific
     rule was already winning. Found by walking up from each element that was
     still under the floor rather than by guessing at selectors. */
  .badge.badge-neutral,
  .badge.badge-primary,
  .badge.badge-success,
  .badge.badge-warning,
  .badge.badge-danger { font-size: 0.7rem; }
  .lang-admin-table th { font-size: 0.7rem; }
  .an-sn-list-t em { font-size: 0.7rem; }
  /* ── The ones written into a style attribute ─────────────────
     About twenty places build markup with font-size:0.6rem or 0.65rem inline
     — 9.6px and 10.4px. An inline style beats every stylesheet rule, so no
     selector above could reach them and none of the earlier attempts did
     anything at all.

     !important is the correct tool exactly here and almost nowhere else: it
     is the only thing that outranks an inline declaration. Matching on the
     attribute keeps it to the sizes that are actually too small, and the
     whole thing is inside the phone breakpoint, so the desktop layouts these
     sizes were chosen for are untouched. The real fix is to stop inlining
     type; this stops it hurting anyone on a phone in the meantime. */
  [style*="font-size:0.6rem"],
  [style*="font-size: 0.6rem"],
  [style*="font-size:0.65rem"],
  [style*="font-size: 0.65rem"] { font-size: 0.7rem !important; }

  /* 30px was still short of a thumb; the card behind it stays clickable, so
     this only has to be big enough to aim at deliberately. */
  .wing-goal-next .wing-step-box { width: 32px; height: 32px; }

  /* ── Thumb-sized controls ────────────────────────────────────
     A thumb is about 9mm. These measured between 20px and 30px, so they were
     being aimed at rather than pressed. Height only: widening them would
     rearrange rows that already fit. */
  .an-range-chip,
  .admin-tool-btn,
  .ali-folder-btn,
  .lang-tab,
  .lib-chip,
  .lib-fav-btn,
  .quest-new-btn,
  .quest-sort-btn,
  .quest-tab,
  .viz-module-tab,
  .viz-swap-btn,
  .viz-tabs-toggle-btn,
  .viz-zoom-btn {
    min-height: 32px;
  }
  /* These two are icon buttons, so they need the width as well or they become
     tall slivers. */
  .admin-tool-btn,
  .ali-folder-btn,
  .viz-zoom-btn { min-width: 32px; }

  /* The title in an admin table row is the way into the entry, so it is a tap
     target and not just text. Padding rather than min-height: it sits inline
     above its tag chips and a fixed height would push those out of the cell. */
  .awx-title-btn { padding: 0.35rem 0; }


  /* The cloud status toast was pinned 366px from the left of a 375px screen,
     so it hung off the right edge. */
  .cloud-toast {
    left: 10px;
    right: 10px;
    width: auto;
    max-width: calc(100vw - 20px);
  }
}

/* A phone in landscape is short rather than narrow — the drawer should not
   eat the whole screen.
   640px, not the 900px this was written at. Every rule in here adjusts one
   set in the block above, which stops at 640 - so between 641 and 900 they
   applied on their own, with nothing to adjust. An 800x450 laptop got the
   folder tree capped at 207px inside a side-by-side layout with the full
   column free, and 50px of header padding to clear a menu button that is
   display:none at that width. */
@media (max-width: 640px) and (max-height: 480px) {
  .pane-1-content { max-height: 46vh; }
  .home-scroll, .lib-hub, .pane-1-header { padding-top: 50px; }
}

/* ── Dialogs on a phone ───────────────────────────────────────
   The cap that keeps a dialog on screen lives in components.css and applies
   everywhere. This is the part that is only true of a small screen: 32px of
   card padding plus 24px of overlay padding is 31% of a 360px width spent on
   empty margin, which is what pushed these panels past the viewport in the
   first place. Give it back to the content and re-cap against the smaller
   inset. */
@media (max-width: 640px) {
  .modal-overlay { padding: 12px; }
  .modal-content {
    padding: var(--space-lg);
    max-height: calc(100vh - 24px);
    max-height: calc(100dvh - 24px);
  }
  /* Long bodies inside a now-scrolling card should not also scroll: nested
     scrollers on a touch screen are a coin toss over which one moves. */
  .modal-content .speech-body { max-height: none; overflow: visible; }
}
/* ── Saving without a keyboard ─────────────────────────────────
   Ctrl+S is not a gesture a touchscreen has, so on a phone there was no way to
   force a save at all — you waited out the 30-second autosave and hoped. The
   button is hidden on desktop, where the shortcut already exists and one more
   icon in that row would be clutter. */
.practice-save-btn { display: none; }
@media (max-width: 640px) {
  .practice-save-btn { display: inline-flex; }
}
/* A tablet has no Ctrl either. */
@media (hover: none) and (pointer: coarse) {
  .practice-save-btn { display: inline-flex; }
}
