/* The car theme, layered over base.css. */

/* Stacked, the card spans the window (the page's side padding included)
   and rests on the bar; its width never follows the text, so a title
   change moves nothing but the words. */
.tunebox .onair {
    box-sizing:    border-box;
    width:         calc(100% + 2rem);
    margin:        0 -1rem;
    padding:       .95rem 1.25rem .8rem;
    border-radius: 1.1rem 1.1rem 0 0;
    color:         var(--car-card-ink);
    background:    var(--car-card);
    box-shadow:    0 -.4rem 1.2rem rgba(0, 0, 0, .12);
    text-align:    left;
    transition:    transform 450ms cubic-bezier(.2, .8, .2, 1), opacity 450ms ease;
}

/* The cover's main colour (palette.js), darker on the light card by day so
   a pale cover still shows; it ties the card to the cover beside it. */
.tunebox .onair::before {
    position:      absolute;
    top:           .4rem;
    left:          1.25rem;
    width:         2.25rem;
    height:        .25rem;
    content:       "";
    border-radius: 1rem;
    background:    var(--art-1);
    background:    hsl(from var(--art-1) h s 62%);
}

.tunebox.car-day .onair::before {
    background: hsl(from var(--art-1) h s 42%);
}

/* A new song: the card slides down behind the bar (the page clips it
   there), takes the new text and slides back up; display.js swaps the
   classes. Transform and opacity only, which a car's graphics chip
   handles without repainting. */
.tunebox .onair.card-out {
    transform:  translateY(110%);
    transition: transform 300ms cubic-bezier(.4, 0, 1, 1);
}

.tunebox .onair.card-in {
    transform:  translateY(110%);
    transition: none;
}

@media (prefers-reduced-motion: reduce) {
    .tunebox .onair,
    .tunebox .onair.card-out,
    .tunebox .onair.card-in {
        transform:  none;
        transition: none;
    }
}

/* Stacked, every line stays one line and the Played-by line keeps its room
   when a song has none, so the card, and the cover that takes the height
   above it, keep their size from song to song. ticker.js scrolls a line
   that does not fit; with reduced motion it ends in an ellipsis. */
.tunebox .onair .car-line {
    overflow:      hidden;
    white-space:   nowrap;
    text-overflow: ellipsis;
}

.tunebox .onair .car-line[hidden] {
    display:    block;
    visibility: hidden;
}

/* A scrolling line fades at its edges, inside the text column, so it keeps
   the card's padding on both sides. Each fade is there only while text is
   hidden behind it: at the start the left fade is gone and the first
   letter is whole, at the end the right one. Registered, so the widths
   change smoothly with the scroll (Chromium 85+; without it they jump). */
@property --fade-start {
    syntax:        '<length>';
    inherits:      false;
    initial-value: 0px;
}

@property --fade-end {
    syntax:        '<length>';
    inherits:      false;
    initial-value: 20px;
}

.tunebox .onair .car-line.scrolling {
    text-overflow:      clip;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--fade-start), #000 calc(100% - var(--fade-end)), transparent);
    mask-image:         linear-gradient(to right, transparent, #000 var(--fade-start), #000 calc(100% - var(--fade-end)), transparent);
    animation:          car-ticker-fade var(--ticker-duration, 10s) linear infinite alternate;
}

/* Runs alongside car-ticker with the same duration, so the two stay in step. */
.tunebox .onair .car-line.scrolling .car-line-text {
    display:   inline-block;
    animation: car-ticker var(--ticker-duration, 10s) linear infinite alternate;
}

/* The holds at both ends leave the start and the end of the line readable. */
@keyframes car-ticker {
    0%, 15% {
        transform: translateX(0);
    }
    85%, 100% {
        transform: translateX(var(--ticker-shift, 0));
    }
}

@keyframes car-ticker-fade {
    0%, 15% {
        --fade-start: 0px;
        --fade-end:   20px;
    }
    20%, 80% {
        --fade-start: 20px;
        --fade-end:   20px;
    }
    85%, 100% {
        --fade-start: 20px;
        --fade-end:   0px;
    }
}

