/* OBEINSON FREQUENCY X — the now-playing player fits the phone it is on.
 *
 * THE REPORT
 *
 * The owner's screenshot showed the dial clipped off the top of the screen and
 * the presets sitting below the fold. Both are the same fault: the card's
 * content is taller than the card, so it scrolls, and a scrolled card shows you
 * the middle of itself.
 *
 * MEASURED, NOT GUESSED — iPhone profile, 393x852, the player in .full:
 *
 *     scrollHeight 1155   clientHeight 944      211px of overflow
 *
 * and on a Pixel-class 412x915 profile, 1155 against 1014, so 141px. Fitting
 * the narrower, shorter phone fits the taller one as well, which is why one set
 * of numbers below serves both.
 *
 * ------------------------------------------------------------------ UNITS
 *
 * EVERY LENGTH HERE IS A LAYOUT PIXEL, AND THAT IS NOT THE SAME AS A PIXEL ON
 * THE GLASS. The app scales the whole document with CSS `zoom` (phones sit at
 * 0.9), so the 852-tall window is 946.67 layout px and the 196px disc below
 * paints at 176. The trap this file had to avoid: getBoundingClientRect()
 * reports VISUAL px while scrollHeight/clientHeight/offsetHeight report LAYOUT
 * px. Mixing the two makes a 196px box look like a 176px box and every sum come
 * out 10% wrong. The 211px above is layout px, compared like with like
 * (scrollHeight against clientHeight), and so is everything that follows.
 *
 * It is also why there is no `@media (max-height: …)` tier in this file. Under
 * zoom it is not obvious which viewport a height query resolves against, and
 * AGENTS.md already records this repo walking into that trap twice. Phones are
 * where the overflow is, so `html.obn-phone` is the whole condition.
 *
 * ------------------------------------------------- BOTH PLATFORMS, ON PURPOSE
 *
 * Scoped to `html.obn-phone:not(.obn-landscape) .nowFab.full` — phones held
 * upright, on EITHER platform, because the two reach this screen by different
 * routes and the owner asked for both:
 *
 *   Android  setFull() calls requestFullscreen(), which succeeds. The UA
 *            stylesheet's `:fullscreen { max-height:none !important }` then
 *            outranks every author rule.
 *   iOS      has no element fullscreen at all, so the same markup is a plain
 *            position:fixed box and author rules are the only ones acting.
 *
 * `.full` is set by the app in BOTH cases — verified on both profiles, not
 * assumed — so this one selector covers them. Nothing here keys off the UA, the
 * platform, or the presence of the fullscreen API, and nothing here needs to.
 *
 * `:not(.obn-landscape)` is not tidiness either. np-phone.css already has a
 * landscape block that solves this same problem sideways, where the screen is
 * 390 tall and the disc has to come down to 116px. Several rules here are the
 * same properties with roomier upright values and carry !important, so without
 * the exclusion they would silently undo that tuning — a fix for one
 * orientation landing as a regression in the other.
 *
 * ---------------------------------------------------------- THE MECHANISM
 *
 * Trimming fixed amounts off every block would fit this phone and leave a taller
 * one with a gap at the bottom, which is the opposite of what the owner asked
 * for last time. So the card is treated as fixed furniture plus ONE elastic
 * block: everything a listener reaches for keeps a size of its own, and the
 * FEATURED list absorbs whatever is left over. On a short phone it gives space
 * back until the presets fit; on a tall one it grows and shows more stations.
 * That is the same measured-height idea the station chart uses for its columns,
 * turned on its side.
 *
 * IF THIS FILE IS NOT LOADED the player behaves exactly as it did before it:
 * taller than the screen and scrolling. Nothing else depends on it.
 */

/* ----------------------------------------------------------------- 1. header
   40 -> 32. It is one line of small caps and a sleep button. */
html.obn-phone:not(.obn-landscape) .nowFab.full .rpHead {
  min-height: 32px !important;
  padding-top: 2px !important;
  padding-bottom: 0 !important;
}

/* ------------------------------------------------------------------- 2. disc
   196 -> 136. The single biggest block on the screen and the cheapest 60px in
   the file. The landscape rules already drop it to 116 for the same reason, so
   this is not a new idea, just a smaller step: 136 keeps it the hero of the
   screen upright, where it has the width to be one.

   #mpArt and .obnNpIni are absolutely positioned INSIDE the disc at fixed
   offsets, so they do not follow a width change on their own — shrinking only
   the wrapper leaves the artwork hanging out of its ring. Both are restated. */
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpDisc {
  width: 136px !important;
  height: 136px !important;
}
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpDisc #mpArt {
  width: 112px !important;
  height: 112px !important;
  left: 12px !important;
  top: 12px !important;
}
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpDisc .obnNpIni { inset: 12px !important; }

