/* ═══════════════════════════════════════════════════════════════════════════
   PHONE.CSS - the phone layer.

   Everything above 767px is untouched by this file. A phone is not a small
   desktop and it is not a tablet in portrait: it has one thumb, one column,
   and no room for chrome that exists to be comprehensive. So on a phone the
   app is deliberately smaller than itself.

   Three decisions drive every rule below.

     1. THERE IS NO DRAWER. The hamburger opened the desktop sidebar in an
        off-canvas panel - the same nav the bottom bar already carries, plus a
        recents list nobody scrolls on a phone, over a scrim. It also owned
        the top-left corner of every screen, which is why every header in the
        app reserved 58-64px of empty space it could float over. Removing it
        gives that space back to the title AND deletes a whole class of layout
        bug at once.

     2. THE HEADER IS TWO ROWS, NOT A CENTRED GRID. redesign.css lays the
        phone header out as `minmax(48px,1fr) auto minmax(48px,1fr)` so the
        title sits dead centre with the hamburger floating over column one.
        That grid has no way to fail gracefully: the moment the title plus its
        status pill plus the actions are wider than the screen - Assignments
        with a Canvas pill and four controls, Tutor with Refresh and New
        session - the columns overlap and the actions run off the right edge.
        A stack cannot do that. Title on its own line, actions on the next,
        and the action row scrolls sideways rather than pushing the page.

     3. ONE THING AT A TIME. Day view only on the calendar, Due or Planner but
        never both, one widget tile per row on Home. A phone shows one column
        of one thing; anything else is two half-legible columns.

   Loaded after every layout sheet and before rounded.css, which owns shape
   and stays the last word on it. Nothing here declares a design token: the
   system is redesign.css's, and this sheet composes it.

   The cut is 767px, deliberately BELOW --bp-compact (900px): a tablet in
   portrait keeps the drawer and has the width for it, a phone does not. The
   same number lives in app.js as PHONE_MQ, and `body.is-phone` carries it to
   the rules that depend on app state rather than on width alone.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 767px) {

  /* ══════════════════ 1 · NO DRAWER, NO HAMBURGER ═══════════════════════ */
  /* The button, the scrim and the panel itself. All three go: a sidebar that
     can never be opened should not still be in the layout, taking hit-tests
     and paint. */
  .mobile-menu-btn,
  .nav-scrim,
  .sidebar { display: none !important; }

  /* Belt and braces for a stray `nav-open` - a keyboard shortcut, a resize
     from tablet width, an old value restored from storage. With the panel
     gone there is nothing to reveal, so the class must not dim the app. */
  body.nav-open .nav-scrim { opacity: 0 !important; pointer-events: none !important; }

  /* Every header in the app reserved space for the floating hamburger, in
     four different sheets and at three different widths. There is nothing to
     reserve it for any more. */
  .topbar,
  .view > .topbar,
  .view-projects > .topbar,
  .view-calendar > .topbar,
  .view-memories > .topbar,
  .view-skills .topbar,
  .view-voice .topbar,
  .cal-topbar { padding-left: max(14px, env(safe-area-inset-left, 0px)) !important; }

  /* ══════════════════ 2 · THE TWO-ROW HEADER ════════════════════════════ */
  /* Undo the centred grid from redesign.css §35 and stack instead. The title
     goes back to the left edge, where a left-to-right reader looks first and
     where the hamburger used to be. */
  .view > .topbar,
  .topbar {
    display: flex !important;
    flex-direction: column;
    /* `nowrap` matters more here than it looks. The bars inherit
       `flex-wrap: wrap` from the 760px block in styles.css, which in a COLUMN
       does not wrap onto a second line - it wraps onto a second COLUMN. With
       the bar's 72px min-height and a two-row stack inside it, the action row
       was being thrown into a phantom column off the right edge of the
       screen, which is exactly what it looked like. */
    flex-wrap: nowrap !important;
    align-items: stretch;
    justify-content: flex-start;
    height: auto;
    gap: 10px;
    padding-top: 10px;
    padding-bottom: 10px;
    padding-right: max(14px, env(safe-area-inset-right, 0px)) !important;
  }

  .view > .topbar > .topbar-left,
  .topbar > .topbar-left {
    grid-column: auto;
    justify-self: stretch;
    justify-content: flex-start;
    align-items: baseline;
    flex-wrap: wrap;
    row-gap: 2px;
    min-width: 0;
    /* `flex-basis: 100%` (styles.css, the <=699px block) was written for a
       header laid out as a ROW, where it means "take the whole width, so the
       actions wrap under you". In the COLUMN above it means 100% of the
       header's HEIGHT - an 850px hypothetical size that flex-shrink then
       squashes back down, and what it squashes is the title. Content height,
       which is what a stacked header wanted from it in the first place. */
    flex: 0 0 auto;
  }

  /* The title now has the whole width, so it is allowed to be a title again -
     no ellipsis, no nowrap, and the size the rest of the app uses. */
  .view > .topbar .page-title,
  .topbar .page-title {
    font-size: 1.4rem;
    line-height: 1.2;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    min-width: 0;
  }

  /* A subline or a status pill ("Not connected", "0 sessions", "3 overdue")
     is context, not a heading. It drops under the title rather than competing
     with it for the same line. */
  .view > .topbar .topbar-left > .muted,
  .topbar .topbar-left > .muted { font-size: 12px; }

  /* The action row. `nowrap` plus a sideways scroll, NOT wrapping: four
     controls that wrap make a 3-row header and push the content off screen,
     and the header is not the page. Every control keeps its full label - a
     row that scrolls is honest about there being more, an ellipsis is not. */
  .view > .topbar > .topbar-right,
  .topbar > .topbar-right {
    grid-column: auto;
    justify-self: stretch;
    /* No `width: 100%`. The stack stretches it to the content box already,
       and a fixed width plus the negative margins below would leave the row
       14px short of the right edge - a scroller that stops before the screen
       does looks like a clipped element, which is the bug this replaces. */
    min-width: 0;
    display: flex;
    flex-wrap: nowrap !important;
    justify-content: flex-start;
    align-items: center;
    gap: 8px;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    /* Bleed to the edges so a half-visible control reads as "scroll me"
       rather than as a clipping bug, then pad the ends back in. */
    margin-inline: calc(-1 * max(14px, env(safe-area-inset-left, 0px)))
                   calc(-1 * max(14px, env(safe-area-inset-right, 0px)));
    padding-inline: max(14px, env(safe-area-inset-left, 0px))
                    max(14px, env(safe-area-inset-right, 0px));
    padding-block: 2px;
    /* The same `flex-basis: 100%`, and here it was doing visible damage: the
       row resolved to 37px around 44px controls, and the `overflow-y: hidden`
       above - which is only there to stop the sideways scroller growing a
       vertical scrollbar - sliced 3px off the top and 4px off the bottom of
       every button in the bar. Descenders, pill ends and all. Content height,
       with a floor under it so no future squeeze can reach the glyphs. */
    flex: 0 0 auto;
    min-height: calc(var(--tap-min, 44px) + 4px);
  }
  .view > .topbar > .topbar-right::-webkit-scrollbar,
  .topbar > .topbar-right::-webkit-scrollbar { display: none; }

  /* The bleed above was supposed to make a half-visible control read as
     "scroll me". It does not. With the scrollbar hidden - which it has to be,
     or the row grows one - a pill sliced down the middle at the screen edge
     is indistinguishable from a pill that got clipped, and that is how it was
     read: "there's text clipping on the top bar". A cut says nothing; a FADE
     says the row continues.

     Same treatment `.settings-tabs` already uses on its own sideways
     scroller (styles.css), so the two rows that scroll in this app behave
     alike. Only the last 26px, so a control the student has scrolled to is
     never dimmed by more than its own edge, and only where masks are
     supported - without them the row is exactly what it is today. */
  @supports (mask-image: linear-gradient(#000, transparent)) {
    .view > .topbar > .topbar-right,
    .topbar > .topbar-right {
      -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 26px), transparent);
      mask-image: linear-gradient(90deg, #000 calc(100% - 26px), transparent);
    }
  }

  /* Nothing in that row may shrink itself into an unreadable sliver, and
     nothing may soak up the leftover space either - `flex: 1 1 auto` from the
     420px block in redesign.css stretched a lone button across the bar. */
  .topbar-right > * { flex: 0 0 auto !important; min-width: 0; }
  .topbar-right .ghost-btn,
  .topbar-right .primary-btn,
  .topbar-right .icon-btn,
  .topbar-right select {
    white-space: nowrap;
    font-size: 13px;
    min-height: var(--tap-min, 44px);
  }
  .topbar-right .ghost-btn,
  .topbar-right .primary-btn { padding-inline: 14px; }

  /* The page's own action goes first in the scroller. On a desktop it is last
     because the eye ends there; in a row that runs off the screen, last means
     "the one you cannot see", and on Assignments that was "+ Add assignment" -
     the reason the page exists. `order` moves the paint, not the DOM, so tab
     order and a screen reader still read the bar as it is written. */
  .topbar-right .primary-btn { order: -1; }

  /* ── 2b · One action: put it back on the title's line ───────────────────
     The stack above exists because a header with four controls cannot share
     a line with its title. Home has ONE control (Sync) and Calendar has one
     on a phone (Plan with Aluma - Day/Week, Go to and Plan my week are all
     hidden below). For those two, the second row was a whole band of empty
     screen with a 65px button parked at the left end of it, which reads as a
     button that fell off the title rather than as the page's action.

     So: title left, action right, one line, sharing a centre. Everything the
     scroller needs - the sideways bleed, the padding that pays it back, the
     scroll itself - is undone here, because there is nothing to scroll.

     Named per view on purpose rather than sniffed with `:has()`. What makes
     these two headers different is not their markup (Calendar's bar holds
     four buttons; three of them are display:none on a phone, which no
     selector can see) but a decision this very file makes about what a phone
     shows. The list of pages is the honest place to write that down, and it
     is the list §4 above already curates. Anything with more than one live
     control - Assignments, Settings, History - keeps the stack. */
  .view-school > .topbar,
  .view-calendar > .topbar {
    flex-direction: row;
    align-items: center;
    column-gap: 12px;
  }
  .view-school > .topbar > .topbar-left,
  .view-calendar > .topbar > .topbar-left {
    flex: 1 1 auto;
    /* A long subline ("9 open · 20 with no due date") drops under the title
       instead of pushing the action off the screen. `.topbar-left` already
       wraps; it just needs to be allowed to shrink to the point where it
       does. */
    min-width: 0;
  }
  /* And when it does wrap, it wraps evenly rather than leaving "shown)" alone
     on a line of its own. Ignored where unsupported, which costs nothing. */
  .view-school > .topbar > .topbar-left > .muted,
  .view-calendar > .topbar > .topbar-left > .muted { text-wrap: balance; }
  .view-school > .topbar > .topbar-right,
  .view-calendar > .topbar > .topbar-right {
    flex: 0 0 auto;
    width: auto;
    justify-content: flex-end;
    overflow: visible;
    margin-inline: 0;
    padding-inline: 0;
    /* Nothing to scroll here, so nothing to hint at - and a mask on a row
       that ends in a button would fade the button. */
    -webkit-mask-image: none;
    mask-image: none;
  }

  /* ══════════════════ 3 · ONE COLUMN, EVERYWHERE ════════════════════════ */
  /* Home's four shortcut tiles ask for `minmax(140px, 1fr)` tracks, which on
     a 364px board is two columns of 178px. Each tile is itself a three-column
     grid - icon, label, arrow - so the label gets 56px and "Nothing due"
     renders as "Nothin / g due". One track. The tiles are a list on a phone. */
  .dash-actions { grid-template-columns: minmax(0, 1fr) !important; }
  .dash-act-text { min-width: 0; }
  .dash-act-text > * { overflow-wrap: anywhere; }

  /* The widget board itself already collapses to one column at 680px
     (home-widgets.css), which is below every phone width - nothing to do. */

  /* ══════════════════ 4 · CALENDAR: THE DAY IN FRONT OF YOU ═════════════ */
  /* Week view is seven columns of nothing on a phone. The switch that offers
     it goes, and app.js pins the mode to `day` so a stored preference cannot
     bring it back. The week stepper goes with it. */
  .cal-modes,
  .cal-weeknav { display: none !important; }

  /* "Go to" is a jump-to-assignment picker, which is the Work tab with extra
     steps. "Plan my week" lays out a week in one tap without a word from the
     student - fine on a laptop next to the grid it rearranges, but on a phone
     it is a second planning button that produces a plan you then cannot edit
     by hand. One route: Plan with Aluma, and you tell it what you want. */
  #cal-goto,
  #cal-autoplan { display: none !important; }

  /* That route is a conversation, so the dock gets the room to be one: the
     mic and the field are both thumb-sized, and the field says out loud that
     talking is an option, because a microphone next to a text box reads as
     dictation-into-the-box rather than as "just say it". */
  .cal-dock { padding-bottom: max(10px, env(safe-area-inset-bottom, 0px)); }
  .cal-dock-form { gap: 8px; }
  .cal-dock-mic { width: var(--tap-min, 44px); height: var(--tap-min, 44px); }
  .cal-dock-form #cal-dock-input { min-height: var(--tap-min, 44px); font-size: 16px; }
  .cal-dock-form .primary-btn { min-height: var(--tap-min, 44px); }

  /* Due and Planner are two columns on a desktop and two SECTIONS stacked on
     a phone, which means scrolling past all of one to reach the other. One at
     a time instead, with the switch below promoted out of week view to
     choose. `both` is not offered on a phone - see app.js. */
  body.is-phone[data-cal-mode="day"] .cal-shows { display: inline-flex !important; }
  body.is-phone[data-cal-mode="day"] .cal-shows [data-cal-shows="both"] { display: none; }
  body.is-phone[data-cal-mode="day"][data-cal-shows="due"]  .cal-col-plan,
  body.is-phone[data-cal-mode="day"][data-cal-shows="plan"] .cal-col-due { display: none !important; }
  /* Late work is work that was due. It belongs to the Due side, and repeating
     it over the planner would be the second thing on a screen that is meant
     to hold one. */
  body.is-phone[data-cal-mode="day"][data-cal-shows="plan"] .cal-overdue { display: none !important; }

  /* The selector bar: ruler, then the Due/Planner switch and Today on one
     row under it. Both are thumb-sized. */
  .cal-selbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 8px max(14px, env(safe-area-inset-left, 0px));
  }
  .cal-selbar .cal-ruler { flex: 1 1 100%; order: 1; }
  .cal-selbar .cal-shows { flex: 1 1 auto; order: 2; }
  .cal-selbar .cal-shows button { flex: 1 1 0; justify-content: center; }
  .cal-selbar .cal-now { order: 3; flex: 0 0 auto; min-height: var(--tap-min, 44px); }

  /* With one section on screen the card can use the full width. */
  .cal-day { grid-template-columns: minmax(0, 1fr) !important; }

  /* ══════════════════ 5 · HOME IS A DASHBOARD, NOT AN EDITOR ════════════ */
  /* The Customize pill floats over the board at every moment, not only while
     editing - on a 390px screen that put a permanent control across the
     middle of the last tile a student can see. A phone is where you READ the
     board; the laptop is where you arrange it.

     The tray and its scrim go with the pill because the pill was the only way
     into them, and a sheet with no door is dead weight in the DOM. The pill
     itself sits in Home's header now (#hw-customize) and goes the same way. */
  #hw-customize,
  .hw-bar,
  .hw-tray,
  .hw-scrim { display: none !important; }

  /* The board reserved a floor for a pill that is no longer there
     (--hw-bar-h is that pill plus its inset). It needs no floor of its own
     now: the element that actually scrolls Home is `.school-shell.dash-shell`,
     and dashboard.css already pads THAT by --mnav-clear so the last tile
     clears the bottom bar. Keeping both stacked 142px of dead grey under the
     final widget - measured, at the end of the scroll. */
  .hw-board { padding-bottom: 0 !important; }

  /* The per-tile resize handle has no door either, and a resize handle in a
     single content-sized column never meant anything. */
  .hw-resize { display: none !important; }

  /* ══════════════════ 6 · FOUR DESTINATIONS, AND SETTINGS ═══════════════ */
  /* switchView refuses everything else outright (PHONE_VIEWS in app.js). These
     rules are the other half of that: a control that cannot go anywhere should
     not be on the screen offering to. A guard with no matching UI is a page
     full of buttons that bounce you Home.

     The More sheet keeps its Set up and Account groups and loses every
     destination marked `data-mm-study` - the ones you open to WORK on
     something rather than to check it. */
  .mobile-more-sheet [data-mm-study] { display: none !important; }

  /* Home's shortcut row offers Mastery and "ask your first question", which
     are Mastery and Chat. Assignments and Calendar stay: those are tabs. */
  .dash-act[data-hub-view="learn"],
  .dash-act[data-action="question"] { display: none !important; }

  /* The Ask box and its prompt chips are a tutoring session with a text field
     in front of it. Planning still talks to Aluma - that is what the dock on
     Calendar is - but a phone is not where you sit down to be taught. */
  .dash-ask,
  .dash-ask-chips { display: none !important; }

  /* Every remaining button that STARTS a session rather than showing you
     something. "Your next step" is the worst of them: its six actions include
     a tutoring modal, Mastery and a focus() on the Ask box that is no longer
     on the page, and the two lines above it already say the same thing in
     words ("1 thing due today." / "Physics is first - due today."). Nothing
     unique goes with it - "Connect your school", the one CTA it carries that
     matters, is also the To-do widget's empty state and Work's. */
  #hub-next-action,
  .dash-btn-tutor,
  [data-hub-tutor],
  .task-card [data-act="run"],
  .task-card [data-act="help"],
  #td-start,
  .cal-detail-acts [data-cal-act="tutor"] { display: none !important; }

  /* A class name is a door to the class page, which a phone does not open
     (switchView sends it Home). Here it is a label: a tap on a card's chip
     falls through to the card and opens the assignment. renderTaskCard and
     openTaskDetail drop the button role at this width too; this rule covers
     a rotation into it. */
  .task-card .task-subject[data-open-class],
  #td-course.is-link { pointer-events: none; cursor: default; }

  /* The rest of Home's doors into rooms a phone does not have. "Pick up where
     you left off" is nothing but those doors (a chat, a tutor run, a Mastery
     review), so the whole tile goes, History link and all. A class card opens
     the class page, which is Classes; it stays as a read-out of the grade and
     what is next, and stops pretending to be a button. */
  .hw-widget[data-w-type="continue"],
  .hw-head-action[data-hub-view="projects"],
  .hw-head-action[data-hub-view="history"],
  .hw-head-action[data-hub-view="learn"],
  .hw-head-action[data-hub-view="test-prep"],
  .hw-head-action[data-hub-view="grades"],
  [data-dash-new-class] { display: none !important; }
  .dash-class-card { pointer-events: none; cursor: default; }

  /* Settings is account, device and setup. Memory is study context,
     Capabilities is the whole Learning group, and Lumi Anywhere sets up a
     desktop browser extension - none of the three is a thing you do from a
     phone. The Learning heading goes with its only row, or it sits over
     nothing. Sign out, reset and delete account all live in the block below
     the tabs and stay exactly where they were. */
  .settings-tab[data-tab="memory"],
  .settings-tab[data-tab="capabilities"],
  .settings-tab[data-tab="desktop"],
  .settings-tabs-divider[data-settings-group="learning"] { display: none !important; }

  /* ══════════════════ 7 · ONE MOVEMENT BETWEEN TABS ═════════════════════ */
  /* The lens glides between tabs in 420ms and the page under it used to cut.
     Two different answers to one gesture reads as a glitch, so the incoming
     page travels with the lens: same curve, same duration, and the direction
     is the direction the lens is going. It slides, it does not scroll - the
     view keeps whatever scroll position it was left at.

     `will-change` is deliberately absent: these sections are the whole page,
     and promising a layer for each of them costs more than the animation. */
  @keyframes mnav-slide-from-right {
    from { opacity: 0; transform: translate3d(7%, 0, 0); }
    to   { opacity: 1; transform: translate3d(0, 0, 0); }
  }
  @keyframes mnav-slide-from-left {
    from { opacity: 0; transform: translate3d(-7%, 0, 0); }
    to   { opacity: 1; transform: translate3d(0, 0, 0); }
  }
  /* `body.is-phone` is load-bearing, not decoration. Four sheets already give
     `.view` an entry animation, and the two that matter are written as
     `body[data-view="calendar"] .view-calendar` - (0,2,1) against the (0,2,0)
     of a plain `.view.mnav-slide-r`, which loses on the type column and left
     the page fading in place while the lens slid. The body selector buys back
     that column and one more. */
  body.is-phone .view.mnav-slide-r { animation: mnav-slide-from-right 420ms var(--ease-lens) both; }
  body.is-phone .view.mnav-slide-l { animation: mnav-slide-from-left  420ms var(--ease-lens) both; }

  /* ══════════════════ 8 · NO HORIZONTAL BLEED ═══════════════════════════ */
  /* Test Prep's exam pills are `flex: 0 0 auto; white-space: nowrap` so the
     row wraps to a second line rather than squeezing one - correct at every
     width except the one where a single pill ("AP United States History") is
     wider than the whole screen. At 320px two of them hung 25px past the
     right edge. On a phone a long pill wraps its own label instead. */
  .tp-pill {
    max-width: 100%;
    min-width: 0;
    white-space: normal;
    text-align: left;
    padding-block: 8px;
  }

  /* The header's action row is the one element allowed to scroll sideways. */
  html, body { overflow-x: hidden; }
  .view, .main { overflow-x: hidden; max-width: 100%; }

  /* ══════════════════ 9 · SETTINGS IS ONE COLUMN ════════════════════════ */
  /* Every Settings row is a flex pair - label on the left, control on the
     right - and soft.css pins the control at `flex: 0 1 300px`. On a 390px
     screen that leaves the label whatever is over. Measured: "Full name" and
     "Time zone" render 42px wide and wrap to two lines of four characters,
     "How should Aluma respond?" takes three lines of eight, and the note
     under Reset wraps to eight. Eleven rows do this, which is most of the
     page, and it is the page a new student sees first.

     A phone has one column. The label goes above its control and both get
     the width, which is what the rest of the app already does at this size.

     `!important` to match, because the declarations being overridden carry
     it - see soft.css, which explains why it needed it. The selector shape
     is copied from there for the same reason `.topbar-right select` is
     copied above: specificity is read before load order. */
  body[data-view="settings"] .set-row {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-2, 8px) !important;
  }
  body[data-view="settings"] .set-input,
  body[data-view="settings"] .set-readonly,
  body[data-view="settings"] select.set-input,
  body[data-view="settings"] .set-textarea {
    flex: 1 1 auto !important;
    max-width: none !important;
  }
  /* The row's action button keeps its own width rather than stretching to
     the full screen - a full-bleed "Delete account" is a bigger target than
     that particular button wants. */
  body[data-view="settings"] .set-row > .ghost-btn,
  body[data-view="settings"] .set-row > .primary-btn { align-self: flex-start; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   A PHONE KEYBOARD MUST NOT ZOOM THE PAGE

   iOS zooms in whenever it focuses a text field with a font-size under 16px,
   and it does not zoom back out. Measured at 390px, 19 fields did this:
   both search boxes, the connector search, the two Assignments selects, and
   every field in the personalisation form. A student taps "Full name", the
   page jumps, and they pinch their way back - once per field.

   `max(16px, 1em)` leaves the design alone wherever the rule does not apply
   and lifts only the phone to the threshold.

   Two things had to be right for this to land, and neither was obvious:

     - `input[type="text"]` does not match `<input class="set-input"
       id="pref-full-name" />`. Most of the personalisation form declares no
       type at all, so a type-scoped selector skipped it. Matching `input` and
       excluding the types that must NOT grow is the way round that.

     - It has to live here. The first version sat in feel.css, which loads
       before this file, so `.topbar-right select { font-size: 13px }` a few
       hundred lines up beat it and the Assignments selects kept zooming.

   Keyed on a coarse pointer rather than width, because a touch tablet zooms
   the same way a phone does.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (hover: none) and (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]),
  textarea,
  select,
  /* Named because a bare `select` is specificity (0,0,1) and loses to the two
     rules that actually size these: `.set-input` at redesign.css:670 and
     `.topbar-right select` in the block above. Matching their shape rather
     than out-escalating them means this wins on load order alone, and adds no
     !important to a cascade that already has thousands. */
  .set-input,
  .set-textarea,
  .topbar-right select {
    font-size: max(16px, 1em);
  }
  /* A row that holds one of those selects sets its buttons in the same type,
     or "+ Add assignment" stood at 13px beside "By due date" at 16. The
     ghost button's 13px is !important in styles.css (.topbar .ghost-btn), so
     this answers it in kind; it reaches only a row with a select in it. */
  .topbar-right:has(> select) > :is(.ghost-btn, .primary-btn) {
    font-size: max(16px, 1em) !important;
  }

  /* The handful of controls a four-point hit test at 390px could not reach.
     Measured, not assumed - a broader pass at this was tried and moved the
     count by one, so it was taken back out.

     Both of these are written to match the shape of the rule that sizes them,
     because loading later is not enough: specificity is read first. The bare
     `.dash-btn-done` (0,1,0) lost to `.dash-assignment-actions button`
     (0,1,1) in dashboard.css and the button stayed 36px tall; the bare
     `.settings-tab` lost to a `body[data-view="settings"]` !important rule
     and stayed 34px. The same lesson as `.topbar-right select` above, twice
     more. */
  .dash-assignment-actions .dash-btn-done {
    min-width: var(--tap-min, 44px); min-height: var(--tap-min, 44px);
  }
  body[data-view="settings"] .settings-tab {
    min-height: var(--tap-min, 44px) !important;
  }

  /* 27px, and its miss is not harmless - it is the only control in a widget
     header. */
  .hw-head .hw-head-action { min-height: var(--tap-min, 44px); }

  /* The class name on a To-do row is plain text now - see the note in
     app.js where the row is built. One row, one destination. */

  /* Controls a few pixels short of a thumb. Each was measured, and each is
     a real control a student presses: the filter pills on Assignments, the
     connector chips and their search box, Settings' back button and its two
     row actions, the Home quick-action tiles, the storage notice's own link.
     `min-height` rather than padding, so nothing reflows around them. */
  /* An inline <a> has no height to set: its box follows its font. */
  #storage-notice p a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-min, 44px);
  }
  .set-row .set-toggle { min-width: var(--tap-min, 44px); }

  .filter-pill,
  .conn-filter-chip,
  /* 53x30 and 65x30, measured at 390px. "Done" and "Not yet" are the ONLY way
     a student closes an overdue row on a platform that does not report
     submissions back (Schoology, Moodle, Sakai, Brightspace, Blackboard,
     Blackbaud - see renderConfirmStrip in app.js), so a miss leaves stale
     "missing" work on the list with nothing else in the app to ask again.
     `.cs-acts button` matches the shape of styles.css's own (0,1,1) rule; a
     bare `.cs-yes` at (0,1,0) would lose to it however late this file loads. */
  .cs-acts button,
  .dash-actions .dash-act,
  .hw-body .dash-actions .dash-act,
  #settings-back,
  #set-redo-onboard,
  #set-goal-edit,
  #reset-btn,
  input#tasks-search,
  input#conn-search-input,
  input#settings-search {
    min-height: var(--tap-min, 44px);
  }

  /* ── The tap floor reaches what COVERS the page ────────────────────────
     The list below and the one above name controls one at a time, and every
     one of them is on a view. Nothing on a modal, a sheet or an overlay was
     ever added, so the app's own 44px floor stopped at the edge of whatever
     was open: the password eye at 32x32, "Skip" at 53x33, "Forgot password?"
     at 112x18, "Resend code" at 73x17. Forty-two of them, on the surfaces a
     student meets FIRST - sign in, verify, the tour.

     Named as a class of surface rather than as forty-two more ids, because
     the next modal should inherit this instead of being added to a list.
     `a[id]` is the honest test for "a link that is really a control here":
     every one of these is wired in JS and carries no href.

     Matched the way the audit matches - by id as well as by class - because
     the three Test Prep overlays carry their name in an id and nothing else,
     and a rule that knows about `.modal-backdrop` only would have left
     "Pause", "End" and "Close" at 37px on the one surface in the app a
     student is sitting an exam in. */
  :is(.modal-backdrop, .palette-backdrop, .auth-overlay, .verify-overlay,
      .onboard-overlay, .tutorial-overlay, .paywall-overlay,
      [id$="-overlay"], [id$="-backdrop"], [id$="-sheet"],
      [role="dialog"], [aria-modal="true"])
    :is(button, a[id], input, select, textarea):not([disabled]) {
    min-height: var(--tap-min, 44px);
  }
  /* An inline box ignores min-height - its height follows its font - so a
     link needs a box that does not. `inline-flex` keeps it on the line it is
     written on, so "Didn't get it? Resend code" stays one sentence, and
     gives the finger the other 27px. Same surface list as the rule above, or
     the two disagree about which links they cover. */
  :is(.modal-backdrop, .palette-backdrop, .auth-overlay, .verify-overlay,
      .onboard-overlay, .tutorial-overlay, .paywall-overlay,
      [id$="-overlay"], [id$="-backdrop"], [id$="-sheet"],
      [role="dialog"], [aria-modal="true"])
    :is(a[id], a:not([href])) {
    display: inline-flex;
    align-items: center;
  }

  .proof-close,
  .conn-browser-x,
  .lumi-icon-btn,
  .lumi-promo-dismiss,
  .feedback-survey-close,
  #mobile-more-close,
  #cal-dock-close {
    min-width: var(--tap-min, 44px);
    min-height: var(--tap-min, 44px);
    display: grid;
    place-items: center;
  }
}

