@font-face {
    font-family: 'Anton';
    src: url('fonts/Anton.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: transparent;
    user-select: none;
}

/* Declared on :root only - repeating them on `body` would shadow the scale
   value app.js writes to the root element.

   The defaults below are the single-player HUD's numbers (ViExtras RadioHud,
   tools/render/radio.py), which config.lua's Config.Hud sends again. --ink is
   the one exception: unset it stays white, the original's text, because the
   single-player render (radio.py) captures this page white and tints it
   itself. config.lua sets it to the grey. */
:root {
    /* --s is the 1080p -> actual resolution scale, set from app.js */
    --s: 1;
    --icon: 103px;
    --spacing: 118px;
    --top: 32px;
    --border: 2.5px;
    --border-px: 2px;   /* --border at this scale, snapped to device pixels (app.js) */
    --box: rgba(0, 0, 0, 0.667);
    --box-active: rgba(0, 0, 0, 0.667);
    --outline: rgba(255, 255, 255, 0);
    --outline-active: rgba(192, 194, 200, 1);
    --ink: #ffffff;
    --tile-scale-on: 1.03;
    --tile-scale-off: 1;
    --tile-opacity-off: 1;
    --text-shadow: none;
    --slide: 110ms;
    --side-gap: 37px;
    --left-icon-h: 55px;
    --right-icon-h: 24px;
    --mute-color: #ffffff;
    --muted-color: #ff3b30;
    --line-y: 20px;
    --line-w: 189px;
    --line-h: 4.8px;
    --station-y: 34.7px;
    --station-size: 25.3px;
    --title-y: 70.6px;
    --title-size: 20.6px;
    --artist-y: 97.2px;
    --artist-size: 20.6px;
}

#radio {
    position: absolute;
    top: calc(var(--top) * var(--s));
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    opacity: 1;
    transition: opacity 160ms ease;
}

#radio.hidden,
#radio.held {
    opacity: 0;
    pointer-events: none;
}

/* --- carousel ------------------------------------------------------------ */

/* Five slots wide. The side icons are absolutely positioned so they cannot
   push the carousel off centre - the info block below is centred on the same
   axis. Nothing is clipped: app.js places every tile and fades the ones past
   two slots out, as the single-player wheel draws them (whole out to two
   either side, gone by three). */
#carousel {
    position: relative;
    width: calc(var(--spacing) * 5 * var(--s));
    height: calc(var(--icon) * var(--s));
}

#viewport {
    position: relative;
    width: 100%;
    height: calc(var(--icon) * var(--s));
}

/* A point on the carousel's centre; each .slot is placed off it by app.js. */
#track {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
}

.slot {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
}

/* The logo fills the tile, the box colour only shows behind its transparent
   corners. The outline is the tile's border, outside its 103px, drawn only
   round the highlighted one (border-box would eat into the logo). The
   browser draws borders in whole device pixels, so app.js hands over the
   width already snapped (--border-px) and the transform moves the tile back
   by exactly that. (In the transform, not in left/top: the single-player
   repo's tools/render/radio.py renders this page with the tiles' border and
   transform off, and its tiles then sit where they always have.) */
.tile {
    position: absolute;
    box-sizing: content-box;
    left: calc(var(--icon) / -2 * var(--s));
    top: calc(var(--icon) / -2 * var(--s));
    width: calc(var(--icon) * var(--s));
    height: calc(var(--icon) * var(--s));
    background: var(--box);
    background-clip: padding-box;
    border: var(--border-px) solid var(--outline);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    opacity: var(--tile-opacity-off);
    transform: translate(calc(-1 * var(--border-px)), calc(-1 * var(--border-px))) scale(var(--tile-scale-off));
    transition: background var(--slide) ease,
                border-color var(--slide) ease,
                opacity var(--slide) ease,
                transform var(--slide) cubic-bezier(0.22, 0.61, 0.36, 1);
}

.tile.active {
    background: var(--box-active);
    background-clip: padding-box;
    border-color: var(--outline-active);
    opacity: 1;
    transform: translate(calc(-1 * var(--border-px)), calc(-1 * var(--border-px))) scale(var(--tile-scale-on));
}

.tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    image-rendering: auto;
}

/* Stations without a logo file fall back to their name. */
.tile .fallback {
    font-family: 'Anton', Impact, sans-serif;
    font-size: calc(15px * var(--s));
    line-height: 1.1;
    letter-spacing: 0.02em;
    text-align: center;
    text-transform: uppercase;
    color: #fff;
    padding: 0 8%;
    word-break: break-word;
}

