/* ============================================================================
 * mobile.css — the phone layout.
 *
 * EVERY rule is scoped under `.is-mobile`, set by mobile.js only when the mobile
 * view is active, so a desktop session cannot be reached by anything here.
 *
 * WRITTEN AGAINST THE APP'S REAL CLASS VOCABULARY, which was measured, not
 * assumed. The first cut of this file targeted .app-shell, .sidebar, .dlg,
 * .modal-actions, .toolbar, .pane — none of which exist. Those rules matched
 * nothing and the phone kept rendering the desktop shell, while every static
 * check passed, because CSS that matches nothing is silence rather than an
 * error. test/mobile-selectors-test.js now fails the build if this file names a
 * class the app never renders. Add to this file, then run that test.
 *
 * The real vocabulary, by frequency of use in the JS:
 *   .row (507) .card (213) .pad (196) .fld (176) .grid (55) .tag .btn .data
 *   shell: .app > aside.side + .nav-backdrop + main.main > .topbar + #view.view
 *   drawer: .app.mobile-nav-open .side   (already exists — do not reinvent it)
 *   dialog: .modal-back > .modal > .modal-head + .modal-body + .modal-foot
 *
 * The two rules everything follows from:
 *   1. NOTHING OVERLAPS — no absolute positioning that can collide, and any bar
 *      RESERVES its space rather than floating over the content.
 *   2. EVERY TAP TARGET >= 44px — a fingertip is that big, and a mis-tap in a
 *      marks screen is worse than a mis-tap in most apps.
 * ==========================================================================*/

/* ---- 1. the shell -------------------------------------------------------- */

/* One runaway wide table must never make the whole PAGE scroll sideways — that
   is what makes a phone layout feel broken even when everything else is right. */
.is-mobile, .is-mobile body { overflow-x: hidden; max-width: 100vw; }
.is-mobile .app { display: block; max-width: 100vw; overflow-x: hidden; }

/* The drawer already exists (.app.mobile-nav-open .side). Only widen the hit
   area and make its rows finger-sized; the open/close mechanics are untouched. */
.is-mobile .side { width: min(84vw, 320px); z-index: 60; }
.is-mobile .side a, .is-mobile .side button { min-height: 48px; }
.is-mobile .nav-backdrop { z-index: 59; }

.is-mobile .main { margin-left: 0; width: 100%; max-width: 100vw; }
.is-mobile .view { padding: 10px 10px calc(20px + env(safe-area-inset-bottom)); max-width: 100vw; overflow-x: hidden; }

/* THE TOP BAR IS THE PAGE-LEVEL SIDEWAYS SCROLL.
   style.css gives .topbar a FIXED `height: 52px` and `display: flex`, and it
   carries a lot — school name, buy-tokens, quick actions, role switcher, sync
   dot, avatar. On a 375px screen those measure ~640px, so with no wrapping they
   run off the edge and drag the whole PAGE sideways: the app appears shifted
   left with its content cut off. My first version said `flex-wrap: nowrap`,
   which guaranteed it.
   It is `position: sticky`, not fixed, so letting it wrap simply makes it taller
   and pushes content down — nothing can end up underneath it. Height must become
   auto or the second row is clipped by the 52px. */
/* ONE LINE. Wrapping fixed the sideways scroll but traded it for a 211px header —
   four stacked rows of chrome before any content, which is most of a phone screen
   before you have read anything. So the bar stays a single 52px line and SCROLLS
   WITHIN ITSELF: every control stays reachable (clipping them would be worse than
   the overflow it replaces), the page never moves, and swiping the bar is the
   natural gesture for it. */
