/* ==========================================================================
   DSA Atlas — mobile layer
   ==========================================================================

   WHY THIS IS A SEPARATE FILE
     The desktop layout was already right, and the brief was to fix the phone
     without touching it. Editing css/app.css could not have delivered that as a
     guarantee, only as a promise. So every mobile rule lives here instead, and
     two things are now checkable by machine rather than by eye:

       1. css/app.css is byte-identical to its recorded baseline.
       2. Every rule in THIS file sits inside a `@media (max-width: ...)` block,
          so none of it can reach a wide viewport.

     `node tools/check-mobile-css.js` enforces both. If you merge this file back
     into app.css, you throw that guarantee away — don't.

   THREE BREAKPOINTS, DELIBERATELY
     900px  the drawer tier. Matches the existing breakpoint in app.css where the
            sidebar becomes off-canvas, so the two cannot disagree.
     620px  the phone tier. Where two-column label/value rows stop fitting.
     380px  small phones. Only spacing and one font size; no layout changes.

     app.css had grown to seven near-duplicate breakpoints (700/720/760/780/900/
     1020/1160), which is why the page collapsed in three visible stages between
     700 and 760px. This file does not add to that pile, and the check tool warns
     if a fourth value appears here.

   LOADED AFTER app.css, so equal-specificity rules here win. Several rules below
   depend on that rather than on `!important`.
   ========================================================================== */


/* ==========================================================================
   TIER 1 — 900px and below: the drawer tier
   ========================================================================== */