/* the halo is sized off the disc and has to come in with it, or it reads as a
   bloom floating clear of the artwork */
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpDisc::before {
  inset: -14px !important;
}

/* ---------------------------------------------------------- 3. the stack gaps
   Four 16px top margins between the disc and the results, which upright add up
   to more than the row they separate. 16 -> 10 keeps them readable as separate
   groups without the air. */
html.obn-phone:not(.obn-landscape) .nowFab.full .mpRow { padding-top: 4px !important; }
html.obn-phone:not(.obn-landscape) .nowFab.full .rpCtrls { margin-top: 10px !important; }
html.obn-phone:not(.obn-landscape) .nowFab.full .mpVolWrap,
html.obn-phone:not(.obn-landscape) .nowFab.full .rpTune { margin-top: 10px !important; }
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpRes { margin-top: 10px !important; }

/* ------------------------------------------------------- 4. the elastic block
   The card is a flex column. These lines are what let the FEATURED list take the
   slack instead of the bottom of the screen taking it.

   `min-height:0` on the container is load-bearing: a flex item will not shrink
   below its content without it.

   A FLOOR ON THE CHILD DOES NOT STOP THE PARENT SHRINKING — it makes the child
   PAINT OUTSIDE IT. The first version of this block put `min-height:112px` on
   the list while leaving the container free to shrink to nothing. The container
   duly shrank to 122px, the list held its 112 floor plus the heading, and the
   extra 26px rendered straight over the presets below: the owner's screenshot
   showed station rows on top of the preset grid and "FEATURED · 9" printed over
   "PRESETS (12)". Measured again with a three-line counts strip in the card, the
   spill was 43px. Overflow and overlap are not the same failure, and a fit test
   that only compares scrollHeight to clientHeight reports "fits" through both.

   So the floor moves to the CONTAINER, the list is free to shrink, and
   `overflow:hidden` makes it structurally impossible for a child to paint
   outside this box however the numbers come out. The list is a scroller, so
   fewer visible rows costs nothing — everything in it is still reachable. */
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpRes {
  flex: 1 1 auto !important;
  min-height: 96px !important;      /* heading + about one row; never vanishes */
  overflow: hidden !important;      /* the guarantee, not the tidying */
  display: flex !important;
  flex-direction: column !important;
}
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpList {
  flex: 1 1 auto !important;
  /* was a flat 230px cap. A cap cannot grow, and growing is half the job. */
  max-height: none !important;
  min-height: 0 !important;         /* see above: the floor belongs to the parent */
  overflow-y: auto !important;

  /* THE FADE THAT USED TO BE HERE IS GONE, and why is worth keeping.

     When the list could end part-way through a row, a flat clip looked like a
     station sliced in half, so the bottom was masked to read as "more below".
     That fixed the bottom and made the top worse: once the listener scrolled,
     the mask left a ghosted half-row at the bottom AND a hard-cut one at the
     top, which is what the owner's screenshot showed. A fade is a way of
     apologising for a ragged edge. Section 7 removes the ragged edge instead —
     whole rows, and snapping so a scroll cannot stop between them — and once
     the edge is honest the apology is just a smudge over real content. */
  scroll-snap-type: y mandatory;
}

/* ------------------------------------------- 4b. the counts strip in the card
   stats-in-player.js moves #obnHitCount into the player while a station plays,
   and stats-in-player.css gives it `order:0` — which put it ABOVE the header, in
   the exact band where #mpClose sits at `position:absolute; top:14px`. The close
   button is out of flow, so nothing moved aside for it and the ✕ landed on top
   of the figures: measured, a 35px overlap on both platforms.

   `order:3` puts the strip under the dial, level with the STREAMING bar it
   belongs beside, and clear of the absolute button entirely. .obnNpLive is also
   order 3 and sits earlier in the DOM, so the two resolve as STREAMING then the
   audience figures, which is the right reading order anyway.

   WHY THIS WAS MISSED FIRST TIME ROUND, since it is the real lesson: the strip
   only enters the card when #nowFab carries `livecard`, which the app derives
   from live audio. Audio never connects in the sandbox, so every measurement
   behind the original fit was taken on a card with this block ABSENT. The height
   budget was therefore computed without something that is always on the owner's
   screen. Any test of this card must set `livecard` and hold it — the app
   re-derives it every 600ms and takes it straight back off.

   TWO IDs IN THIS SELECTOR, DELIBERATELY. stats-in-player.css sets the order
   with `html.obn-phone #nowFab #obnHitCount.obnStatsInPlayer` — two IDs — and
   two IDs beat one however many !importants the loser carries. Written first as
   `.nowFab.full #obnHitCount…` this rule lost silently and the ✕ stayed on the
   figures. That is the THIRD specificity miss in this one file (see the presets
   note below); in this app, matching the rule you are overriding ID-for-ID is
   the default, not the exception. */