.is-mobile .topbar {
  flex-wrap: nowrap;
  height: 52px; min-height: 52px;
  gap: 6px; padding: 6px 8px;
  overflow-x: auto; overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;                 /* the bar is swiped, not scrollbar-dragged */
}
.is-mobile .topbar::-webkit-scrollbar { display: none; }
.is-mobile .topbar > * { flex: 0 0 auto; }
.is-mobile .topbar h1 { flex: 0 1 auto; font-size: 15px; max-width: 46vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.is-mobile .topbar button { flex: 0 0 auto; min-width: 44px; min-height: 40px; }
/* .spacer is flex:1 on desktop to push controls right; in a scrolling bar it
   would stretch to infinity and push everything else out of reach. */
.is-mobile .topbar .spacer { display: none; }

/* The global search claims width:100% inline, so on a phone it ate the whole bar
   and forced everything else onto later rows. Collapsed to an icon-sized field
   that expands when tapped — the placeholder starts with 🔍, so a 44px box still
   reads as "search". */
.is-mobile #gsInput {
  width: 44px !important; flex: 0 0 auto;
  padding-left: 8px; transition: width .18s ease;
}
.is-mobile #gsInput:focus { width: min(70vw, 320px) !important; flex: 0 0 auto; }

/* ---- 2. tap targets ------------------------------------------------------ */

.is-mobile button, .is-mobile .btn,
.is-mobile input, .is-mobile select, .is-mobile textarea {
  min-height: 44px;
  font-size: 16px;              /* under 16px makes iOS zoom the whole page */
  line-height: 1.25;
}
/* 44 IS A SQUARE, NOT A HEIGHT.
   A button is padding-sized, so a one-glyph label — "⋯", "🎙", "－", "Fit" —
   comes out 37-43px WIDE against a 44px-tall body. It reads as a full-size
   button and is not one, which is the worst kind of miss: nothing looks wrong.
   Four of the five phone layouts for the core screens each discovered this
   separately and each wrote its own inline `min-width:44px`; the fifth did not,
   and shipped a 42.5px microphone on the marks screen. One rule in the kit is
   the answer — the exemptions below (round dots, tick boxes) come after it and
   win, which is why this cannot be stated any later in the file. */
.is-mobile button, .is-mobile .btn { min-width: 44px; }
/* The size modifiers stay visually smaller but keep a full-size hit area. */
.is-mobile .btn.sm, .is-mobile .btn.xs { min-height: 44px; padding: 10px 14px; font-size: 15px; }

/* CIRCLES MUST STAY CIRCLES.
   A carousel pager dot is an 8x8 <button> with border-radius:50%. The blanket
   44px min-height above stretched it to 8 WIDE by 44 TALL — a vertical oval, on
   every screen with a pager. The rule was right in intent and wrong in reach: a
   deliberately tiny circular control needs a bigger HIT AREA, not a bigger body.
   So the visual size is restored and a transparent ::after gives it a ~40px
   target — larger to the thumb than before, while still round to the eye. */
.is-mobile .wpop-dot,
.is-mobile .wm-dots i {
  min-height: 0; min-width: 0;
  position: relative;
  aspect-ratio: 1;              /* squashed by a flex parent → still a circle */
  flex: 0 0 auto;
}
.is-mobile .wpop-dot::after { content: ''; position: absolute; inset: -16px; }
/* Any round control keeps its aspect under flex pressure — shrinking one axis is
   what turns a circle into an oval in the first place. */
.is-mobile .wpop-live-dot, .is-mobile .av { flex: 0 0 auto; aspect-ratio: 1; }

/* SAME MISTAKE, DIFFERENT CONTROL: a checkbox is ~13px square by default and
   `width: auto` (style.css:65), so the 44px floor above made it 13 WIDE by 44
   TALL — a blue column beside every band, division and column toggle in the app.
   A tick box is square or it is wrong. 22px is comfortably tappable and most of
   these sit inside a <label>, which is the real target anyway. */
.is-mobile input[type=checkbox], .is-mobile input[type=radio] {
  min-height: 0; width: 22px; height: 22px; flex: 0 0 auto;
}

/* ---- 2b. the drawer footer ----------------------------------------------- */