/* The station, at the top of every page: it changes only when you pick
   another, so it stays out of the song's caption. The side padding keeps
   it clear of the day/night button. */
.tunebox {
    --car-station-h: 2.75rem;
}

/* Its own layer: the visualizer and the effects canvas are positioned
   layers in the same container and would paint over a header in the
   normal flow. */
.tunebox .car-station {
    position:        relative;
    z-index:         10;
    display:         flex;
    flex:            none;
    align-items:     center;
    justify-content: center;
    box-sizing:      border-box;
    height:          var(--car-station-h);
    padding:         0 3.25rem;
    gap:             .5rem;
    font-size:       .95rem;
    font-weight:     600;
    min-width:       0;
}

.tunebox .car-station .current-channel {
    overflow:      hidden;
    white-space:   nowrap;
    text-overflow: ellipsis;
}

.tunebox .car-station-logo {
    flex:          none;
    width:         1.5rem;
    height:        1.5rem;
    object-fit:    contain;
    border-radius: .25rem;
}

/* Dimmed, not see-through: the light wall and the colour fields run behind
   the header. */
.tunebox.car-night .car-station {
    filter: brightness(.7);
}

/* The fullscreen button ends the right-hand group; the gap before it
   comes from the group (base.css). */
.tunebox .player .fullscreen-toggle {
    cursor: pointer;
}

.tunebox .player .fullscreen-toggle .exit,
.tunebox .player .fullscreen-toggle.active .enter {
    display: none;
}

.tunebox .player .fullscreen-toggle.active .exit {
    display: inline;
}

/* The visualizer button starts the right-hand icons and pushes them to the
   edge, mirroring history and stations on the left. */
.tunebox .player .viz-toggle {
    margin-left: auto;
    cursor:      pointer;
}

/* History, channels and the visualizer menu are read and tapped; the
   visualizer fades out behind them. Only opacity animates: a
   transition on every property would visibly slide the Ring canvas into
   place around the cover. */
.tunebox #analyzer,
.tunebox .car-fx {
    transition: opacity 300ms ease;
}

.tunebox[data-page]:not([data-page="main"]) #analyzer,
.tunebox[data-page]:not([data-page="main"]) .car-fx {
    opacity: 0;
}

/* A stopped analyzer leaves its last frame on the canvas. */
.tunebox[data-viz="off"] #analyzer {
    visibility: hidden;
}

/* A full-height LED wall towers over the artwork and runs behind the buttons;
   keep it between the two. */
.tunebox[data-viz="led"] #analyzer {
    top:    45%;
    bottom: var(--bar-h);
}

/* The track text sits on a card: docked on the control bar when stacked,
   floating beside the cover when wide. Its solid ground keeps the text
   readable whatever the cover or the visualizer does, and the bar shares
   its colour, so the two read as one unit. */
.tunebox {
    --car-card:      rgba(14, 22, 32, .94);
    --car-card-ink:  #e3e9f0;
    --car-card-soft: #a3b2c3;
}

.tunebox.car-day {
    --car-card:      rgba(255, 255, 255, .92);
    --car-card-ink:  #1f2a37;
    --car-card-soft: #4b5869;
}

.tunebox .player {
    background: var(--car-card);
}

/* Cover palette from palette.js. Registered so a track change cross-fades
   the colours instead of jumping; browsers without @property just switch.
   For a colourless cover palette.js publishes the accent and its
   neighbours; these initial values (the same accent and neighbours) only
   show before that. */
@property --art-1 {
    syntax:        '<color>';
    inherits:      true;
    initial-value: #62a8ea;
}

@property --art-2 {
    syntax:        '<color>';
    inherits:      true;
    initial-value: #9b7bf0;
}

@property --art-3 {
    syntax:        '<color>';
    inherits:      true;
    initial-value: #3cc6c0;
}