@media (max-width: 900px) {

  /* Stop iOS inflating text in landscape. Mobile-only concern, which is why it
     was never needed in the desktop file. */
  html {
    -webkit-text-size-adjust: 100%;
  }

  /* One token, needed by the table scroll shadows below and defined here rather
     than in app.css because that file is frozen. Declared inside the breakpoint,
     so it does not exist at desktop widths — nothing there consumes it.
     Both values are dark ink; in dark mode the surface is dark too, so the alpha
     has to climb for the shadow to register at all. */
  :root {
    --scroll-ink: rgba(21, 23, 30, .16);
  }
  [data-theme='dark'] {
    --scroll-ink: rgba(0, 0, 0, .55);
  }

  /* ---------- the company-tag percentage ----------
     js/app.js emits the frequency in `<span class="cochip-pct" hidden>`. The
     `hidden` attribute keeps it invisible at every width via the user-agent
     stylesheet, which is what leaves the desktop table exactly as it was. This
     author rule outranks the UA rule and brings it back on mobile, where the
     `title` tooltip that used to hold it cannot be opened by a finger. */
  .cochip-pct[hidden] {
    display: inline;
  }

  /* ---------- the off-canvas drawer ----------
     app.css already slides `.side` in and out. What it does not do is make the
     closed drawer inert, and that was the worst accessibility bug on the page:
     the panel was hidden by `transform` alone, so all ~130 nav links stayed in
     the tab order and in the accessibility tree while it was shut. A keyboard or
     screen-reader user on a phone had to walk the entire sidebar before reaching
     the content.

     `visibility: hidden` fixes it without JavaScript, and it is why no focus trap
     is needed: there is nothing focusable behind the veil to trap focus against.
     The delay on the visibility transition lets the slide-out finish before the
     panel disappears; without it the drawer vanishes instantly and the animation
     is lost. */
  .side {
    visibility: hidden;
    transition: transform .26s var(--ease), visibility 0s linear .26s;
    /* 100vh is the LARGE viewport height on mobile, so the foot of the drawer
       sat behind the browser toolbar and the last nav items could not be
       reached. dvh tracks the visible area. The 100vh line stays first as the
       fallback for anything that does not know dvh. */
    height: 100vh;
    height: 100dvh;
  }
  .side.open {
    visibility: visible;
    transition: transform .26s var(--ease), visibility 0s;
  }

  /* app.css animates the slide. Since this file has taken ownership of the
     `transition` declaration on `.side`, it also has to honour the reduced-motion
     preference that app.css's own block does not cover. */
  @media (prefers-reduced-motion: reduce) {
    .side,
    .side.open,
    .side-veil {
      transition: none;
    }
  }

  /* Momentum scrolling, and stop an overscroll at the end of the nav from
     scrolling the page underneath it. */
  .side-nav {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    /* Clears the home bar on gesture-nav phones. Harmless where there is none:
       env() resolves to 0. */
    padding-bottom: calc(34px + env(safe-area-inset-bottom, 0px));
  }

  /* ---------- the backdrop ----------
     js/app.js toggles `.on` and the `hidden` attribute together. The [hidden]
     rule has to be here and has to come after the base rule: giving .side-veil a
     `display` value inside a media query beats the user-agent stylesheet's
     `[hidden] { display: none }`, and without this line the veil would be stuck
     on screen permanently. */
  /* No opacity transition here on purpose. Fading it would mean the JS could not
     move `hidden` in the same task as the drawer's other three pieces of state,
     and a synchronous state machine is worth more than a fade on a backdrop —
     the reasoning is written out at navVeil() in js/app.js. */
  .side-veil {
    position: fixed;
    inset: 0;
    z-index: 80;                 /* under .side (90), over the topbar (40) */
    background: rgba(21, 23, 30, .42);
    pointer-events: none;
  }
  .side-veil.on {
    pointer-events: auto;
  }
  /* Redundant against the rules as they stand — nothing here gives .side-veil a
     `display`, so the user-agent `[hidden] { display: none }` already applies. Kept
     as a guard: an author `display` added to the rule above would outrank the UA
     rule and strand the veil on screen permanently, and that failure would be
     confusing to diagnose. */
  .side-veil[hidden] {
    display: none;
  }

  /* Scroll lock. Scoped to this breakpoint on purpose: if a stale `nav-open`
     ever survived a resize to desktop it would lock the whole site, and here it
     simply stops applying. `position: fixed` on the body was rejected — it loses
     scroll position on every open. */
  body.nav-open {
    overflow: hidden;
  }

  /* ---------- touch targets ----------
     31x31 was the size of the menu button, which is the primary navigation
     control on a phone. 42px is inside Apple's 44pt guidance once the tap slop
     around a button is counted, and it does not visually dominate a 56px bar. */
  .iconbtn {
    width: 42px;
    height: 42px;
    font-size: 15px;
  }
  .topbar-right {
    gap: 2px;
  }
  /* Kill the grey flash Android draws over a tapped control; the CSS :active and
     focus styles already give feedback. */
  .iconbtn,
  .btn,
  .searchbtn,
  .nav-item,
  .nav-link,
  .nav-group > summary,
  .sec-fold > summary,
  .trow,
  .rel a,
  .samplebtn,
  .copybtn,
  .seg button,
  .pal-item {
    -webkit-tap-highlight-color: transparent;
  }

  /* #searchBtn receives focus the moment the drawer opens, so it needs to look
     focused or opening the drawer appears to do nothing for a keyboard user. */
  .searchbtn:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
    border-color: var(--primary);
  }

  /* ---------- the iOS zoom-on-focus trap ----------
     Any input or textarea under 16px makes iOS Safari zoom the page when it gains
     focus, and it never zooms back out. app.css sets this textarea to 14.5px.

     This belongs in the 900px tier, not the phone tier: an iPhone Pro Max in
     landscape is 932px wide in CSS pixels, so a phone-only rule would have missed
     the exact device most likely to hit it. The palette input is already 16px in
     app.css and is deliberately left alone. */
  .finder-in textarea {
    font-size: 16px;
  }

  /* ---------- tap feedback ----------
     Two rules further down suppress `:hover` on rows and tables, because on touch
     a hover state latches after a tap and looks like a stuck selection. But on
     touch that latched hover WAS the only acknowledgment a tap produced, so
     removing it without a replacement leaves nothing. These `:active` styles are
     the replacement, and they are why turning off the tap-highlight colour is
     safe rather than merely tidier. */
  .trow:active,
  .nav-item:active,
  .nav-link:active,
  .rel a:active,
  .pn a:active,
  .samplebtn:active,
  .pal-item:active,
  .asked-probs a:active,
  .srcs a:active {
    background: var(--bg-sunk);
  }
  .btn:active,
  .searchbtn:active,
  .copybtn:active,
  .iconbtn:active {
    transform: scale(.97);
  }
  .sec-fold > summary:active {
    opacity: .6;
  }

  /* Drawer rows to a comfortable tap height. These are the most-tapped controls
     on the whole site and every one of them was between 33 and 37px. */
  .nav-item,
  .nav-link {
    min-height: 44px;
    padding-top: 9px;
    padding-bottom: 9px;
  }
  .nav-group > summary {
    min-height: 44px;
    padding-top: 10px;
    padding-bottom: 10px;
  }
  .nav-seg .seg button {
    padding-top: 11px;
    padding-bottom: 11px;
  }

  /* The level switcher is what the whole three-level explanation feature hangs
     off, and app.css leaves it at roughly 27px tall — smaller on a tablet than the
     phone tier makes it. Raised here so the whole drawer tier is covered, not just
     phones. `line-height: 1` in app.css means padding is the entire height. */
  .seg button {
    padding-top: 12px;
    padding-bottom: 12px;
  }

  /* Same for the copy button, which app.css leaves at about 29px — the smallest
     control on the site. */
  .copybtn {
    padding: 10px 13px;
    min-height: 40px;
  }

  /* ---------- the sticky stack ----------
     `.levelbar` was `sticky; top: 56px` with z-index 30, under the topbar's 40.
     56px is the topbar's MINIMUM height, so as soon as the breadcrumb wrapped the
     topbar grew past it and painted over the level switcher.

     Rather than chase the topbar's height with a magic number, the level bar
     stops being sticky here. Two stacked sticky bars were eating up to a third of
     a short phone screen, and the switcher is only needed when you arrive at a
     topic, not while you scroll it. That removes the overlap and the wasted
     space in one move. */
  .levelbar {
    position: static;
    margin-bottom: 28px;
    padding: 12px 0 0;
    background: none;            /* the fade only existed to mask scrolled content */
  }

  /* With one sticky bar left, the anchor offset is the topbar plus a little air.
     130px assumed both bars were sticky and left headings under the chrome. */
  .sec,
  .sec-fold {
    scroll-margin-top: 68px;
  }

  /* ---------- horizontal overflow ----------
     `.hero-orb` is a 320px decorative circle at `right: -60px`. An absolutely
     positioned box still counts toward the page's scrollable area, so the home
     page scrolled sideways by about 40px on a 390px screen with nothing visible
     to scroll to. It is decoration, it costs a 20-second infinite animation on a
     battery, and removing it fixes the bug outright. */
  .hero-orb {
    display: none;
  }

  /* Belt and braces for anything added later.

     On `.page` rather than `.main` deliberately. `.main` contains the sticky
     topbar, and putting any overflow value on an ancestor of a sticky element
     invites subtle breakage — `clip` should be safe per spec but this cannot be
     checked without a browser, and a broken topbar would be a worse regression
     than the overflow it guards against. `.page` has no sticky descendants at
     this breakpoint: `.levelbar` is made static just above and `.rail` is already
     `display: none` below 1160px.

     `clip` rather than `hidden` so no scroll container is created either way. */
  .page {
    overflow-x: clip;
  }

  /* Deliberately NOT setting `overscroll-behavior` on the body. An earlier draft
     used `none` to stop scroll chaining, but the body IS the document — there is
     nothing above it for a rubber-band to chain to, so the only thing `none` would
     have achieved is disabling pull-to-refresh across the whole site. The case that
     genuinely needed containing is the drawer's own scrollable nav, handled on
     `.side-nav` above. */

  /* ---------- breadcrumb ----------
     Three segments plus separators do not fit, and wrapping them is what grew the
     topbar and broke the sticky stack above. One line, clipped. */
  .crumb {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex: 1 1 auto;
  }
  /* `display: block` is what makes the ellipsis work, but it also stops app.css's
     flex `gap: 7px` from applying, and crumb() emits no whitespace between the
     segments — so without this the tablet range reads "Data Structures/Sorting/
     Merge Sort" with no spaces. Below 620px the segments are hidden entirely and
     this does nothing. */
  .crumb .sep {
    margin: 0 6px;
  }

  /* Space for the taller icon buttons without the bar growing. */
  .topbar {
    min-height: 54px;
    gap: 8px;
  }

  /* ---------- long unbreakable content ----------
     Nothing in app.css sets any wrapping behaviour, so a pasted URL, a long
     array literal in a hand-trace, or a run-on identifier pushed the page wider.
     `anywhere` rather than `break-word` so it also works inside a flex or grid
     item that has already been squeezed to min-content. */
     This list has to be close to exhaustive, because `overflow-x: clip` on `.page`
     below turns any container this misses from a horizontal scrollbar into
     silently cut-off text. Everything that can hold an identifier, a URL, a
     complexity string or a code span is included. Cells inside `.tblwrap` are
     deliberately absent: they scroll in their own container. */
  .prose,
  .prose p,
  .prose li,
  .clue-text,
  .clue-phrase,
  .clue-target p,
  .step-state,
  .step-note,
  .wt-io code,
  .vid-title,
  .vid-why,
  .asked-probs a,
  .asked-src,
  .srcs a,
  .trow-tag,
  .thead h1,
  .thead-tag,
  .thead-aka,
  .hit-rule,
  .hit-why-row,
  .worked-intro,
  .worked-lesson,
  .worked-answer,
  .sec-title,
  .pn .v,
  .pit-trap,
  .pit-fix,
  .mlist li,
  .tell,
  .drill,
  .cxv,
  .cxv b,
  .chint code,
  .chint span,
  .vrow span,
  .rung-item span,
  .rung-lever,
  .budget-row .hint,
  .usedin-why,
  .usedin-where,
  .usedin-steps li,
  .analogy p,
  .reco-rule,
  .pill,
  .asked-co-name {
    /* `anywhere` rather than `break-word` so it also applies inside a grid or flex
       item already squeezed to min-content. The legacy `word-break: break-word`
       companion is not needed: it means "normal plus anywhere", which this already
       is. */
    overflow-wrap: anywhere;
  }

  /* ---------- tables ----------
     Every table is already inside `.tblwrap { overflow-x: auto }`, so they scroll
     rather than break the page. What was missing was any sign that they scroll.
     The two `local` gradients paint a fade that travels with the content; the two
     `scroll` shadows stay pinned to the edges. Together they show a shadow only on
     the side you can still scroll toward. */
  .tblwrap {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    /* The two `local` layers scroll with the content, so they uncover the edge as
       you reach it; the two `scroll` layers stay pinned. Net effect: a shadow
       appears only on a side you can still scroll toward.

       The cover colour must be `--bg`, not `--surface`. `.tblwrap` has no
       background of its own, so what shows through the transparent cells is the
       page background — and in dark mode `--surface` (#14171f) is LIGHTER than
       `--bg` (#0e1016), so a surface-coloured cover would paint a visible pale
       band at each end of every table instead of masking anything.

       The shadow ink is a theme variable for the same reason: a hardcoded
       near-black is invisible on a near-black surface. */
    background:
      linear-gradient(to right, var(--bg) 32%, transparent) left center / 30px 100% no-repeat local,
      linear-gradient(to left, var(--bg) 32%, transparent) right center / 30px 100% no-repeat local,
      radial-gradient(farthest-side at 0 50%, var(--scroll-ink), transparent) left center / 11px 100% no-repeat scroll,
      radial-gradient(farthest-side at 100% 50%, var(--scroll-ink), transparent) right center / 11px 100% no-repeat scroll;
  }

  /* Hover is the wrong predicate here — what actually matters is whether the
     pointer can hover at all. A mouse user with a narrow window should keep row
     highlighting; a finger should not leave one latched after a tap. Nesting the
     feature query inside the breakpoint keeps every declaration in this file
     inside a max-width block, so the desktop guarantee still holds. */
  @media (hover: none) {
    .ptable tr:hover td,
    .ctable tr:hover td,
    .trow:hover {
      background: none;
    }
    /* The background was not the only thing that latched. */
    .trow:hover .trow-name,
    .feat:hover h3,
    .vid-title:hover {
      color: inherit;
    }
    .vid-title:hover,
    .pn a:hover,
    .rel a:hover,
    .srcs a:hover,
    .asked-probs a:hover {
      text-decoration: none;
    }
  }

  /* ---------- the toast ----------
     Sat 24px from the bottom, which on a gesture-nav phone is behind the home
     bar. */
  .toast {
    bottom: calc(24px + env(safe-area-inset-bottom, 0px));
    max-width: calc(100vw - 32px);
    text-align: center;
  }

  /* ---------- command palette ----------
     12vh from the top is fine on a tall screen and wastes half of a short one,
     and the keyboard shortcut footer is meaningless on a phone. */
  .pal {
    top: 6vh;
  }
  .pal-list {
    max-height: min(440px, 60vh);
    -webkit-overflow-scrolling: touch;
  }
  .pal-item {
    min-height: 46px;
  }
  .pal-foot,
  .pal-esc,
  .searchbtn kbd {
    display: none;
  }
}