/* Theme · version · reset-menu sit in a `flex-wrap: wrap` footer. My 44px floor
   made each tall enough to wrap onto a second row, and the last one fell off the
   bottom of the drawer entirely — visible but unreachable, which is the worst of
   both. They are three small utilities, so they belong on ONE line: nowrap,
   tighter padding (the inline `padding:2px 8px` on the chip and the reset button
   needs !important to beat), and 40px rather than 44 because these are secondary
   controls in a drawer, not primary page actions.
   `· just now` is dropped: it is the widest part of the version chip and the
   least useful on a phone — the exact date is still in the chip's title, and
   tapping it opens the changelog either way. */
.is-mobile .side-foot { flex-wrap: nowrap; align-items: center; gap: 6px; padding: 8px; }
.is-mobile .side-foot > .btn {
  flex: 0 0 auto; white-space: nowrap;
  min-height: 40px; padding: 8px 10px !important; font-size: 13px;
}
.is-mobile .side-foot .btn .muted { display: none; }

/* ---- 3. rows wrap, they never squeeze ------------------------------------ */

/* .row is the app's flex row — 507 uses. Squeezing one is how two controls end
   up on top of each other, so on a phone it wraps instead. */
.is-mobile .row { flex-wrap: wrap; gap: 6px; align-items: center; }
/* A BUTTON IS AS WIDE AS ITS LABEL — nothing more.
   This used to say `flex: 1 1 auto`, which made every button in a row grow to
   fill the leftover space. On a row that wrapped, the last button landed alone
   on its own line and stretched the full width: a "✕ remove" turned into a
   345px empty slab, and three of them down a band list read as broken layout
   rather than as buttons. Growing was never the point — reaching 44px was, and
   the tap-target rule already does that. */
.is-mobile .row > .btn { flex: 0 1 auto; }
/* A grid of fixed columns becomes one column. */
.is-mobile .grid { grid-template-columns: 1fr !important; gap: 8px; }

/* ---- 4. text never lands on a control ------------------------------------ */

/* Long unbroken words — a school name, an email, a file name — are the usual
   reason text escapes its box and covers the button beside it. */
.is-mobile .card, .is-mobile .fld, .is-mobile td, .is-mobile th, .is-mobile label {
  overflow-wrap: anywhere;
}
.is-mobile .card { padding: 12px; }
.is-mobile .card.pad { padding: 12px; }
.is-mobile .fld { display: block; width: 100%; }
.is-mobile .fld input, .is-mobile .fld select, .is-mobile .fld textarea { width: 100%; }

/* ---- 5. tables ----------------------------------------------------------- */

/* A desktop table cannot be made to fit a phone; it can only be made to scroll,
   and a sideways-scrolling table is where "hard to tap" comes from. Opt a table
   into .m-cards and it restacks as one card per row, each cell labelled from its
   data-label. A grid that must stay a grid goes in .m-scroll, which contains the
   sideways scroll so the page itself never moves. */
.is-mobile table.m-cards, .is-mobile table.m-cards thead,
.is-mobile table.m-cards tbody, .is-mobile table.m-cards tr,
.is-mobile table.m-cards th, .is-mobile table.m-cards td { display: block; width: auto; }
.is-mobile table.m-cards thead { position: absolute; left: -9999px; }
.is-mobile table.m-cards tr {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 10px 12px; margin: 0 0 10px; background: var(--panel);
}
.is-mobile table.m-cards td {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 7px 0; border: 0; border-bottom: 1px solid var(--line); text-align: right;
}
.is-mobile table.m-cards td:last-child { border-bottom: 0; }
.is-mobile table.m-cards td::before {
  content: attr(data-label); font-weight: 600; color: var(--ink-soft);
  text-align: left; flex: 0 0 auto;
}
.is-mobile .m-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%; }
/* Any table not opted in still must not break the page. */
.is-mobile .view table { max-width: 100%; }

/* ---- 6. dialogs become bottom sheets ------------------------------------- */