@property --art-glow {
    syntax:        '<color>';
    inherits:      true;
    initial-value: rgba(98, 168, 234, .6);
}

.tunebox {
    transition: --art-1 2s ease, --art-2 2s ease, --art-3 2s ease, --art-glow 2s ease;
}

/* Animated background: three soft colour fields drifting slowly. Only transform
   is animated, which the compositor handles without repainting. */
.tunebox .car-ambient {
    position:       absolute;
    z-index:        0;
    top:            0;
    right:          0;
    bottom:         0;
    left:           0;
    overflow:       hidden;
    transition:     opacity 1s ease, background 1s ease;
    pointer-events: none;
    opacity:        0;
}

.tunebox.car-ambient-on .car-ambient {
    opacity: 1;
}

.tunebox .car-ambient::before {
    position:   absolute;
    top:        -30%;
    right:      -30%;
    bottom:     -30%;
    left:       -30%;
    content:    "";
    animation:  car-drift 45s ease-in-out infinite alternate;
    opacity:    .32;
    background: radial-gradient(circle at 28% 30%, var(--art-1) 0, transparent 38%),
                radial-gradient(circle at 74% 38%, var(--art-2) 0, transparent 34%),
                radial-gradient(circle at 46% 74%, var(--art-3) 0, transparent 42%);
}

@keyframes car-drift {
    0% {
        transform: translate(0, 0) rotate(0deg) scale(1);
    }
    50% {
        transform: translate(-7%, 5%) rotate(25deg) scale(1.12);
    }
    100% {
        transform: translate(6%, -6%) rotate(-20deg) scale(1.05);
    }
}

/* Cover glow, breathing with the bass level palette.js writes to --bass. */
.tunebox.car-glow-on .artwork-img {
    box-shadow: 0 0 calc(1rem + var(--bass, 0) * 2.5rem) calc(var(--bass, 0) * .6rem) var(--art-glow);
}

.tunebox[data-viz="neon"] #analyzer canvas {
    filter: drop-shadow(0 0 4px var(--art-1)) drop-shadow(0 0 10px var(--art-1));
}

/* The glow filter repaints the whole canvas every frame. */
.tunebox[data-power="low"][data-viz="neon"] #analyzer canvas {
    filter: none;
}

/* A full-screen light wall is calm only when dim. */
.tunebox[data-viz="ambient"] #analyzer {
    opacity: .55;
}

.tunebox .car-fx {
    position:       absolute;
    z-index:        0;
    top:            0;
    left:           0;
    width:          100%;
    height:         100%;
    pointer-events: none;
}

/* Effects draw on .car-fx; the spectrum canvas would show its last frame. */
.tunebox[data-effect] #analyzer {
    visibility: hidden;
}

/* Night: dim everything that glows, the cover and the controls; the title
   stays the brightest thing on screen so it still reads at a glance. */
/* The ambient layer covers the skin's background, the largest bright area,
   so its own dark fill dims that too. */
.tunebox.car-night .car-ambient {
    filter:     brightness(.4);
    background: rgba(0, 0, 0, .55);
}

.tunebox.car-night #analyzer,
.tunebox.car-night .car-fx {
    opacity: .35;
}

.tunebox.car-night .artwork-img {
    filter: brightness(.65);
}

/* The bar keeps the card's colour at night; only its controls dim. */
.tunebox.car-night .player > *,
.tunebox.car-night .car-daynight {
    opacity: .6;
}

/* Day: a light theme, like the car's own screen by day. base.css paints the
   page navy with white text; the ambient layer covers it with a light base
   here, and the colours base.css sets explicitly are replaced one by one. The cover's colours stay, as pastel fields. */
.tunebox.car-day {
    color: #1f2a37;
}