/* ==========================================================================
   TIER 2 — 620px and below: the phone tier
   ========================================================================== */
@media (max-width: 620px) {

  /* ---------- page rhythm ----------
     20px each side was inherited from the tablet block. 16px buys 8px of line
     length, which is worth more than the margin on a 360px screen. */
  .page {
    padding: 22px 16px 88px;
  }
  .topbar {
    padding: 0 12px 0 8px;
  }

  /* ---------- breadcrumb ----------
     On a phone the leading segments are noise: the page heading repeats the topic
     name two lines further down anyway, and the group is on the page as a pill.
     Hiding the `<b>` segments and the separators leaves the bare text node, which
     is the leaf — the topic name, "Pattern Finder", "Overview". */
  .crumb b,
  .crumb .sep {
    display: none;
  }
  .crumb {
    font-size: 13px;
    color: var(--text-dim);
  }

  /* ---------- the level switcher ----------
     Three labels at `white-space: nowrap` and `padding: 7px 14px` come to roughly
     305px, against a 328px content box at 360px wide — so it fitted by a hair and
     overflowed on anything narrower. Letting the buttons share the row equally
     makes it fluid instead of fragile. The sidebar copy of `.seg` already does
     exactly this, so this is bringing the level bar in line with it rather than
     inventing something.

     The sliding pill is positioned in JS from `offsetWidth`/`offsetLeft`
     (`fitPill` in js/app.js) and there is a `resize` listener that re-runs it, so
     it follows these widths without any change. */
  .levelbar-inner {
    gap: 8px 10px;
  }
  .levelbar-label {
    flex: 0 0 auto;
  }
  .seg {
    width: 100%;
  }
  .seg button {
    flex: 1 1 0;
    min-width: 0;
    padding: 12px 6px;
    font-size: 12px;
  }
  /* The hint sentence under the switcher is a nice-to-have that costs two lines
     on a phone. The labels already say what each level is. */
  .seg-hint {
    display: none;
  }

  /* ---------- two-column rows that stop fitting ----------
     app.css collapses most of these at 700px, but missed these two. `.cxrow` gave
     84px plus an 18px gap to the label, leaving 226px for the value on a 360px
     screen. */
  .cxrow {
    grid-template-columns: 1fr;
    gap: 3px;
    padding: 11px 0;
  }
  .cxk {
    line-height: 1.4;
  }

  /* `.trow` is the home-page topic row: name and tagline on the left, the variant
     count pinned right with `white-space: nowrap`. Below the fold of a phone the
     count is not worth 20px of gap plus its own column. */
  .trow {
    grid-template-columns: 1fr;
    gap: 4px;
    padding: 13px 2px;
  }
  .trow-meta {
    font-size: 10.5px;
  }

  /* ---------- home page ----------
     `auto-fit, minmax(120px, 1fr)` yields two columns at 360px, which is right,
     but the padding was written for a row of five. */
  .stat {
    padding: 15px 12px 15px 0;
  }
  .stat-n {
    font-size: 22px;
  }
  .hero-lede {
    font-size: 15.5px;
  }
  /* Full-width stacked buttons: two 34px-tall pills side by side on a phone are
     both cramped and easy to mis-tap. */
  .hero-cta {
    gap: 8px;
  }
  .hero-cta .btn {
    width: 100%;
    justify-content: center;
    padding: 13px 16px;
    min-height: 46px;
  }
  .kindhead {
    margin-top: 44px;
    font-size: 19px;
  }

  /* ---------- section headers ----------
     `.sec-note` is pushed right with `margin-left: auto`, so on a narrow screen it
     collides with a long section title. Let the header wrap and put the note on
     its own line. */
  .sec-head {
    flex-wrap: wrap;
    gap: 4px 10px;
  }
  .sec-note {
    margin-left: 0;
    flex-basis: 100%;
  }
  .sec-fold > summary {
    flex-wrap: wrap;
    gap: 4px 10px;
    padding: 15px 0;
    /* The caret is pushed right by `margin-left: auto`; keep it on the first line
       with the title rather than letting it wrap under. */
  }
  .sec-fold > summary .sec-title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 16px;
  }
  .sec-fold > summary .fold-caret {
    flex: 0 0 auto;
  }
  .sec-fold > summary .sec-note {
    flex-basis: 100%;
    margin-left: 0;
  }
  .sec {
    margin-bottom: 40px;
  }
  .sec-fold-body {
    padding-bottom: 26px;
  }

  /* ---------- code ----------
     Already taken to 40px in the drawer tier; this only adjusts the text. */
  .copybtn {
    font-size: 12px;
  }
  .codebar {
    padding: 8px 10px;
    gap: 8px;
  }
  .codeblk pre {
    padding: 14px 14px;
    font-size: 12.2px;
    -webkit-overflow-scrolling: touch;
  }
  /* The language tag duplicates what the code obviously is, and the label is
     already ellipsised. Drop the tag to give the label room. */
  .codelang {
    display: none;
  }

  /* ---------- pattern finder ----------
     The 16px font-size that suppresses the iOS zoom is set in the 900px tier, so
     it already covers a landscape phone. Only spacing here. */
  .finder-in textarea {
    padding: 14px 15px;
    min-height: 150px;
  }
  .finder-bar .btn {
    flex: 1 1 auto;
    justify-content: center;
    min-height: 46px;
  }
  .samplebtn {
    padding: 9px 12px;
    min-height: 40px;
  }
  .clue-text {
    padding: 15px 16px;
    font-size: 14px;
    line-height: 1.75;
  }
  /* The score meter is pinned right by `margin-left: auto` and collides with a
     long technique name. */
  .hit-top {
    gap: 8px 10px;
  }
  .hit-score {
    margin-left: 0;
    flex-basis: 100%;
  }
  .meter {
    flex: 1 1 auto;
    width: auto;
  }

  /* ---------- videos ----------
     Five metadata items separated by dots wrap awkwardly at this width; the
     upload date is the least useful of them on a phone and the `why` note
     underneath is what actually helps someone choose. */
  .vid {
    padding: 13px 13px;
  }
  .vid-title {
    font-size: 14.5px;
  }
  .vid-meta {
    font-size: 11.8px;
    gap: 3px 8px;
  }
  .vid-meta span + span::before {
    margin-right: 8px;
  }
  .vid-why {
    font-size: 13px;
  }

  /* ---------- company tags ----------
     The chips now carry a visible percentage, so they are wider than they were.
     One column for the roll-up, and a slightly larger chip since it is also a
     touch target with a tooltip. */
  .asked-grid {
    grid-template-columns: 1fr;
  }
  .cochip {
    font-size: 11px;
    padding: 5px 8px;
  }
  .cochips {
    gap: 4px;
  }
  .asked-co {
    padding: 13px 14px;
  }
  .asked-co-name {
    font-size: 13.5px;
  }
  /* These are ordinary text links in a list, so they cannot be padded into 44px
     boxes without wrecking the rhythm. 34px with the row gap is the compromise:
     comfortably tappable without turning a list of problem names into a stack of
     buttons. */
  .asked-probs {
    gap: 2px;
  }
  .asked-probs a {
    display: block;
    padding: 8px 0;
    min-height: 34px;
  }

  /* ---------- tables ----------
     Tighter cells fit one more column before the reader has to drag. */
  .ptable,
  .ctable {
    font-size: 12.8px;
  }
  .ptable th,
  .ctable th {
    padding: 10px 12px;
  }
  .ptable td,
  .ctable td {
    padding: 11px 12px;
  }

  /* ---------- neighbouring topics, sources, prev/next ---------- */
  .rel a {
    padding: 9px 13px;
    min-height: 40px;
  }
  .srcs {
    gap: 2px;
  }
  .srcs a {
    padding: 8px 0;
    min-height: 34px;
  }
  /* Every remaining .btn on the site, so the one in the not-found view is covered
     too rather than only the hero and finder buttons. */
  .btn {
    min-height: 44px;
  }
  .pn {
    margin-top: 40px;
  }
  .pn a {
    padding: 14px 15px;
  }
  /* Right-aligning "next" reads as a typo once the two cards are stacked. */
  .pn .next {
    text-align: left;
  }

  /* ---------- real-world examples ---------- */
  .usedin-item {
    padding: 16px 14px;
  }
  .usedin-steps {
    padding-left: 18px;
  }

  /* ---------- topic header ---------- */
  .thead-tag {
    font-size: 15.5px;
  }
  .analogy {
    margin-top: 22px;
    padding-left: 15px;
  }
  .analogy p {
    font-size: 15px;
  }

  /* ---------- cards ---------- */
  .card {
    padding: 16px 16px;
  }
  .reco-rule {
    padding: 15px 16px;
    font-size: 14px;
  }

  /* Explanation levels: the age-6 level is deliberately large on desktop. 18px
     with a 54ch measure is fine on a phone too, so only the line length changes. */
  .expl-6 .prose,
  .expl-12 .prose {
    max-width: 100%;
  }
}


/* ==========================================================================
   TIER 3 — 380px and below: small phones
   Spacing and one font size only. No layout changes, so there is nothing here
   that can reflow differently from tier 2.
   ========================================================================== */
@media (max-width: 380px) {

  .page {
    padding: 18px 13px 80px;
  }
  .seg button {
    padding: 10px 4px;
    font-size: 11.2px;
  }
  .thead-badges {
    gap: 6px;
  }
  .badge {
    font-size: 9.5px;
    padding: 4px 6px;
  }
  .sec-fold > summary .sec-title {
    font-size: 15.5px;
  }
  .vid {
    grid-template-columns: 22px minmax(0, 1fr);
    gap: 9px;
  }
  .vid-rank {
    width: 22px;
    height: 22px;
    font-size: 11px;
  }
  .codeblk pre {
    padding: 12px 11px;
    font-size: 11.8px;
  }
  .stat-n {
    font-size: 20px;
  }
  /* `.stats` is deliberately left alone. app.css's
     `repeat(auto-fit, minmax(120px, 1fr))` already resolves to two columns in the
     ~294px content box a 320px screen leaves, which is the right answer — an
     explicit override here would only restate it. */
}