.is-mobile .modal-back { align-items: flex-end; padding: 0; }
.is-mobile .modal {
  width: 100%; max-width: 100%; max-height: 92vh; margin: 0;
  border-radius: 16px 16px 0 0;
  display: flex; flex-direction: column;
}
.is-mobile .modal-body { overflow-y: auto; flex: 1 1 auto; -webkit-overflow-scrolling: touch; }
/* .modal-foot is the real footer class (not .modal-actions). Stacked, full-width
   buttons, primary nearest the thumb. */
.is-mobile .modal-foot {
  position: sticky; bottom: 0;
  display: flex; flex-direction: column-reverse; gap: 8px;
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
}
.is-mobile .modal-foot .btn { width: 100%; }

/* ---- 7. the mobile kit --------------------------------------------------- */

.is-mobile .m-list { display: flex; flex-direction: column; gap: 8px; }
/* `--panel`, not `--card`: no theme in this app defines a `--card` token
   (style.css and skins.css ship --panel, --panel-2, --line). A rule written
   against it computes to transparent, so the rows quietly lose their card
   surface — the kind of miss that looks like a design choice rather than a bug. */
.is-mobile .m-row {
  display: flex; align-items: center; gap: 12px;
  min-height: 56px; padding: 10px 12px;
  background: var(--panel);
  border: 1px solid var(--line); border-radius: 12px;
}
.is-mobile .m-row-main { flex: 1 1 auto; min-width: 0; }
.is-mobile .m-row-main b { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.is-mobile .m-row-sub { color: var(--ink-soft); font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.is-mobile .m-row-end { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; }

/* A page action (Save / Add) spanning the width. It RESERVES its space via
   .has-actionbar below — a floating bar over the last row of content is exactly
   the "I can't tap it" complaint. */
.is-mobile .m-actionbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 49;
  display: flex; gap: 8px;
  padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
  background: var(--panel); border-top: 1px solid var(--line);
}
.is-mobile .m-actionbar .btn { flex: 1 1 auto; }
.is-mobile.has-actionbar .view { padding-bottom: calc(84px + env(safe-area-inset-bottom)); }

/* ---- 8. show / hide ------------------------------------------------------ */
.is-mobile .desktop-only { display: none !important; }
.is-mobile .m-only { display: revert; }
.m-only { display: none; }

/* ---- 9. compact ---------------------------------------------------------- */

/* A phone screen is a fifth of a desktop one, so desktop breathing room reads as
   holes: a heading with 16px above AND below it, a paragraph with another 16,
   and by the third one you are scrolling past emptiness to find the next
   control. Everything here is spacing only — nothing moves, hides or resizes. */
.is-mobile .view h1 { font-size: 21px; margin: 6px 0 4px; }
.is-mobile .view h2 { font-size: 18px; margin: 10px 0 4px; }
.is-mobile .view h3 { font-size: 16px; margin: 8px 0 4px; }
.is-mobile .view h4 { font-size: 15px; margin: 8px 0 3px; }
.is-mobile .view p { margin: 3px 0 8px; }
.is-mobile .view hr { margin: 10px 0; }
/* The stack of cards down a page — the single biggest source of dead space. */
.is-mobile .view > .card + .card, .is-mobile .view > .row + .card { margin-top: 8px; }
.is-mobile .m-list { gap: 6px; }
.is-mobile .m-row { min-height: 52px; padding: 8px 10px; gap: 10px; }
.is-mobile table.m-cards tr { padding: 8px 10px; margin: 0 0 8px; }
.is-mobile table.m-cards td { padding: 6px 0; }
/* .sm/.xs buttons keep the 44px hit area from section 2 but stop being wide. */
.is-mobile .btn.sm, .is-mobile .btn.xs { padding: 8px 12px; }
/* A bare form control is 44px tall already; desktop padding on top of that just
   makes it taller than the text inside it. */
.is-mobile input, .is-mobile select { padding: 8px 10px; }
/* Helper text under a heading belongs WITH the heading, not floating between. */
.is-mobile .muted.small { line-height: 1.35; }