html.obn-phone:not(.obn-landscape) #nowFab.full #obnHitCount.obnStatsInPlayer {
  order: 3 !important;
  flex: 0 0 auto !important;
  margin: 8px 0 0 !important;
}

/* the FEATURED heading: 49 -> 32, a label and a button, not a section break */
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpRes .rpSec {
  min-height: 0 !important;
  padding-top: 0 !important;
  padding-bottom: 4px !important;
  margin: 0 !important;
  flex: 0 0 auto !important;
}

/* --------------------------------------------------------------- 5. presets
   The drawer is the last thing in the column and the thing that was off the
   bottom of the screen. Its own padding was 14/16 around a 12px heading. */
html.obn-phone:not(.obn-landscape) .nowFab.full .rpDrawer {
  flex: 0 0 auto !important;
  padding-top: 3px !important;
  padding-bottom: 3px !important;
  gap: 4px !important;
}
/* THE PRESET BUTTONS THEMSELVES ARE DELIBERATELY NOT TOUCHED HERE, and the first
   draft of this file was wrong about that in a way worth recording.
 *
   It carried `.rpDrawer .rpPresets button { min-height:32px !important }` to
   take another 16px out of the two rows. That rule never applied: np-phone.css
   sets `#rpPresets button { min-height:40px !important }`, and an ID beats any
   number of classes no matter how many !importants the loser carries. The
   measured height never moved — 40 layout px, 36 on the glass, before and
   after — so the file shipped a rule that did nothing while its comment
   explained the saving it was making. Had I trusted the comment instead of the
   hit test, the next person would have inherited a lie in a stylesheet.
 *
   It is also the right outcome. 32px would have been 29px on the glass, and
   these are the smallest targets on the screen already. The 40px floor stays,
   the drawer fits anyway on the padding above, and the presets keep a thumb's
   worth of height. The same applies to the grid's 7px gap, also set by ID.
 *
   If this drawer ever has to give up more height, take it from the heading or
   drop to one row of six — not from the button height. */

/* ---------------------------------------------------------------- 6. the card
   With the content now fitting, the card itself should not invite a scroll it no
   longer needs. It stays `auto` rather than `hidden` deliberately: if a future
   station name wraps to three lines, or a listener has larger text set, the
   content can still exceed the box and must stay reachable. Clipping it would
   trade a visible overflow for a silent one. */
html.obn-phone:not(.obn-landscape) .nowFab.full {
  overscroll-behavior: contain;
}

/* ===========================================================================
   7. EVERY SECTION THE SAME HEIGHT
   ===========================================================================

   Asked for directly: the stacked blocks should read as one rhythm instead of
   seven different heights. The dial stays as it is — it is the hero of the
   screen and the one block that earns its size — and everything below it is
   cut to a single unit.

   THE UNIT IS MEASURED, NOT CHOSEN. On the 393x852 profile, after the header
   (29), the dial (277), six inter-section gaps (54) and the card's padding
   (75), there are 417 visual px left. Those have to cover four control panels
   plus FEATURED and PRESETS, each of which is a heading and two rows:

       417 = 4H + (30 + 2H) + (14 + 2H)  =>  8H + 44  =>  H = 46.6 visual

   which is 52 layout px, and 52 layout is what --sec holds. Everything in this
   section is expressed in it, so the rhythm survives one edit rather than six.

   IT ALSO FIXES THE TAP TARGETS. The preset buttons were the smallest thing on
   the screen at 36px on the glass, under the 44px guidance, and an earlier note
   in this file says to take height from the heading rather than from them. At
   --sec they are 47 — the first time they clear the guidance. Equal sizing and
   reachability happen to want the same thing here.

   AND IT IS WHAT KILLS THE SLICED ROWS. Rows of a known, uniform height can be
   snapped to: the list is exactly two rows tall, so it never ends mid-row, and
   `scroll-snap-align` on each row means a scroll cannot come to rest between
   two of them either. That is a structural fix where the fade was a cosmetic
   one. */
html.obn-phone:not(.obn-landscape) .nowFab.full { --sec: 50px; }