.side-icon {
    position: absolute;
    top: 50%;
    height: calc(var(--left-icon-h) * var(--s));
    width: auto;
    object-fit: contain;
    opacity: 0.9;
    transform: translateY(-50%);
}

#icon-left {
    right: 100%;
    margin-right: calc(var(--side-gap) * var(--s));
    height: auto;
}

#icon-left img {
    height: calc(var(--left-icon-h) * var(--s));
    width: auto;
    object-fit: contain;
}

/* --- the radio / on demand switch ---------------------------------------- */
/* Redrawn from the original artwork rather than shown as it ships, because the
   PNG has the knob baked in at RADIO and a switch that cannot move is a label.
   Sized off --left-icon-h, the same value the static icon used, so it lands
   where the original did at any resolution. */

#od-switch-graphic {
    display: flex;
    align-items: stretch;
    gap: calc(var(--left-icon-h) * 0.26 * var(--s));
}

/* display: flex above would otherwise beat the hidden attribute, and with On
   Demand off the switch showed beside the static artwork, twice over. */
#od-switch-graphic[hidden] { display: none; }

.sw-labels {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: flex-end;
    gap: calc(var(--left-icon-h) * 0.36 * var(--s));
}

.sw-label {
    font-family: 'Anton', Impact, sans-serif;
    font-size: calc(var(--left-icon-h) * 0.38 * var(--s));
    line-height: 1;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: #fff;
    white-space: nowrap;
    text-shadow: 0 calc(2px * var(--s)) calc(6px * var(--s)) rgba(0, 0, 0, 0.75);
    transition: opacity 160ms ease;
}

.sw-track {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
}

/* The line the knob travels along, tucked behind both dots. */
.sw-track::before {
    content: '';
    position: absolute;
    top: calc(var(--left-icon-h) * 0.19 * var(--s));
    bottom: calc(var(--left-icon-h) * 0.19 * var(--s));
    width: max(1px, calc(var(--left-icon-h) * 0.02 * var(--s)));
    background: rgba(255, 255, 255, 0.6);
}

.sw-dot {
    position: relative;
    width: calc(var(--left-icon-h) * 0.38 * var(--s));
    height: calc(var(--left-icon-h) * 0.38 * var(--s));
    border-radius: 50%;
    border: max(1px, calc(var(--left-icon-h) * 0.025 * var(--s))) solid rgba(255, 255, 255, 0.9);
    background: transparent;
    transition: background-color 160ms ease;
}

/* Off: knob at the top, on RADIO -- which is how the original ships. */
.sw-top    { background: #fff; }
.sw-radio  { opacity: 1; }
.sw-od     { opacity: 0.45; }

#radio.od-on .sw-top    { background: transparent; }
#radio.od-on .sw-bottom { background: #fff; }
#radio.od-on .sw-radio  { opacity: 0.45; }
#radio.od-on .sw-od     { opacity: 1; }

#icon-right {
    left: 100%;
    margin-left: calc(var(--side-gap) * var(--s));
    height: calc(var(--right-icon-h) * var(--s));
}

/* The mute icon is a pure white glyph, so it is drawn as a mask filled with a
   colour rather than an <img>. That is the only way to recolour it exactly -
   a CSS filter cannot turn white into an arbitrary hue. */
.mask-icon {
    width: auto;
    background-color: var(--mute-color);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
    transition: background-color 140ms ease;
}

/* Muted: everything except the indicator drains of colour, and the indicator
   goes red. The filter cannot live on #carousel because filters apply to
   descendants too - it would drag the indicator down with it. */
#radio.muted #viewport,
#radio.muted #icon-left,
#radio.muted #info {
    filter: grayscale(1) brightness(0.62);
}

#radio.muted #icon-right {
    opacity: 1;
    background-color: var(--muted-color);
}

#radio.dim-off #viewport,
#radio.dim-off #icon-left,
#radio.dim-off #info {
    filter: none;
}

/* --- info block ---------------------------------------------------------- */

#info {
    position: relative;
    width: calc(var(--line-w) * 3.4 * var(--s));
    height: calc((var(--artist-y) + 40px) * var(--s));
}

#info-line {
    position: absolute;
    top: calc(var(--line-y) * var(--s));
    left: 50%;
    transform: translateX(-50%);
    width: calc(var(--line-w) * var(--s));
    height: calc(var(--line-h) * var(--s));
    background: var(--ink);
}

