/* OBEINSON FREQUENCY X — the phone's full-screen now-playing player.
 *
 * The app already opened .nowFab.full on phones whenever a station started; what
 * it did not have was a shape worth filling a screen with, or a way back. This
 * file is the shape. np-phone.js is the way back.
 *
 * Everything here is scoped to  html.obn-phone .nowFab.full  — the desktop and
 * tablet player is untouched, and so is the same card's docked and minimised
 * states. The dock at the bottom is phone-only too.
 *
 * !important is used throughout for the usual reason in this app: the vendor
 * styles these same elements with it (#mpArt alone is sized !important in four
 * separate rules, and #nowLbl carries a max-width of 120px), so a plain
 * declaration loses no matter how specific the selector is.
 *
 * Colours are the six roles, the same meaning they carry everywhere else:
 *   value  orange  the clock, and the favourite/share actions
 *   head   cyan    station identity and the way back
 *   ok     green   live — the same green as the pinned row in the catalog
 *   tag    magenta genre
 *   label  violet  country, volume, sleep
 *   peak   yellow  bitrate, and the loud end of the meter
 */

/* ---------------------------------------------------------------- 0
 * Light theme.
 *
 * Every rule below reaches for a role through var(--dial-x, <dark hex>), and
 * dial-colors.css only defines those under :root[data-theme="dark"] — so in
 * light theme each one fell through to its dark-surface fallback and painted a
 * bright hue on a near-white card. Measured before this block: 49 AA failures
 * across the player, the worst of them ON AIR at 1.23:1.
 *
 * These are the same six hue angles re-solved for a light surface, the values
 * site-colors.css already uses on the 28 site pages, so a role is the same
 * colour in the app and on the site in whichever theme you are in. Yellow is
 * the exception: it cannot be text on white at any useful chroma, so peak
 * becomes a dark amber and keeps the yellow only as a background wash.
 *
 * Setting the variables rather than rewriting the rules also means
 * enhance-rack.css — which reaches for var(--dial-label) with no fallback, and
 * was therefore inert in light theme — now gets proper light hues too.
 */
:root[data-theme="light"] {
  --dial-value: #ab5601;
  --dial-head:  #007787;
  --dial-ok:    #007e11;
  --dial-tag:   #b8005f;   /* #d40071 measured 4.36:1 on its own pale wash */
  --dial-label: #6e00ff;
  --dial-peak:  #7a5c00;
  --dial-bad:   #da0700;
  /* the app's own --muted is #6b7f95, which on the white row measures 4.12:1 —
     under AA, and this is the line that carries the city, genre and bitrate */
  --obnRowMeta: #55687e;
}

/* ---------------------------------------------------------------- 1
 * Page order. #nowFab is already a flex column, so the running order on a full
 * screen is set with `order` rather than by moving anything in the DOM — the
 * vendor's markup keeps working and nothing below applies outside .full.
 */
html.obn-phone .nowFab.full .rpHead      { order:1 }
html.obn-phone .nowFab.full .mpRow       { order:2 }
html.obn-phone .nowFab.full .rpViz       { order:3 }
html.obn-phone .nowFab.full .rpCtrls     { order:4 }
html.obn-phone .nowFab.full .mpVolWrap   { order:5 }
html.obn-phone .nowFab.full .obnNpRes    { order:6 }

/* The TUNE bar is gone from the phone player. Sweeping a band is a desktop
 * gesture — on a phone the station list right above does the same job better,
 * and the slider was one more full-width control competing with volume. The
 * element stays in the DOM, so #rpTuner's handlers and the interval that keeps
 * it in sync with the current station carry on untouched, and the desktop and
 * tablet player still shows it. */
html.obn-phone .nowFab.full .rpTune { display:none !important }
html.obn-phone .nowFab.full .rpDrawer    { order:8 }

/* full() adds both .full and .expanded, and .expanded carries max-height:88vh.
 * So "full screen" was never full: measured at 390x844 it filled 742.7 of the
 * 938 layout px available, leaving the screen behind it showing through the
 * bottom 20%. The cap belongs to the expanded-card state, not to this one.
 *
 * This is the vendor's bug and it applies to every full-screen player, not only
 * the phone's, so the override is not scoped to obn-phone. */
.nowFab.full { max-height:none !important }

/* #nowFab sits at z-index 97 and the station catalog overlay at 100. Reopening
 * the player from the dock while the catalog is open therefore set .full on a
 * card that stayed behind the catalog — the state was right and nothing was on
 * screen. 102 clears both the overlay and the dock's own 101. */
html.obn-phone .nowFab.full { z-index:102 !important }

html.obn-phone .nowFab.full {
  gap:0 !important;
  padding-left:14px !important;
  padding-right:14px !important;
  /* The vendor reserves --obnTabsH at the bottom so the tab bar does not cover
   * the card. At z-index 102 this card covers the tab bar instead, so that
   * reservation was ~52px of dead space under the last control, on top of the
   * phone's own safe-area inset. Only the inset is kept. */
  padding-bottom:calc(14px + env(safe-area-inset-bottom,0px)) !important;

  /* Vibrancy: two soft washes over the flat navy, cyan from the top and a warm
   * one rising from the bottom, so the screen has some depth behind the disc
   * instead of reading as a grey sheet. Fixed attachment keeps them still while
   * the content scrolls, so they behave like a backdrop rather than wallpaper. */
  background-image:
    radial-gradient(130% 60% at 50% -6%,  rgba(0,225,255,.26), transparent 72%),
    radial-gradient(90% 45% at 12% 104%,  rgba(255,78,172,.14), transparent 72%),
    radial-gradient(95% 50% at 92% 108%,  rgba(255,127,0,.13),  transparent 70%) !important;
  /* NOT background-attachment:fixed. iOS Safari repaints a fixed background
     against the viewport on every scroll and every compositor change, which is
     what made the screen flick on each press; this card scrolls, so it hit it
     constantly. Scrolling with the content costs nothing and looks the same. */
  background-attachment:scroll !important;
  background-repeat:no-repeat !important;
}