.tunebox.car-day .car-ambient {
    background: linear-gradient(160deg, #f6f8fb 0%, #e4eaf1 100%);
}

.tunebox.car-day .car-ambient::before {
    opacity: .4;
}

/* The accent darkened for contrast on the light base; browsers without
   color-mix() keep the first value, the accent darkened likewise. */
.tunebox.car-day .icon-fill,
.tunebox.car-day .car-daynight svg {
    fill: #1f6fbf;
    fill: color-mix(in srgb, var(--accent-color) 65%, #000);
}

.tunebox.car-day .car-daynight {
    background: rgba(255, 255, 255, .7);
}

.tunebox.car-day .page.viz-page .viz-list button {
    color: #1f2a37;
}

.tunebox.car-day .page.viz-page .viz-list li.active button {
    color: #fff;
}

.tunebox.car-day .history-list li:hover {
    background: rgba(0, 0, 0, .05);
}

/* Day/night button, top left. Icon only, so it clears the cover's corner in
   the narrow window. */
.tunebox .car-daynight {
    position:      absolute;
    z-index:       20;
    top:           .5rem;
    left:          .5rem;
    display:       flex;
    align-items:   center;
    justify-content: center;
    width:         2.4rem;
    height:        2.4rem;
    transition:    opacity 300ms ease;
    color:         #fff;
    border-radius: 50%;
    background:    rgba(0, 0, 0, .25);
}

.tunebox .car-daynight svg {
    display: none;
    width:   1.5rem;
    height:  1.5rem;
    fill:    currentColor;
}

.tunebox .car-daynight[data-mode="auto"] .auto,
.tunebox .car-daynight[data-mode="day"] .day,
.tunebox .car-daynight[data-mode="night"] .night {
    display: block;
}

.tunebox[data-page]:not([data-page="main"]) .car-daynight {
    pointer-events: none;
    opacity:        0;
}

/* base.css sizes the pages from 100vh, which car and mobile browsers can
   report taller than the window (see layout.js). The pages take the height
   the station header and the control bar leave, from the flex layout
   itself: a height computed from --car-vh follows a resize only once it is
   measured again, and the gap it leaves above the control bar stayed open
   in the car after entering fullscreen. */
.tunebox .container {
    flex:       1 1 0;
    min-height: 0;
    height:     auto;
}

.tunebox .container .page {
    height: 100%;
}

/* Stacked (the car's third, phones): the cover takes the height the card
   leaves, square by height and never wider than the window. */
.tunebox .artwork-image {
    display:         flex;
    flex:            1 1 0;
    align-items:     center;
    justify-content: center;
    min-height:      8rem;
    margin-bottom:   1rem;
}

.tunebox .artwork-image .artwork-img {
    width:        auto;
    height:       100%;
    max-height:   min(30rem, 85vw);
    aspect-ratio: 1 / 1;
}

/* A window too short for that (a phone on its side, a small window) lets
   the cover shrink further, so the one-line caption still fits. */
@media (max-height: 30rem) {
    .tunebox .artwork-image {
        min-height: 5rem;
    }
}

/* Visualizer menu: two columns of car-sized tap targets. */
.tunebox .page.viz-page .viz-list {
    display:               grid;
    width:                 min(92vw, 26rem);
    margin:                0;
    padding:               0;
    list-style:            none;
    grid-template-columns: repeat(2, 1fr);
    gap:                   .6rem;
}

/* The grid spaces the rows; base.css's gap between list items would push
   every button but the first down. */
.tunebox .page.viz-page .viz-list li {
    margin: 0;
}

.tunebox .page.viz-page .viz-list button {
    font-size:     1rem;
    display:       block;
    width:         100%;
    padding:       .85rem 1rem;
    transition:    opacity 250ms ease, background 250ms ease;
    text-align:    center;
    opacity:       .85;
    color:         #fff;
    border:        .1rem solid var(--accent-color, #62a8ea);
    border-radius: .15rem;
    background:    transparent;
}

.tunebox .page.viz-page .viz-list li.active button {
    opacity:    1;
    background: var(--accent-color, #62a8ea);
}

/* Channel tiles: one square per group and per station outside a group,
   two columns in the car's third and more as the window widens. The rows
   of an open group keep base.css's look. */
.tunebox .channel-groups.tiled {
    width: min(100%, 42rem);
}

.tunebox .channel-tiles {
    display:               grid;
    grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
    gap:                   .75rem;
}

.tunebox .tile {
    position:       relative;
    display:        flex;
    flex-direction: column;
    gap:            .4rem;
    min-width:      0;
    padding:        .5rem;
    text-align:     center;
    border:         .1rem solid transparent;
    border-radius:  .2rem;
    background:     rgba(72, 99, 114, .3);
}

/* The square stays when its image fails, so the grid keeps its rows. */
.tunebox .tile-art {
    display:       block;
    overflow:      hidden;
    width:         100%;
    aspect-ratio:  1 / 1;
    border-radius: .2rem;
}

.tunebox .tile-art:empty {
    background: rgba(255, 255, 255, .08);
}

.tunebox.car-day .tile-art:empty {
    background: rgba(31, 42, 55, .12);
}

/* A group's own image fills the square; a station logo is shown whole. */
.tunebox .tile[data-group] .tile-art > img {
    object-fit: cover;
}

.tunebox .tile-art > img {
    display:    block;
    width:      100%;
    height:     100%;
    object-fit: contain;
}

.tunebox .tile .mosaic {
    display:               grid;
    width:                 100%;
    height:                100%;
    grid-template-columns: 1fr 1fr;
    grid-template-rows:    1fr 1fr;
    gap:                   .25rem;
}

.tunebox .tile .mosaic img {
    width:      100%;
    height:     100%;
    min-height: 0;
    object-fit: contain;
}

/* Two lines at most; line height 1.3 for the reason given at the track text
   below. */
.tunebox .tile .name {
    display:            -webkit-box;
    overflow:           hidden;
    line-height:        1.3;
    overflow-wrap:      anywhere;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

/* The colours of the active channel row. */
.tunebox .tile.active {
    border-color: #1f6fbf;
    background:   rgba(42, 130, 214, .5);
}

.tunebox .tile-playing {
    position:      absolute;
    top:           .3rem;
    right:         .3rem;
    display:       none;
    box-sizing:    border-box;
    width:         1.6rem;
    height:        1.6rem;
    padding:       .25rem;
    fill:          #fff;
    border-radius: 50%;
    background:    #1f6fbf;
}

.tunebox .tile.active .tile-playing {
    display: block;
}

.tunebox .tile + .streams {
    grid-column: 1 / -1;
}

.tunebox .btn-tiles {
    margin: 0 0 .5rem;
}

/* Line heights stay at 1.3 or more: below that, tall Poppins glyphs of a
   cut line's hidden next line show as marks above the cut. */
.tunebox .onair .car-title-line {
    font-size:   1.7rem;
    font-weight: 500;
    line-height: 1.3;
}

.tunebox .onair .car-artist-line {
    font-size:   1.15rem;
    line-height: 1.3;
    color:       var(--car-card-soft);
}

/* Which festival set the track comes from, under the artist: large enough
   to read at arm's length, with its label set back. */
.tunebox .onair .car-played-line {
    margin-top:  .15rem;
    font-size:   1.03rem;
    line-height: 1.3;
    color:       var(--car-card-soft);
}

.tunebox .onair .car-played-label {
    opacity: .8;
}

/* A chart position or show label from the channel's badge rule, before the
   title. The card's own colours swapped keep it readable by day and night. */
.tunebox .onair .car-badge {
    display:        inline-block;
    margin-right:   .4em;
    padding:        0 .45em;
    font-size:      .62em;
    font-weight:    600;
    line-height:    1.6;
    vertical-align: .18em;
    border-radius:  .8em;
    color:          var(--car-card);
    background:     var(--car-card-ink);
}

.tunebox .onair .car-badge[hidden] {
    display: none;
}

/* Wide windows (data-layout from layout.js): cover left, sized by height, text
   beside it. The page's ::before/::after auto margins (base.css) centre the row. */
.tunebox[data-layout="wide"] {
    --car-art-size: clamp(8rem, min(42vw, calc(var(--car-vh, 100vh) - 10rem - var(--car-station-h))), 32rem);
}

.tunebox[data-layout="wide"] .page.main {
    flex-direction: row;
    box-sizing:     border-box;
    padding:        0 2rem;
    gap:            2.5rem;
}

.tunebox[data-layout="wide"] .artwork-image {
    flex:          none;
    margin-bottom: 0;
}

.tunebox[data-layout="wide"] .artwork-image .artwork-img {
    width:  var(--car-art-size);
    height: var(--car-art-size);
}

/* The text takes the room a full-width window leaves beside the cover, and
   its two main lines grow with it; two thirds of the car screen keeps the
   minimum sizes. */
.tunebox[data-layout="wide"] .onair {
    flex:          1 1 34rem;
    width:         auto;
    max-width:     46rem;
    min-width:     0;
    margin:        0;
    padding:       1.3rem 1.6rem 1.15rem;
    border-radius: 1.1rem;
    box-shadow:    0 .6rem 1.8rem rgba(0, 0, 0, .16);
}

.tunebox[data-layout="wide"] .onair::before {
    left: 1.6rem;
}

/* Floating, a new song fades the card down a little instead of behind the bar. */
.tunebox[data-layout="wide"] .onair.card-out {
    transform:  translateY(1.5rem);
    opacity:    0;
    transition: transform 300ms cubic-bezier(.4, 0, 1, 1), opacity 300ms ease;
}

.tunebox[data-layout="wide"] .onair.card-in {
    transform: translateY(-1rem);
    opacity:   0;
}

/* Beside the cover the card's height moves nothing, so the lines wrap, two
   at most, and a glance reads them whole without waiting for a scroll.
   Plain wrapping, not balanced: Firefox balances text over all the lines
   it needs before the clamp cuts it, which leaves a word or two per
   visible line ("MEDUZA,..."). */
.tunebox[data-layout="wide"] .onair .car-line {
    display:            -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    white-space:        normal;
    overflow-wrap:      anywhere;
}

.tunebox[data-layout="wide"] .onair .car-line[hidden] {
    display: none;
}

/* Too short for two lines each (a phone on its side, a small window): one
   line each, ending in an ellipsis. */
@media (max-height: 30rem) {
    .tunebox[data-layout="wide"] .onair .car-line {
        -webkit-line-clamp: 1;
    }
}

.tunebox[data-layout="wide"] .onair .car-title-line {
    font-size: 2.1rem;
    font-size: clamp(2.1rem, 3vw, 3rem);
}

/* Beside the cover there is height to spare and the column is narrow for
   the title's size: long remix titles get a third line. */
@media (min-height: 30.01rem) {
    .tunebox[data-layout="wide"] .onair .car-title-line {
        -webkit-line-clamp: 3;
    }
}

.tunebox[data-layout="wide"] .onair .car-artist-line {
    font-size: 1.4rem;
    font-size: clamp(1.4rem, 2vw, 2rem);
}

.tunebox[data-layout="wide"] .onair .car-played-line {
    font-size: 1.1rem;
    font-size: clamp(1.1rem, 1.4vw, 1.4rem);
}

/* Day: the keypad on the light theme's ground. */
.tunebox.car-day .pin-screen {
    color:      #1f2a37;
    background: linear-gradient(160deg, #f6f8fb 0%, #e4eaf1 100%);
}

.tunebox.car-day .pin-keys button {
    background: rgba(31, 42, 55, .08);
}

.tunebox.car-day .pin-keys .pin-ok {
    color:      #fff;
    background: var(--accent-color);
}

.tunebox.car-day .pin-stop {
    color: #1f2a37;
}

.tunebox.car-day .pin-error {
    color: #b42318;
}