#station, #title, #artist {
    position: absolute;
    left: 0;
    width: 100%;
    text-align: center;
    font-family: 'Anton', Impact, sans-serif;
    text-transform: uppercase;
    color: var(--ink);
    letter-spacing: 0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-shadow: var(--text-shadow);
}

#station {
    top: calc(var(--station-y) * var(--s));
    font-size: calc(var(--station-size) * var(--s));
    line-height: 1.05;
}

#title {
    top: calc(var(--title-y) * var(--s));
    font-size: calc(var(--title-size) * var(--s));
    line-height: 1.05;
}

#artist {
    top: calc(var(--artist-y) * var(--s));
    font-size: calc(var(--artist-size) * var(--s));
    line-height: 1.05;
}

/* The original's softer lines, with Config.Hud.textShadow on. */
#radio.shadowed #title  { opacity: 0.92; }
#radio.shadowed #artist { opacity: 0.62; }

#title:empty, #artist:empty { display: none; }

/* --- On Demand panel ------------------------------------------------------ */
/* Sized off the same --s scale as the HUD so it matches at any resolution.
   Unlike the HUD this takes NUI focus, so it is the only part of the page that
   is ever allowed pointer events. */

#od {
    position: absolute;
    inset: 0;
    display: flex;
    /* Pushed below the HUD rather than centred on it: the carousel stays
       readable above the panel instead of being sliced in half by it. */
    align-items: flex-start;
    justify-content: center;
    padding-top: calc(300px * var(--s));
    background: rgba(0, 0, 0, 0.55);
    opacity: 1;
    transition: opacity 140ms ease;
}

#od.hidden {
    opacity: 0;
    pointer-events: none;
}

#od-panel {
    width: calc(620px * var(--s));
    max-height: calc(700px * var(--s));
    display: flex;
    flex-direction: column;
    gap: calc(14px * var(--s));
    padding: calc(26px * var(--s)) calc(28px * var(--s)) calc(22px * var(--s));
    background: rgba(12, 12, 14, 0.94);
    border: calc(3.5px * var(--s)) solid #fff;
    color: #fff;
    font-family: system-ui, 'Segoe UI', Arial, sans-serif;
    font-size: calc(15px * var(--s));
}

#od-head {
    display: flex;
    align-items: center;
    gap: calc(14px * var(--s));
}

#od-head h1 { margin-right: auto; }

/* The switch is the panel's statement of what On Demand currently is: on.
   Clicking it is how you turn it off without going back to the wheel. */
#od button.switch {
    display: flex;
    align-items: center;
    gap: calc(9px * var(--s));
    padding: calc(6px * var(--s)) calc(10px * var(--s)) calc(6px * var(--s)) calc(6px * var(--s));
    background: none;
    border-color: var(--od-line);
}

.switch-track {
    position: relative;
    width: calc(40px * var(--s));
    height: calc(20px * var(--s));
    border-radius: calc(10px * var(--s));
    background: rgba(255, 255, 255, 0.16);
    transition: background 140ms ease;
}

.switch-knob {
    position: absolute;
    top: calc(3px * var(--s));
    left: calc(3px * var(--s));
    width: calc(14px * var(--s));
    height: calc(14px * var(--s));
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.5);
    transition: transform 140ms cubic-bezier(0.22, 0.61, 0.36, 1), background 140ms ease;
}

.switch-state {
    min-width: calc(30px * var(--s));
    text-align: left;
    opacity: 0.55;
}

#od button.switch[aria-checked="true"] .switch-track { background: var(--muted-color); }
#od button.switch[aria-checked="true"] .switch-knob {
    background: #fff;
    transform: translateX(calc(20px * var(--s)));
}
#od button.switch[aria-checked="true"] .switch-state { opacity: 1; }

#od-head h1 {
    font-family: 'Anton', Impact, sans-serif;
    font-size: calc(38px * var(--s));
    letter-spacing: 0.02em;
    text-transform: uppercase;
    font-weight: normal;
}

#od-now {
    padding: calc(14px * var(--s)) calc(16px * var(--s));
    background: var(--box);
    border-left: calc(4px * var(--s)) solid var(--muted-color);
}

#od-now-title {
    font-family: 'Anton', Impact, sans-serif;
    font-size: calc(22px * var(--s));
    text-transform: uppercase;
    letter-spacing: 0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

#od-now-artist {
    margin-top: calc(2px * var(--s));
    opacity: 0.6;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

#od-now-artist:empty { display: none; }