/* --- paying for the unit --------------------------------------------------
   Solving against the REAL fixed costs rather than my estimate of them gave
   516 visual px of furniture and 336 left for eight units — 42 each, which is
   under the 44px tap guidance, and the presets are already the smallest thing
   here. So three pieces of chrome give up a little to buy it back: the FEATURED
   heading (48 -> 36), the drawer's own padding (34 -> 20) and the inter-section
   gaps (9 -> 5). That is 40px recovered, which puts the unit at 45 on the glass
   and clears the guidance with the dial untouched and both rows intact.

   The cost is honest and worth stating: "Browse all" comes down with its
   heading. It stays full width, and it is a secondary affordance next to the
   transport and the presets, which is why it is the one that pays. */
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpRes .rpSec {
  height: 40px !important;
  min-height: 0 !important;
  padding: 0 !important;
  align-items: center !important;
}
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpRes .obnNpMore {
  /* fills its heading row rather than floating inside it: the row is already
     40px, so the button taking all of it costs no layout height and buys back
     5px of target. Still the shortest control here, which is the trade stated
     above — it is the secondary affordance next to transport and presets. */
  height: 40px !important;
  min-height: 0 !important;
  padding: 0 14px !important;
}

/* --- the single-line control panels ------------------------------------- */
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpLive,
html.obn-phone:not(.obn-landscape) .nowFab.full .mpVolWrap {
  min-height: var(--sec) !important;
  height: var(--sec) !important;
  box-sizing: border-box !important;
  display: flex !important;
  align-items: center !important;
}

/* two IDs: stats-in-player.css selects this with two as well (see 4b) */
html.obn-phone:not(.obn-landscape) #nowFab.full #obnHitCount.obnStatsInPlayer {
  min-height: var(--sec) !important;
  box-sizing: border-box !important;
  align-content: center !important;
}

/* --- transport ------------------------------------------------------------
   The row takes the unit, and the play button grows to fill it rather than
   shrinking to a token. It stays the biggest control in the row — prev/next
   are 44 on the glass against its 47 — so the hierarchy survives the levelling;
   it just no longer sets the section's height at 64. */
html.obn-phone:not(.obn-landscape) .nowFab.full .rpCtrls {
  min-height: var(--sec) !important;
  height: var(--sec) !important;
  box-sizing: border-box !important;
  align-items: center !important;
}
html.obn-phone:not(.obn-landscape) #nowFab.full #nowPause {
  width: var(--sec) !important;
  height: var(--sec) !important;
  font-size: 22px !important;
}

/* --- the station rows -----------------------------------------------------
   `flex:none` matters: .obnNpList is a flex column, so without it the rows
   share out the list's height instead of keeping the unit, and two rows in a
   slightly-too-short box quietly become two slightly-short rows — which is the
   ragged edge coming back by another route. */
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpList .qsRow {
  min-height: var(--sec) !important;
  height: var(--sec) !important;
  box-sizing: border-box !important;
  flex: none !important;
  padding: 0 9px !important;
  scroll-snap-align: start !important;
}

/* exactly two rows plus the gap between them: a whole number of rows, so the
   box cannot end part-way through one */
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpList {
  height: calc(var(--sec) * 2 + 2px) !important;
  flex: 0 0 auto !important;
}
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpRes {
  flex: 0 0 auto !important;
  min-height: 0 !important;
}

/* --- the presets ----------------------------------------------------------
   TWO IDs again, and this is the rule the first draft of this file lost: the
   note further up explains that np-phone.css pins these at 40px with
   `#rpPresets button`, and a class selector cannot beat it however many
   !importants it carries. `#nowFab.full #rpPresets button` can. */
html.obn-phone:not(.obn-landscape) #nowFab.full #rpPresets button {
  min-height: var(--sec) !important;
  height: var(--sec) !important;
  box-sizing: border-box !important;
}

/* --- one gap everywhere ---------------------------------------------------
   Equal blocks separated by unequal gaps still read as irregular, so the four
   different margins in section 3 collapse to a single value here. */
html.obn-phone:not(.obn-landscape) .nowFab.full .rpCtrls,
html.obn-phone:not(.obn-landscape) .nowFab.full .mpVolWrap,
html.obn-phone:not(.obn-landscape) .nowFab.full .rpTune,
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpRes,
html.obn-phone:not(.obn-landscape) .nowFab.full .obnNpLive {
  margin-top: 5px !important;
}
html.obn-phone:not(.obn-landscape) #nowFab.full #obnHitCount.obnStatsInPlayer {
  margin: 5px 0 0 !important;
}