/* Of the drawer, only the presets survive on a phone. The SDR chip row, the
 * "connect any stream URL" field and the second station list all go: the
 * results list directly above already does the browsing, and those three were
 * the blank area at the end of the scroll. Presets earn their place — twelve
 * slots, tap to play, hold to save — so they stay.
 *
 * Everything is hidden rather than removed, so the handlers still work and the
 * desktop and tablet player is untouched.
 *
 * The :has() rules are deliberately in their own block. A browser that does not
 * support :has() throws out the whole rule it appears in, so keeping them apart
 * means the worst case is two stray headings rather than the sections coming
 * back with them. */
html.obn-phone .nowFab.full .rpExpand,
html.obn-phone .nowFab.full #rpSDR,
html.obn-phone .nowFab.full .rpConnect,
html.obn-phone .nowFab.full #rpStations { display:none !important }

html.obn-phone .nowFab.full .rpSec:has(+ #rpSDR),
html.obn-phone .nowFab.full .rpSec:has(+ #rpStations) { display:none !important }

html.obn-phone .nowFab.full .rpDrawer {
  /* #nowFab is a flex column, so the drawer was being shrunk to fit the line
     and its preset grid overflowed without the scroll box growing: the grid
     sat past the end of the scroll and could not be reached at all. */
  flex:none !important;
  margin-top:16px !important;
  padding-top:14px !important;
  /* enough that the last row of presets clears the bottom of the scroll; the
     card itself keeps only the safe-area inset */
  padding-bottom:16px !important;
  gap:10px !important;
}
html.obn-phone .nowFab.full .rpDrawer .rpSec {
  font:700 9.5px/1.4 var(--mono,monospace) !important;
  letter-spacing:.15em;
  color:var(--dial-head,#00e1ff) !important;
  -webkit-text-fill-color:var(--dial-head,#00e1ff) !important;
}

/* The preset grid read as twelve dark squares, which is most of why this area
 * looked like dead space. An empty slot now says it is empty, and a saved one
 * carries the peak hue and its station's name. */
/* `1fr` columns stretch to whatever the card is wide, so twelve small controls
   were drawn as twelve full-width slabs — the grid was sized by the container
   rather than by what a preset actually needs, which is room for two digits and
   a station name. Fixed tracks let them be the size of the thing they are, and
   `justify-content:center` puts the block in the middle of the card instead of
   leaving it stretched edge to edge. minmax() keeps the row intact on a narrow
   phone: the tracks shrink before they ever wrap. */
html.obn-phone .nowFab.full #rpPresets {
  display:grid !important;
  grid-template-columns:repeat(6,minmax(0,44px)) !important;
  justify-content:center !important;
  gap:7px !important;
}
html.obn-phone .nowFab.full #rpPresets button {
  min-height:40px !important;
  padding:4px 2px !important;
  border-radius:11px !important;
  border:1px dashed rgba(130,160,200,.38) !important;
  background:rgba(130,160,200,.06) !important;
  color:var(--muted,#8ba0bd) !important;
  -webkit-text-fill-color:var(--muted,#8ba0bd) !important;
  font:700 12px/1.1 var(--mono,monospace) !important;
  display:flex !important;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:2px;
  overflow:hidden;
}
html.obn-phone .nowFab.full #rpPresets button small {
  display:block;
  max-width:100%;
  font:600 7.5px/1.1 var(--mono,monospace);
  letter-spacing:.02em;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}
html.obn-phone .nowFab.full #rpPresets button.set {
  border:1px solid rgba(255,212,0,.55) !important;
  background:rgba(255,212,0,.12) !important;
  color:var(--dial-peak,#ffd400) !important;
  -webkit-text-fill-color:var(--dial-peak,#ffd400) !important;
}
html.obn-phone .nowFab.full #rpPresets button:active {
  background:rgba(0,225,255,.18) !important;
}

/* ---------------------------------------------------------------- 2
 * Top row: the way back on the left, what the radio is doing in the middle,
 * the sleep timer on the right.
 *
 * The vendor's ✕ and its ⇲ / — / ⛶ cluster are dropped here. On a full screen
 * ⇲ docks to a corner that does not exist, — minimises to the same corner and
 * ⛶ toggles the state we are already in; ▾ does the leaving for all three, and
 * ♡ moves down to the actions row where it can be reached by a thumb. Measured:
 * that cluster is 187px wide on a 363px row, which is why nothing else fitted.
 */
html.obn-phone .nowFab.full .mpCtl { display:none !important }

/* ✕ stays: it is the control everyone reaches for to leave a full-screen
   player, and np-phone.js has it collapse to the dock and reopen the station
   catalog rather than the vendor's "hide the card and leave you nowhere".
   ▾ on the left does the same thing; two ways out, both landing in the list. */
html.obn-phone .nowFab.full .mpClose {
  display:flex !important;
  align-items:center;
  justify-content:center;
  position:absolute !important;
  top:calc(14px + env(safe-area-inset-top,0px)) !important;
  right:14px !important;
  width:34px !important;
  height:34px !important;
  border-radius:50% !important;
  background:rgba(130,160,200,.12) !important;
  border:1px solid rgba(130,160,200,.40) !important;
  color:var(--text,#eaf2fb) !important;
  -webkit-text-fill-color:var(--text,#eaf2fb) !important;
  font-size:14px !important;
  z-index:3;
}
html.obn-phone .nowFab.full .mpClose:active { background:rgba(130,160,200,.26) !important }

/* the sleep button clears ✕ entirely: 34px of button plus its 14px inset,
   plus breathing room, or the two labels sit on top of each other */
html.obn-phone .nowFab.full .obnNpSleep { margin-right:56px }

html.obn-phone .nowFab.full .rpHead {
  padding:2px 0 0 !important;
  min-height:40px;
  align-items:center;
  gap:0 !important;
}
html.obn-phone .nowFab.full .obnNpBack {
  flex:none;
  width:38px; height:38px;
  margin-right:auto;
  border-radius:50%;
  background:rgba(0,225,255,.10);
  border:1px solid rgba(0,225,255,.40);
  color:var(--dial-head,#00e1ff);
  -webkit-text-fill-color:var(--dial-head,#00e1ff);
  font-size:15px;
  display:flex; align-items:center; justify-content:center;
  cursor:pointer;
}
html.obn-phone .nowFab.full .obnNpBack:active { background:rgba(0,225,255,.26) }

/* the live lamp sits inside the pill's left padding rather than beside it */
/* the lamp sits inside the pill's left padding; the numbers have to agree or it
   lands on the first letter */
html.obn-phone .nowFab.full .rpHead .gdot {
  position:relative; left:-8px; z-index:1;
  margin-right:-26px;
  flex:none;
}
html.obn-phone .nowFab.full .rpHead .t {
  flex:none !important;
  font-size:10px !important;
  letter-spacing:.17em !important;
  color:var(--dial-ok,#00ff22) !important;
  -webkit-text-fill-color:var(--dial-ok,#00ff22) !important;
  background:rgba(0,255,34,.10);
  border:1px solid rgba(0,255,34,.42);
  border-radius:999px;
  padding:5px 12px 5px 26px;
}
/* paused and connecting are not live, and must not read as live */
html.obn-phone .nowFab.full.obnNpOff .rpHead .t {
  color:var(--dial-peak,#ffd400) !important;
  -webkit-text-fill-color:var(--dial-peak,#ffd400) !important;
  background:rgba(255,212,0,.10);
  border-color:rgba(255,212,0,.42);
}

html.obn-phone .nowFab.full .obnNpSleep {
  flex:none;
  margin-left:auto;
  padding:0 2px;
  display:flex; flex-direction:column; align-items:center; gap:3px;
  background:none; border:0; cursor:pointer;
  color:var(--dial-label,#af79f6);
  -webkit-text-fill-color:var(--dial-label,#af79f6);
}
html.obn-phone .nowFab.full .obnNpSleep .i { font-size:16px; line-height:1 }
html.obn-phone .nowFab.full .obnNpSleep .l {
  font:600 8.5px/1 var(--mono,monospace);
  letter-spacing:.11em;
}
/* a sleep timer that is armed says so in the peak hue, like any other
   countdown in the app */
html.obn-phone .nowFab.full .obnNpSleep.on {
  color:var(--dial-peak,#ffd400);
  -webkit-text-fill-color:var(--dial-peak,#ffd400);
}

/* ---------------------------------------------------------------- 3
 * The disc. .obnNpDisc is built by np-phone.js around the vendor's own #mpArt
 * and is display:contents everywhere else, so off the phone — and in this
 * card's docked, minimised and desktop states — the box tree is as it was.
 *
 * The ring sweeps the current minute. Radio is live: there is no duration to
 * fill, so nothing here pretends to be progress through a track.
 */
.obnNpDisc { display:contents }
/* the disc's parts are absolutely positioned, and with the wrapper at
   display:contents they would otherwise resolve against the card itself and
   paint over the desktop player */
.obnNpRing, .obnNpTime, .obnNpIni { display:none }

html.obn-phone .nowFab.full .mpRow {
  flex-direction:column !important;
  align-items:center !important;
  text-align:center;
  gap:0 !important;
  padding-top:8px;
}
html.obn-phone .nowFab.full .mpRow > div { width:100%; min-width:0 }

html.obn-phone .nowFab.full .obnNpDisc {
  display:block;
  position:relative;
  width:196px; height:196px;
  flex:none;
  margin:0 auto;
}
/* a coloured halo behind the disc, so the top of the screen carries the weight
   rather than the meter further down */
html.obn-phone .nowFab.full .obnNpDisc::before {
  content:"";
  position:absolute;
  inset:-26px;
  border-radius:50%;
  /* No filter:blur here. A radial gradient is already soft, and a blur pass
     under the animated ring was being recomputed every frame on the phone.
     The extra stops do the softening instead, for free. */
  background:radial-gradient(circle at 50% 42%,
    rgba(0,225,255,.30) 0%,
    rgba(0,225,255,.20) 22%,
    rgba(255,78,172,.16) 44%,
    rgba(255,127,0,.14) 60%,
    rgba(255,127,0,.05) 70%,
    transparent 78%);
  pointer-events:none;
  z-index:0;
}
html.obn-phone .nowFab.full .obnNpDisc .obnNpRing {
  display:block;
  position:absolute; inset:0; z-index:2;
  width:100%; height:100%;
  transform:rotate(-90deg);
  pointer-events:none;
}
html.obn-phone .nowFab.full .obnNpDisc #mpArt {
  width:166px !important; height:166px !important;
  position:absolute !important;
  left:15px !important; top:15px !important;
  z-index:1;
  margin:0 !important;
  border:0 !important;
  outline:0 !important;
  border-radius:50% !important;
  font-size:50px !important;
  box-shadow:
    0 0 0 2px rgba(0,225,255,.40),
    0 0 26px rgba(0,225,255,.22),
    0 14px 38px rgba(0,0,0,.60) !important;
}
html.obn-phone .nowFab.full .obnNpDisc #mpArt.hasNpLogo,
html.obn-phone .nowFab.full .obnNpDisc #mpArt.hasLogo {
  background-size:cover !important;
  background-position:center !important;
}
/* the station's initials, for the stations that have no logo. The vendor's own
   ::after tag is suppressed here: at this size it was the only thing showing. */
html.obn-phone .nowFab.full .obnNpDisc #mpArt.hasTag::after { display:none !important }
/* font-size:0 so the vendor's 📻 fallback glyph does not sit behind the
   initials — the two were drawn on top of each other */
html.obn-phone .nowFab.full .obnNpDisc #mpArt:not(.hasNpLogo):not(.hasLogo) {
  background:linear-gradient(140deg,#20405e,#122033) !important;
  font-size:0 !important;
}
html.obn-phone .nowFab.full .obnNpDisc .obnNpIni {
  position:absolute; inset:15px; z-index:2;
  display:flex; align-items:center; justify-content:center;
  border-radius:50%;
  pointer-events:none;
  font:800 50px/1 var(--mono,monospace);
  letter-spacing:.04em;
  /* literal, not the role variable: these sit on the disc's own dark gradient
     in both themes, so the light-surface hue would be dark-on-dark here */
  color:#00e1ff;
  -webkit-text-fill-color:#00e1ff;
  text-shadow:0 0 22px rgba(0,225,255,.45);
}

html.obn-phone .nowFab.full .obnNpDisc .obnNpTime {
  display:block;
  position:absolute; left:50%; bottom:-7px; z-index:3;
  transform:translateX(-50%);
  background:rgba(8,11,18,.90);
  border:1px solid rgba(255,127,0,.55);
  border-radius:999px;
  padding:5px 15px;
  font:700 15px/1 var(--mono,monospace);
  letter-spacing:.06em;
  /* literal for the same reason: the clock's own pill is dark in both themes */
  color:#ff7f00;
  -webkit-text-fill-color:#ff7f00;
  white-space:nowrap;
}

/* ---------------------------------------------------------------- 4
 * Who it is, and what is on.
 *
 * Both lines are real: the small line is the station and where it broadcasts
 * from, the big line is the ICY StreamTitle the station is sending right now
 * (window.__nowTrack, polled from /meta) and falls back to the station's name
 * when a station sends no track metadata — never an invented title.
 *
 * np-phone.js owns these two elements and adds .obnNpSplit to the card, which
 * is what hides the vendor's own single label. If that script never runs the
 * class is never added, so the vendor label is still there and nothing is lost.
 */
html.obn-phone .nowFab.full.obnNpSplit .lbl,
html.obn-phone .nowFab.full.obnNpSplit .mpState { display:none !important }

html.obn-phone .nowFab.full .obnNpStn {
  display:block;
  margin-top:18px;
  font:700 11.5px/1.45 var(--mono,monospace);
  letter-spacing:.2em;
  text-transform:uppercase;
  color:var(--dial-head,#00e1ff);
  -webkit-text-fill-color:var(--dial-head,#00e1ff);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
html.obn-phone .nowFab.full .obnNpNow {
  display:block;
  margin-top:8px;
  max-width:none !important;
  font-size:23px;
  font-weight:700;
  line-height:1.25;
  color:var(--text,#eaf2fb);
  -webkit-text-fill-color:var(--text,#eaf2fb);
  /* two lines at most, then an ellipsis — a long track title must not push the
     transport off the screen */
  display:-webkit-box;
  -webkit-line-clamp:2;
  -webkit-box-orient:vertical;
  overflow:hidden;
}
/* belt and braces: the vendor's 120px cap is on the id, so if the split ever
   fails the big label still gets the room it needs */
html.obn-phone .nowFab.full #nowLbl {
  max-width:none !important;
  font-size:19px !important;
  white-space:normal !important;
}

html.obn-phone .nowFab.full .mpMeta {
  justify-content:center !important;
  margin:11px 0 0 !important;
  gap:6px !important;
}
html.obn-phone .nowFab.full .mpMeta em {
  font-size:10.5px !important;
  font-weight:600;
  letter-spacing:.09em;
  text-transform:uppercase;
  padding:6px 13px !important;
  border-radius:999px !important;
  /* the fallback is the label hue; np-phone.js re-tags each badge by what it
     actually says, because the vendor's /k$/ bitrate test also matches rock,
     folk, funk and talk */
  color:var(--dial-label,#af79f6) !important;
  -webkit-text-fill-color:var(--dial-label,#af79f6) !important;
  background:rgba(175,121,246,.11) !important;
  border:1px solid rgba(175,121,246,.45) !important;
}
/* genre, bitrate, country — in that order, whatever order they arrived in */
html.obn-phone .nowFab.full .mpMeta em.obnNpG {
  order:1;
  color:var(--dial-tag,#ff4eac) !important;
  -webkit-text-fill-color:var(--dial-tag,#ff4eac) !important;
  background:rgba(255,78,172,.11) !important;
  border-color:rgba(255,78,172,.45) !important;
}
html.obn-phone .nowFab.full .mpMeta em.obnNpB {
  order:2;
  color:var(--dial-peak,#ffd400) !important;
  -webkit-text-fill-color:var(--dial-peak,#ffd400) !important;
  background:rgba(255,212,0,.10) !important;
  border-color:rgba(255,212,0,.45) !important;
}
html.obn-phone .nowFab.full .mpMeta em.obnNpC { order:3 }

/* ---------------------------------------------------------------- 5
 * Favourite and share, where a thumb can reach them. ♡ is the vendor's own
 * #mpFav, moved down out of the top cluster — the same button, same handler,
 * same "on" state when the station is already a favourite.
 */
html.obn-phone .nowFab.full .obnNpActs {
  display:flex;
  justify-content:center;
  gap:18px;
  margin-top:14px;
}
html.obn-phone .nowFab.full .obnNpActs > button,
html.obn-phone .nowFab.full .obnNpActs > #mpFav {
  width:42px !important; height:42px !important;
  min-height:0 !important;
  padding:0 !important;
  border-radius:50% !important;
  background:rgba(255,127,0,.09) !important;
  border:1px solid rgba(255,127,0,.45) !important;
  color:var(--dial-value,#ff7f00) !important;
  -webkit-text-fill-color:var(--dial-value,#ff7f00) !important;
  font-size:16px !important;
  display:flex !important; align-items:center; justify-content:center;
  cursor:pointer;
  position:static !important;
}
/* an already-favourited station keeps the app's red heart, as on every other
   screen — this is the one place the six roles give way to an existing signal */
html.obn-phone .nowFab.full .obnNpActs > #mpFav.on {
  color:#ff3b5c !important;
  -webkit-text-fill-color:#ff3b5c !important;
  border-color:rgba(255,59,92,.6) !important;
  background:rgba(255,59,92,.12) !important;
}

/* ---------------------------------------------------------------- 6
 * The meter. This is the vendor's live #rpViz canvas drawn in the "np" style —
 * smooth bars, yellow at the top and orange at the base, on the card's own
 * background rather than the LED meter's grey faceplate. It moves with the
 * audio: it is a readout, not decoration.
 */
/* The ten-band meter is gone from the phone player: 92px of an 852px screen,
 * and for most stations it was not showing anything real — the analyser only
 * has data for same-origin audio and almost every directory stream is
 * cross-origin, so it fell back to a synthetic sine. Hidden, not removed, so
 * the desktop and tablet player keeps its meter and nothing else breaks. */
html.obn-phone .nowFab.full .rpViz { display:none !important }

/* In its place, 30px that says the same thing and means it. The state comes
 * from the app's own playback state (np-phone.js), and the motion is CSS, so
 * the phone is not running a canvas at 60fps to draw a wave nobody needed. */
html.obn-phone .nowFab.full .obnNpLive {
  order:3;
  display:flex;
  align-items:center;
  gap:10px;
  margin:18px 2px 0;
  padding:8px 14px;
  border-radius:999px;
  background:rgba(0,255,34,.08);
  border:1px solid rgba(0,255,34,.34);
}
html.obn-phone .nowFab.full .obnNpLiveDot {
  width:9px; height:9px; flex:none;
  border-radius:50%;
  background:var(--dial-ok,#00ff22);
  box-shadow:0 0 10px var(--dial-ok,#00ff22);
  animation:obnNpBlink 1.4s ease-in-out infinite;
}
html.obn-phone .nowFab.full .obnNpLiveTxt {
  flex:1;
  font:700 10.5px/1 var(--mono,monospace);
  letter-spacing:.2em;
  color:var(--dial-ok,#00ff22);
  -webkit-text-fill-color:var(--dial-ok,#00ff22);
}
html.obn-phone .nowFab.full .obnNpLiveBars {
  display:flex; align-items:flex-end; gap:3px;
  height:16px; flex:none;
}
html.obn-phone .nowFab.full .obnNpLiveBars i {
  width:3px;
  border-radius:2px;
  background:linear-gradient(180deg,var(--dial-peak,#ffd400),var(--dial-value,#ff7f00));
  animation:obnNpBar .9s ease-in-out infinite;
}
html.obn-phone .nowFab.full .obnNpLiveBars i:nth-child(1){ animation-delay:0s;    height:40% }
html.obn-phone .nowFab.full .obnNpLiveBars i:nth-child(2){ animation-delay:.18s;  height:70% }
html.obn-phone .nowFab.full .obnNpLiveBars i:nth-child(3){ animation-delay:.36s;  height:100% }
html.obn-phone .nowFab.full .obnNpLiveBars i:nth-child(4){ animation-delay:.54s;  height:60% }
html.obn-phone .nowFab.full .obnNpLiveBars i:nth-child(5){ animation-delay:.72s;  height:85% }

@keyframes obnNpBlink { 0%,100%{opacity:1} 50%{opacity:.25} }
@keyframes obnNpBar   { 0%,100%{transform:scaleY(.35)} 50%{transform:scaleY(1)} }

/* Paused and connecting are not live, and must not look it. The bars stop
 * rather than slow down — motion is the signal that sound is coming out. */
html.obn-phone .nowFab.full .obnNpLive.is-paused,
html.obn-phone .nowFab.full .obnNpLive.is-wait {
  background:rgba(255,212,0,.08);
  border-color:rgba(255,212,0,.34);
}
html.obn-phone .nowFab.full .obnNpLive.is-paused .obnNpLiveTxt,
html.obn-phone .nowFab.full .obnNpLive.is-wait .obnNpLiveTxt {
  color:var(--dial-peak,#ffd400);
  -webkit-text-fill-color:var(--dial-peak,#ffd400);
}
html.obn-phone .nowFab.full .obnNpLive.is-paused .obnNpLiveDot,
html.obn-phone .nowFab.full .obnNpLive.is-wait .obnNpLiveDot {
  background:var(--dial-peak,#ffd400);
  box-shadow:none;
}
html.obn-phone .nowFab.full .obnNpLive.is-paused .obnNpLiveDot { animation:none }
html.obn-phone .nowFab.full .obnNpLive.is-paused .obnNpLiveBars i,
html.obn-phone .nowFab.full .obnNpLive.is-wait .obnNpLiveBars i {
  animation:none;
  transform:scaleY(.35);
  background:rgba(255,212,0,.5);
}

/* Reduce Motion gets the state without the movement: the colour and the words
 * already carry it, so nothing is lost by holding everything still. */
@media (prefers-reduced-motion: reduce) {
  html.obn-phone .nowFab.full .obnNpLiveDot,
  html.obn-phone .nowFab.full .obnNpLiveBars i { animation:none !important }
}

/* ---------------------------------------------------------------- 7
 * Transport. Play/pause is the control a thumb reaches for without looking, so
 * it is the big one and it carries the live green. SCAN keeps its place but
 * drops to its own line: it is a real feature and is not worth losing, but it
 * is not what a listener reaches for first.
 */
html.obn-phone .nowFab.full .rpCtrls {
  flex-wrap:nowrap;
  gap:13px !important;
  margin:16px 0 0 !important;
}
/* These were reading as plain grey discs on a real phone — the vendor's tint is
 * a 10% wash that all but disappears on a small bright screen. Stated outright
 * here, with a glow, so the transport row carries the same cyan as the headings
 * and the catalog's play buttons. */
html.obn-phone .nowFab.full #rpPrev,
html.obn-phone .nowFab.full #rpNext {
  width:48px !important; height:48px !important;
  font-size:14px !important;
  background:rgba(0,225,255,.14) !important;
  border:1px solid rgba(0,225,255,.55) !important;
  color:var(--dial-head,#00e1ff) !important;
  -webkit-text-fill-color:var(--dial-head,#00e1ff) !important;
  box-shadow:0 0 16px rgba(0,225,255,.22) !important;
}
html.obn-phone .nowFab.full #nowPause {
  width:64px !important; height:64px !important;
  font-size:28px !important;
  background:rgba(0,255,34,.12) !important;
  border-color:rgba(0,255,34,.50) !important;
  color:var(--dial-ok,#00ff22) !important;
  -webkit-text-fill-color:var(--dial-ok,#00ff22) !important;
  box-shadow:0 0 24px rgba(0,255,34,.20) !important;
}
/* SCAN rides on the end of the transport row rather than taking a line of its
   own: it is a real control and stays, but it is not what a thumb goes for. */
html.obn-phone .nowFab.full #rpScan {
  width:auto !important;
  height:30px !important;
  font-size:9px !important;
  letter-spacing:.07em !important;
  padding:0 11px !important;
  margin:0;
  flex:0 0 auto;
  background:rgba(255,212,0,.13) !important;
  border:1px solid rgba(255,212,0,.50) !important;
  color:var(--dial-peak,#ffd400) !important;
  -webkit-text-fill-color:var(--dial-peak,#ffd400) !important;
}
/* scanning is a running state, so it says so rather than staying amber */
html.obn-phone .nowFab.full #rpScan.on {
  background:rgba(0,255,34,.16) !important;
  border-color:rgba(0,255,34,.6) !important;
  color:var(--dial-ok,#00ff22) !important;
  -webkit-text-fill-color:var(--dial-ok,#00ff22) !important;
}

/* ---------------------------------------------------------------- 8
 * Volume and tuning, in the violet the app uses for keys and controls.
 */
/* bare rows: the vendor gives these a panel of their own, which on a full
   screen reads as two grey boxes rather than two controls */
html.obn-phone .nowFab.full .mpVolWrap,
html.obn-phone .nowFab.full .rpTune {
  margin:16px 8px 0 !important;
  padding:0 !important;
  gap:12px !important;
  background:none !important;
  border:0 !important;
  box-shadow:none !important;
}
html.obn-phone .nowFab.full .rpTune span,
html.obn-phone .nowFab.full .mpVolWrap > span {
  color:var(--dial-label,#af79f6) !important;
  -webkit-text-fill-color:var(--dial-label,#af79f6) !important;
  font-size:11px !important;
}
/* -webkit-appearance:none is the whole trick on iOS. Without it Safari keeps
 * its native control and ignores ::-webkit-slider-thumb entirely, which is why
 * the volume slider rendered as a plain white iOS bar on a real iPhone while
 * looking correctly violet in a desktop Chromium. accent-color alone does not
 * cover it either. */
html.obn-phone .nowFab.full #mpVol,
html.obn-phone .nowFab.full #rpTuner {
  -webkit-appearance:none !important;
  appearance:none !important;
  accent-color:var(--dial-label,#af79f6);
  /* the touch layer puts min-height:var(--tap) on every range thumb, which
     stretched this one into a 44px-tall ellipse. The tap target stays 44px by
     living on the input itself; the thumb goes back to being round. */
  height:44px !important;
  background:transparent !important;
  border:0 !important;
}
html.obn-phone .nowFab.full #mpVol::-webkit-slider-runnable-track,
html.obn-phone .nowFab.full #rpTuner::-webkit-slider-runnable-track {
  height:6px !important;
  border-radius:999px !important;
  border:0 !important;
  background:linear-gradient(90deg,
    var(--dial-label,#af79f6) 0%,
    rgba(175,121,246,.45) 62%,
    rgba(130,160,200,.22) 62%) !important;
}
html.obn-phone .nowFab.full #mpVol::-webkit-slider-thumb,
html.obn-phone .nowFab.full #rpTuner::-webkit-slider-thumb {
  -webkit-appearance:none !important;
  appearance:none !important;
  width:22px !important;
  height:22px !important;
  min-height:0 !important;
  margin-top:-8px !important;      /* centres a 22px thumb on a 6px track */
  border-radius:50% !important;
  background:var(--dial-label,#af79f6) !important;
  border:2px solid #fff !important;
  box-shadow:0 0 12px rgba(175,121,246,.85) !important;
}
html.obn-phone .nowFab.full #mpVol::-moz-range-track,
html.obn-phone .nowFab.full #rpTuner::-moz-range-track {
  height:6px; border-radius:999px; background:rgba(130,160,200,.22);
}
html.obn-phone .nowFab.full #mpVol::-moz-range-thumb,
html.obn-phone .nowFab.full #rpTuner::-moz-range-thumb {
  width:20px; height:20px; border-radius:50%; border:2px solid #fff;
  background:var(--dial-label,#af79f6);
}

/* ---------------------------------------------------------------- 9
 * More of the catalog, without leaving the player. This block is built by the
 * site-config script in index.html; only its appearance is set here.
 */
html.obn-phone .nowFab.full .obnNpRes { margin-top:16px !important }
/* the results list scrolls inside itself, so a shorter box costs nothing and
   leaves room for the presets below it to be reached */
html.obn-phone .nowFab.full .obnNpList { max-height:230px !important }
html.obn-phone .nowFab.full .obnNpRes .rpSec span {
  font:700 9.5px/1 var(--mono,monospace) !important;
  letter-spacing:.15em;
  color:var(--dial-head,#00e1ff) !important;
  -webkit-text-fill-color:var(--dial-head,#00e1ff) !important;
}
html.obn-phone .nowFab.full .obnNpRes .obnNpMore {
  border:1px solid rgba(255,127,0,.45) !important;
  color:var(--dial-value,#ff7f00) !important;
  -webkit-text-fill-color:var(--dial-value,#ff7f00) !important;
  font:600 10.5px/1 var(--mono,monospace) !important;
  padding:6px 12px !important;
}

/* ---------------------------------------------------------------- 10
 * The dock: a strip above the phone's tab bar that stays while the catalog is
 * open, so the station you are listening to is never off screen and never
 * without a control. Tapping it puts the full screen back.
 *
 * It is the same green as the pinned row in the catalog (now-playing.css) and
 * for the same reason: in this app, green means this one is playing.
 */
.obnNpDock { display:none }
/* !important on the position: something in the app's own layers sets
   position:relative on body-level divs and wins on specificity, which left the
   dock laid out in the page flow 4000px down instead of pinned to the screen.
   z-index 101 clears the catalog overlay's 100 — the dock has to be reachable
   while the catalog is the thing on screen, which is the whole point of it. */
html.obn-phone .obnNpDock.on {
  display:flex !important;
  position:fixed !important;
  left:0 !important; right:0 !important;
  bottom:calc(var(--obnTabsH,0px) + env(safe-area-inset-bottom,0px)) !important;
  z-index:101 !important;
  align-items:center;
  gap:10px;
  padding:8px 11px;
  background:linear-gradient(135deg,#0a7019 0%,#0b7a1b 55%,#0c851d 100%);
  border-top:1px solid #00ff22;
  box-shadow:0 -5px 18px rgba(0,255,34,.18);
  cursor:pointer;
}
html.obn-phone .obnNpDock.paused {
  background:linear-gradient(135deg,#1e2a3e 0%,#243147 100%);
  border-top-color:rgba(130,160,200,.4);
  box-shadow:0 -5px 18px rgba(0,0,0,.45);
}
.obnNpDock .obnNpDockArt {
  width:36px; height:36px; flex:none;
  border-radius:9px;
  background:rgba(0,0,0,.30);
  background-size:cover; background-position:center;
  color:#dcffe2;
  font:700 16px/36px var(--mono,monospace);
  text-align:center;
  overflow:hidden;
}
.obnNpDock .obnNpDockTxt { flex:1; min-width:0; text-align:left }
.obnNpDock .obnNpDockTxt b {
  display:block;
  font-size:12.5px;
  color:#fff;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.obnNpDock .obnNpDockTxt i {
  display:block;
  font-style:normal;
  font:600 9px/1 var(--mono,monospace);
  letter-spacing:.12em;
  color:#b6f5c2;
  margin-top:3px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.obnNpDock.paused .obnNpDockTxt i { color:#9fb3cc }
.obnNpDock button {
  width:32px; height:32px; flex:none;
  border-radius:50%;
  background:rgba(0,0,0,.30);
  border:1px solid rgba(255,255,255,.45);
  color:#fff;
  font-size:13px;
  display:flex; align-items:center; justify-content:center;
  cursor:pointer;
}
.obnNpDock button:active { background:rgba(0,0,0,.5) }

/* ---------------------------------------------------------------- 10b
 * A phone held sideways.
 *
 * The upright layout stacks disc, title, badges, actions, meter and transport.
 * At 844x390 that pushes play/pause below the fold: measured, the transport sat
 * off the bottom of the screen and you had to scroll the card to pause what you
 * were listening to. A control you need right now must never need a scroll.
 *
 * So sideways the disc moves beside the words rather than above them, and
 * everything above the meter gets tighter. The results list and the drawer stay
 * below the fold, which is fine — those you are willing to scroll for.
 */
html.obn-phone.obn-landscape .nowFab.full .rpHead { min-height:32px; padding-top:0 !important }

html.obn-phone.obn-landscape .nowFab.full .mpRow {
  flex-direction:row !important;
  align-items:center !important;
  gap:18px !important;
  padding-top:0;
  text-align:left;
}
html.obn-phone.obn-landscape .nowFab.full .mpRow > div { flex:1 1 auto }

html.obn-phone.obn-landscape .nowFab.full .obnNpDisc {
  width:116px; height:116px;
  margin:0;
}
html.obn-phone.obn-landscape .nowFab.full .obnNpDisc #mpArt {
  width:98px !important; height:98px !important;
  left:9px !important; top:9px !important;
}
html.obn-phone.obn-landscape .nowFab.full .obnNpDisc .obnNpIni {
  inset:9px;
  font-size:30px;
}
html.obn-phone.obn-landscape .nowFab.full .obnNpDisc .obnNpTime {
  bottom:2px;
  padding:3px 8px;
  font-size:10.5px;
}

html.obn-phone.obn-landscape .nowFab.full .obnNpStn { margin-top:0 }
html.obn-phone.obn-landscape .nowFab.full .obnNpNow {
  margin-top:4px;
  font-size:17px;
  -webkit-line-clamp:1;
}
html.obn-phone.obn-landscape .nowFab.full .mpMeta { justify-content:flex-start !important; margin-top:7px !important }
html.obn-phone.obn-landscape .nowFab.full .obnNpActs { justify-content:flex-start; margin-top:9px }

html.obn-phone.obn-landscape .nowFab.full .rpViz { height:56px !important; margin-top:9px }
html.obn-phone.obn-landscape .nowFab.full .rpCtrls { margin-top:9px !important; gap:12px !important }
html.obn-phone.obn-landscape .nowFab.full #nowPause { width:52px !important; height:52px !important; font-size:22px !important }
html.obn-phone.obn-landscape .nowFab.full #rpPrev,
html.obn-phone.obn-landscape .nowFab.full #rpNext { width:42px !important; height:42px !important }
html.obn-phone.obn-landscape .nowFab.full .mpVolWrap { margin-top:9px !important }

/* ---------------------------------------------------------------- 11
 * The station rows, on phones.
 *
 * A row carried four things at once: the name, a playback progress bar, four
 * pill badges that overflowed their own container (the last one clipped
 * mid-word), and three buttons. At 390px that is four competing elements in
 * 56px of height and none of them readable.
 *
 * What a listener is choosing between is the name, where it broadcasts from,
 * how good the stream is and what it plays — so that is what the row says, on
 * two lines, with one button. Same markup, same handlers; only what is shown.
 *
 * This covers the catalog list and the results list inside the player, because
 * they are the same component and should not read differently.
 */
html.obn-phone #qsList .qsRow,
html.obn-phone .obnNpList .qsRow {
  align-items:center !important;
  gap:10px !important;
  padding:8px 9px !important;
  border-radius:13px !important;
}
html.obn-phone #qsList .qsThumb,
html.obn-phone .obnNpList .qsThumb {
  width:40px !important; height:40px !important;
  border-radius:10px !important;
  flex:none;
  outline:none !important;
  border:0 !important;
}
/* the station's name is what the row is for */
html.obn-phone #qsList .qsRow .qsInfo b,
html.obn-phone .obnNpList .qsRow .qsInfo b {
  font-size:13.5px !important;
  font-weight:700 !important;
  color:var(--text,#eaf2fb) !important;
  -webkit-text-fill-color:var(--text,#eaf2fb) !important;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  display:block;
}
/* the playback bar belongs to the station that is playing, not to a list of
   candidates; the pinned green row already says which one that is */
html.obn-phone #qsList .qsRow .qsLive,
html.obn-phone .obnNpList .qsRow .qsLive { display:none !important }

/* the badges become one quiet line: city · bitrate · genre */
html.obn-phone #qsList .qsRow .qsMeta,
html.obn-phone .obnNpList .qsRow .qsMeta {
  display:flex !important;
  flex-wrap:nowrap !important;
  align-items:baseline;
  gap:0 !important;
  margin-top:3px !important;
  overflow:hidden;
}
/* the vendor's own phone block is `html.obn-phone body .qsOverlay #qsList
   .qsMeta em` with !important, so the reset has to carry the same weight or the
   badges keep their pill padding and background */
html.obn-phone body .qsOverlay #qsList .qsRow .qsMeta em,
html.obn-phone body .nowFab .obnNpList .qsRow .qsMeta em,
html.obn-phone body .obnNpList .qsRow .qsMeta em,
html.obn-phone #qsList .qsRow .qsMeta em,
html.obn-phone .obnNpList .qsRow .qsMeta em {
  font-style:normal !important;
  font:500 10.5px/1.35 var(--mono,monospace) !important;
  letter-spacing:.02em !important;
  padding:0 !important;
  border:0 !important;
  background:none !important;
  border-radius:0 !important;
  color:var(--obnRowMeta,#8ba0bd) !important;
  -webkit-text-fill-color:var(--obnRowMeta,#8ba0bd) !important;
  white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis;
  flex:0 1 auto;
  min-width:0;
}
html.obn-phone #qsList .qsRow .qsMeta em + em::before,
html.obn-phone .obnNpList .qsRow .qsMeta em + em::before {
  content:" · ";
  opacity:.55;
}
/* No `order` here on purpose. The separator is drawn with `em + em::before`,
   which follows DOM order, so reordering the badges visually put a leading "·"
   in front of whichever one moved to the front. The badges keep the order the
   app builds them in — place, genre, bitrate — and the separators line up. */
html.obn-phone #qsList .qsRow .qsMeta em.geo,
html.obn-phone .obnNpList .qsRow .qsMeta em.geo { flex-shrink:2 }
html.obn-phone #qsList .qsRow .qsMeta em.br,
html.obn-phone .obnNpList .qsRow .qsMeta em.br { flex-shrink:0 }
/* IN-APP says how the app will open the stream — useful in the station's own
   details, noise in a list of forty */
html.obn-phone #qsList .qsRow .qsMeta em.routeBadge,
html.obn-phone .obnNpList .qsRow .qsMeta em.routeBadge { display:none !important }

/* one button. ♡ and ⚑ stay on the station's details and, for whatever is
   playing, on the player itself. */
html.obn-phone #qsList .qsRow .qsFav,
html.obn-phone #qsList .qsRow .qsFlag,
html.obn-phone .obnNpList .qsRow .qsFav,
html.obn-phone .obnNpList .qsRow .qsFlag { display:none !important }
html.obn-phone #qsList .qsRow .qsActs,
html.obn-phone .obnNpList .qsRow .qsActs { flex:none !important; gap:0 !important }
html.obn-phone #qsList .qsRow .qsPlay,
html.obn-phone .obnNpList .qsRow .qsPlay {
  width:36px !important; height:36px !important;
  min-height:0 !important;
  border-radius:50% !important;
  font-size:12px !important;
  background:rgba(0,225,255,.10) !important;
  border:1px solid rgba(0,225,255,.38) !important;
  color:var(--dial-head,#00e1ff) !important;
  -webkit-text-fill-color:var(--dial-head,#00e1ff) !important;
}
/* `html body .qsRow.on::after` pins a white "● ON AIR" pill to the row's
   top-right corner. At the desktop row height it sits in empty space; at 56px
   it lands squarely on the play button. On a phone the green row, its position
   at the top of the list and the dock all already say which station is playing,
   so the badge is three ways redundant and one way in the way. */
html.obn-phone body .qsRow.on::after { display:none !important }

/* The playing row inside the player's own results list matches now-playing.css's
   `html body .qsRow.on` — verified with .matches() — and still painted the old
   blue, so something scoped inside .obnNpList outranks it. Rather than hunt the
   winner, the green is restated here, in the last stylesheet loaded, at a weight
   nothing in that list reaches. The catalog's copy of the row was never affected;
   this is only the in-player list. */
html.obn-phone body .nowFab .obnNpList .qsRow.on {
  background-image:linear-gradient(135deg,#0a7019 0%,#0b7a1b 55%,#0c851d 100%) !important;
  background-color:#0a7019 !important;
  border:1px solid #00ff22 !important;
}

/* on the row that is playing, the button sits on green and has to read on it */
html.obn-phone #qsList .qsRow.on .qsPlay,
html.obn-phone .obnNpList .qsRow.on .qsPlay {
  background:rgba(0,0,0,.32) !important;
  border-color:rgba(255,255,255,.55) !important;
  color:#fff !important;
  -webkit-text-fill-color:#fff !important;
}
html.obn-phone body .qsOverlay #qsList .qsRow.on .qsInfo b,
html.obn-phone body .obnNpList .qsRow.on .qsInfo b,
html.obn-phone #qsList .qsRow.on .qsInfo b,
html.obn-phone .obnNpList .qsRow.on .qsInfo b {
  color:#fff !important;
  -webkit-text-fill-color:#fff !important;
}
/* These must carry at least the weight of the reset above, or the reset — which
   is more specific than a plain `.qsRow.on` selector — repaints the playing row
   in muted grey. Measured on the green: #94a3b8 lands at 2.45:1, #b6f5c2 at
   8.0:1. */
html.obn-phone body .qsOverlay #qsList .qsRow.on .qsMeta em,
html.obn-phone body .nowFab .obnNpList .qsRow.on .qsMeta em,
html.obn-phone body .obnNpList .qsRow.on .qsMeta em,
html.obn-phone #qsList .qsRow.on .qsMeta em,
html.obn-phone .obnNpList .qsRow.on .qsMeta em {
  color:#b6f5c2 !important;
  -webkit-text-fill-color:#b6f5c2 !important;
}

/* The floating share and donate docks are positioned above this card's z-index
   and land on the results list. They are hidden only while the full-screen
   player is actually up, and return the moment it closes. */
html.obn-phone body.obnNpFull #obnShareLaunch,
html.obn-phone body.obnNpFull #obnDonateDock { display:none !important }

/* the catalog's own list ends above the dock rather than behind it */
html.obn-phone body.obnNpDocked .qsOverlay #qsList { padding-bottom:78px !important }