/* ── Nothing lives underneath the fixed chrome ───────────────────────────────
   Two pieces of furniture float over the bottom of a phone screen: the tab
   bar (--mnav-clear, which most views already reserve) and the one-time
   storage notice. The notice reserved nothing at all. Measured at 390x844 it
   is 159px tall and sits 16px up, so it covers the bottom 231px, against the
   60-96px of padding these scrollers carry - which put the last assignment
   card, the last connector tile and two Settings fields out of reach until it
   was dismissed. A student who has not tapped "Got it" yet is every student
   on their first visit.

   --storage-notice-h is published by the notice's own script (index.html), so
   this follows the real box through a rotation or a copy change rather than
   guessing at it. */
@media (max-width: 900px) {
  /* Connectors reserved 60px for a 96px tab bar - the last tile was under it
     even with the notice dismissed. */
  .view-connectors { padding-bottom: var(--mnav-clear, 96px); }

  html.has-storage-notice .school-shell.dash-shell,
  html.has-storage-notice #tasks-body,
  /* styles.css:30686 sets this one with a `body[data-view="settings"]`
     !important shorthand, which out-specifies a bare class. Match its shape
     rather than shouting louder. */
  html.has-storage-notice body[data-view="settings"] .settings-pane,
  html.has-storage-notice .view-connectors,
  html.has-storage-notice .cal-col-body {
    padding-bottom: calc(var(--mnav-clear, 96px)
                         + var(--storage-notice-h, 0px) + 16px) !important;
  }
  /* The signed-out cards (sign-in, a family invite's welcome) open on a
     first visit, with the notice up: they scroll their buttons clear of it,
     and a control that takes focus stops above it, not under it. The notice
     stands 72px off the bottom here (redesign.css), so that counts too. */
  html.has-storage-notice .auth-overlay {
    padding-bottom: calc(var(--storage-notice-h, 0px) + 72px + env(safe-area-inset-bottom, 0px) + 32px);
    scroll-padding-bottom: calc(var(--storage-notice-h, 0px) + 72px + env(safe-area-inset-bottom, 0px) + 16px);
  }
}