#od-transport {
    display: flex;
    gap: calc(8px * var(--s));
    margin-top: calc(12px * var(--s));
}

#od-form {
    display: flex;
    gap: calc(8px * var(--s));
}

/* The panel keeps the original's white lines: the HUD's outline colours above
   are the carousel's, and an invisible inactive outline is no use here. */
#od { --od-line: rgba(255, 255, 255, 0.353); }

#od-url {
    flex: 1 1 auto;
    min-width: 0;
    padding: calc(10px * var(--s)) calc(12px * var(--s));
    background: rgba(255, 255, 255, 0.07);
    border: calc(2px * var(--s)) solid var(--od-line);
    color: #fff;
    font: inherit;
    outline: none;
}

#od-url:focus { border-color: #fff; }
#od-url::placeholder { color: rgba(255, 255, 255, 0.35); }

#od button {
    padding: calc(10px * var(--s)) calc(14px * var(--s));
    background: rgba(255, 255, 255, 0.09);
    border: calc(2px * var(--s)) solid var(--od-line);
    color: #fff;
    font: inherit;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease, opacity 120ms ease;
    white-space: nowrap;
}

#od button:hover { background: rgba(255, 255, 255, 0.18); border-color: #fff; }
#od button.primary { background: #fff; color: #111; border-color: #fff; }
#od button.primary:hover { background: rgba(255, 255, 255, 0.85); }
#od button.danger:hover { background: var(--muted-color); border-color: var(--muted-color); }
#od button.ghost { background: none; border: none; opacity: 0.6; padding: 0; }
#od button.ghost:hover { opacity: 1; background: none; }
#od button:disabled { opacity: 0.3; cursor: default; }
#od button:disabled:hover { background: rgba(255, 255, 255, 0.09); border-color: var(--od-line); }

#od-notice {
    min-height: calc(18px * var(--s));
    font-size: calc(14px * var(--s));
    color: var(--muted-color);
}

#od-notice.ok { color: rgba(255, 255, 255, 0.65); }

#od-queue-head {
    display: flex;
    justify-content: space-between;
    padding-bottom: calc(6px * var(--s));
    border-bottom: calc(2px * var(--s)) solid var(--od-line);
    font-family: 'Anton', Impact, sans-serif;
    font-size: calc(16px * var(--s));
    text-transform: uppercase;
    letter-spacing: 0.06em;
    opacity: 0.8;
}

#od-queue {
    list-style: none;
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: calc(60px * var(--s));
    max-height: calc(240px * var(--s));
}

#od-queue li {
    display: flex;
    align-items: center;
    gap: calc(10px * var(--s));
    padding: calc(9px * var(--s)) calc(4px * var(--s));
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

#od-queue li.playing { background: rgba(255, 255, 255, 0.07); }

#od-queue .pos {
    width: calc(26px * var(--s));
    opacity: 0.45;
    font-variant-numeric: tabular-nums;
}

#od-queue li.playing .pos { opacity: 1; color: var(--muted-color); }

#od-queue .name {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

#od-queue .by {
    opacity: 0.45;
    font-size: calc(13px * var(--s));
    white-space: nowrap;
}

#od-queue .empty {
    padding: calc(14px * var(--s)) calc(4px * var(--s));
    opacity: 0.4;
    border: none;
}

#od-queue button {
    padding: calc(4px * var(--s)) calc(9px * var(--s));
    font-size: calc(13px * var(--s));
}

#od-foot {
    display: flex;
    align-items: center;
    gap: calc(18px * var(--s));
    padding-top: calc(12px * var(--s));
    border-top: calc(2px * var(--s)) solid var(--od-line);
    font-size: calc(14px * var(--s));
}

#od-foot label { display: flex; align-items: center; gap: calc(8px * var(--s)); cursor: pointer; }
#od-foot .od-volume span:first-child { opacity: 0.6; white-space: nowrap; }
#od-foot .od-check span { opacity: 0.6; white-space: nowrap; }
#od-foot .od-hint { margin-left: auto; opacity: 0.35; white-space: nowrap; }

#od-volume { width: calc(120px * var(--s)); accent-color: #fff; }
#od-volume-value { width: calc(42px * var(--s)); font-variant-numeric: tabular-nums; opacity: 0.8; }
#od-mute { accent-color: var(--muted-color); width: calc(15px * var(--s)); height: calc(15px * var(--s)); }

/* The HUD is never selectable, but the link field has to be. */
#od-url { user-select: text; }
