/**
 * Site chrome: header controls, footer and music player.
 * ======================================================
 *
 * The footer and the player are owned outright by this file. The header
 * controls strip is not - header-controls-panel.css still owns it, and the
 * short section at the end of this file only corrects its narrow-screen
 * layout.
 *
 * It replaces these five, whose links are removed from templates/header.php:
 *
 *   music-player-v2.css          music-player-unified-fix.css
 *   footer-layout.css            footer-gap-fix.css
 *   footer-copyright-fix.css
 *
 * That is the point of it. Those files set the same properties on the same
 * selectors at different values, so every rule in the later ones needed
 * !important to land, and a sixth file would have needed !important to beat
 * those. Almost nothing here carries !important, because nothing here is
 * arguing with anything.
 *
 *
 * WHAT CHANGED, AND WHY
 *
 * The footer was not a footer. .site-footer was position:fixed at bottom:40px
 * and the server strip fixed at bottom:0, so the bottom 80px of every
 * viewport was permanently spent - on a phone that is a tenth of the screen,
 * held by a copyright line - and body carried padding-bottom:110px to keep
 * content out from under it. The player then had to float above both at
 * bottom:120px, which is why a 500x440 card sat in the middle of the screen,
 * covering the hero on mobile and the devlog on desktop.
 *
 * So: the footer returns to normal document flow and arrives when the page
 * ends, and the player - the one thing that genuinely has to survive
 * navigation - is the only fixed element left. It is docked to the bottom
 * edge as a bar rather than parked in the middle as a card.
 *
 * Type and colour are the home page's: the --re-brand ramp, --home-rule for
 * hairlines, Silkscreen for small caps labels only, Albert Sans for anything
 * read as a sentence.
 */

/* ------------------------------------------------------------------ *
 * Page model
 * ------------------------------------------------------------------ */

/*
 * Only the player bar is fixed now, so this is the only reserved space at the
 * bottom of the page. The class is added by music-player.js once the bar is
 * actually in the DOM - if that script fails to load, the page simply has no
 * dead strip at the bottom.
 */
body.has-player-bar {
    /* 13 + 45 + 15 + 1px border = 74. Rounded up so the last line of a page
       is never touching the bar. */
    padding-bottom: 80px;
}

/* ------------------------------------------------------------------ *
 * Footer
 * ------------------------------------------------------------------ */

/*
 * The !important here is not a fight with the files this one replaced - it is
 * glass-standard.css, which paints every <footer> element as a glass card:
 *
 *   footer, .footer { background: var(--re-surface) !important;
 *                     backdrop-filter: var(--glass-blur) !important;
 *                     border-top: 1px solid var(--re-border) !important;
 *                     box-shadow: 0 -8px 32px rgba(0,0,0,.5),
 *                                 inset 0 1px 0 rgba(255,255,255,.18)
 *                                 !important; }
 *
 * That is the right treatment for a bar hovering over content, which is what
 * the footer used to be. In normal flow, running the full width at the end of
 * the page, a translucent panel with a lit top edge and a shadow reads as a
 * card floating on top of itself. The page's own ground shows through
 * instead, and the only division is the hairline - the same one under the
 * theme strip and between home page sections.
 *
 * home-refined.css carries the identical override for the controls panel and
 * explains the same constraint: this file loads after glass-standard, so
 * matching !important is what it takes. Nothing else in here needs it.
 */
.site-footer {
    position: static;
    margin-top: var(--home-section, 96px);
    background: transparent !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    /*
     * The rule across the top of the footer belongs to .footer-inner, not to
     * this element.
     *
     * As a border here it ran the full width of the window, while every other
     * rule on the site stops at --home-max - including the one directly below
     * it, above the base row, which is 1180px against this one's 1430px on a
     * desktop. Two hairlines a few hundred pixels apart, at different widths,
     * and the mismatch is the only thing you see.
     */
    border-top: 0 !important;
    box-shadow: none !important;
    border-radius: 0 !important;
}

/* glass-standard's inset highlight along the top edge. */
.site-footer::before,
.site-footer::after {
    display: none !important;
}

.footer-inner {
    max-width: var(--home-max, 1180px);
    margin: 0 auto;
    padding: 48px 24px 28px;
    /* Same box, same declaration, as the rule above .footer-base below - so
       the footer's two hairlines line up instead of one overshooting. */
    border-top: 1px solid var(--home-rule, rgba(130, 107, 125, 0.22));
    display: grid;
    /* The identity column is wider because it carries a portrait and two
       lines; the other two are lists of short links. */
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr);
    gap: 40px;
    align-items: start;
}

/* --- identity ----------------------------------------------------- */

.footer-identity {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 16px;
    align-items: center;
}

/*
 * 48px is 1x - the sprite's own size, so no resampling happens at all and
 * image-rendering is academic here. It is set anyway so that changing this
 * number to 96 later still lands on a whole multiple and still stays crisp.
 */
.footer-avatar {
    width: 48px;
    height: 48px;
    display: block;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: pixelated;
}

.footer-name {
    display: block;
    font-family: var(--re-font-display);
    font-size: 1.15rem;
    line-height: 1.2;
    color: var(--re-text-color);
}

.footer-role,
.footer-eyebrow {
    display: block;
    font-family: var(--re-font-ui);
    font-size: 0.62rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--re-text-color);
    opacity: 0.5;
}

.footer-role {
    margin-top: 6px;
}

.footer-eyebrow {
    margin-bottom: 14px;
}

/* --- link columns ------------------------------------------------- */

.footer-column {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.footer-links {
    display: flex;
    flex-direction: column;
    gap: 9px;
}

.footer-links a,
.footer-legal a {
    line-height: 1.3;
    color: var(--re-text-color);
    opacity: 0.62;
    text-decoration: none;
    width: fit-content;
    transition: opacity 0.18s ease, transform 0.18s ease;
}

/*
 * The site column takes the UI face, matching the SITE eyebrow directly above
 * it. The headings were already Silkscreen and their contents were not, which
 * read as two footers stacked on each other.
 *
 * 0.68rem rather than the 0.88rem the body face used. Silkscreen carries far
 * more width and height per character, and at the old number these links came
 * out larger than the heading they sit under.
 *
 * The typography moved out of the shared rule above rather than being
 * overridden here, because .footer-legal a - the bottom bar - is grouped with
 * it and sets its own size. Two rules quietly disagreeing about one selector
 * is what made the legal links need their own correction in the first place.
 */
.footer-links a {
    font-family: var(--re-font-ui);
    font-size: 0.68rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.footer-links a:hover,
.footer-legal a:hover {
    opacity: 1;
    color: var(--re-text-color);
    transform: translateX(3px);
}

/* --- status ------------------------------------------------------- */

.footer-status {
    display: flex;
    align-items: center;
    gap: 9px;
    /* Same face and size as the site links opposite it - the two columns sit
       side by side and should not read as different kinds of text. */
    font-family: var(--re-font-ui);
    font-size: 0.68rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--re-text-color);
    opacity: 0.85;
    margin-bottom: 12px;
}

/*
 * The dot pulses rather than sits still, because "open to work" is a claim
 * about right now. Reduced-motion callers get a static dot, at the end of
 * this file.
 */
.footer-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #4ec98a;
    box-shadow: 0 0 0 0 rgba(78, 201, 138, 0.55);
    animation: footerPulse 2.4s ease-out infinite;
    flex: 0 0 auto;
}

@keyframes footerPulse {
    0%   { box-shadow: 0 0 0 0 rgba(78, 201, 138, 0.5); }
    70%  { box-shadow: 0 0 0 8px rgba(78, 201, 138, 0); }
    100% { box-shadow: 0 0 0 0 rgba(78, 201, 138, 0); }
}

.footer-cta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-family: var(--re-font-ui);
    font-size: 0.66rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--re-text-color);
    padding: 9px 16px;
    border: 1px solid var(--re-brand-400);
    border-radius: var(--re-radius-sm, 6px);
    transition: background 0.18s ease, border-color 0.18s ease;
}

.footer-cta:hover {
    background: rgba(var(--re-brand-400-rgb), 0.16);
    border-color: var(--re-brand-200);
    color: var(--re-text-color);
}

/* --- base row ----------------------------------------------------- */

.footer-base {
    max-width: var(--home-max, 1180px);
    margin: 0 auto;
    padding: 18px 24px 26px;
    border-top: 1px solid var(--home-rule, rgba(130, 107, 125, 0.22));
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
}

.footer-copy {
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: var(--re-font-ui);
    font-size: 0.6rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--re-text-color);
    opacity: 0.45;
}

.footer-legal {
    display: flex;
    align-items: center;
    gap: 22px;
    margin-left: auto;
}

/*
 * The bottom bar is Silkscreen: the copyright to the left of these links and
 * the "Back to top" button to their right are both var(--re-font-ui) at
 * 0.6rem, uppercase. These two were the only things on the line still in
 * Albert Sans at 0.8rem, sentence case - the face rule above is written for
 * the FOOTER COLUMNS (it is grouped with .footer-links), and the bottom-bar
 * block only ever corrected the size, so the body face came through.
 */
.footer-legal a {
    font-family: var(--re-font-ui);
    font-size: 0.6rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    opacity: 0.45;
}

/*
 * And they fade rather than slide. translateX on hover reads correctly in the
 * vertical column of site links; on a horizontal bar it nudges the link into
 * the gap beside "Back to top", which does not move.
 */
.footer-legal a:hover {
    opacity: 1;
    transform: none;
}

.footer-top {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: none;
    border: none;
    padding: 6px 2px;
    cursor: pointer;
    font-family: var(--re-font-ui);
    font-size: 0.6rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--re-text-color);
    opacity: 0.5;
    transition: opacity 0.18s ease;
}

.footer-top:hover {
    opacity: 1;
}

/* --- server strip (admins only; gated in templates/footer.php) ----- */

.server-info-footer {
    position: static;
    max-width: var(--home-max, 1180px);
    margin: 0 auto;
    padding: 10px 24px 22px;
    opacity: 0.55;
}

.server-stats-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 22px;
}

/*
 * These rules used to live in a <style> block inside templates/footer.php,
 * every declaration !important, because the strip was fighting the same five
 * stylesheets as everything else down here. Nothing competes for these
 * selectors now.
 */
.server-stat-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
    font-family: var(--re-font-ui);
    font-size: 8px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    /* Silkscreen is a bitmap face; smoothing it at 8px turns the stems grey. */
    -webkit-font-smoothing: none;
    text-rendering: optimizeSpeed;
}

.server-stat-item i {
    font-size: 11px;
    color: var(--re-brand-400);
}

.server-stat-key {
    color: var(--re-text-color);
    opacity: 0.55;
}

.server-stat-value {
    color: var(--re-text-color);
}

/* ------------------------------------------------------------------ *
 * Music player - docked bar
 * ------------------------------------------------------------------ */

.persistent-player {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* The old rule centred with translateX(-50%) against left:50%. Edge to
       edge needs neither, and a stale transform would drag the bar off
       screen. */
    transform: none;
    z-index: 9990;
    /*
     * The player's own ground, from the palette rather than a fixed plum.
     *
     * This was rgba(26, 20, 30, 0.82) - a dark mauve baked in when mauve was
     * the only palette there was. Once the site could turn green or teal or
     * grey, the bar across the bottom stayed purple and stopped belonging to
     * the page above it.
     *
     * brand-900 is the palette's darkest step, which is what the ground
     * gradient is built from too, so the two now agree by construction. The
     * rgba line stays as the fallback for browsers without color-mix; the
     * second declaration wins everywhere else and, because brand-900 is a
     * registered property, it crossfades with everything else.
     */
    background: rgba(26, 20, 30, 0.82);
    background: color-mix(in srgb, var(--re-brand-900) 82%, transparent);
    -webkit-backdrop-filter: blur(18px) saturate(160%);
    backdrop-filter: blur(18px) saturate(160%);
    border-top: 1px solid var(--home-rule, rgba(130, 107, 125, 0.22));
    /* Upward, because the bar is lit from the page above it. */
    box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.38);
}

/*
 * The bar's breathing room is PADDING, not slack.
 *
 * This used to be `padding: 0 24px` with `min-height: 64px`, and it looked
 * fine closed for one reason only: the content came to about 50px, so
 * align-items:center shared the leftover 14px above and below it. That is not
 * spacing, it is a leftover, and it disappears the moment there is no leftover
 * to share. Open the queue and the content becomes 458px - taller than the
 * min-height - so the centring had nothing to distribute and everything went
 * flush to the edges: the album art of the playing track sat ONE pixel below
 * the top border, with the transport row jammed against it.
 *
 * Stated padding holds in both states, so the bar is spaced the same whether
 * the queue is open or shut.
 */
.player-container {
    max-width: var(--home-max, 1180px);
    margin: 0 auto;
    padding: 13px 24px 15px;
    display: flex;
    /*
     * Top, not centre. With the queue open this box is ~450px tall, and
     * centring stretched the space above the transport row down the whole
     * panel. The minimize button no longer depends on this - it is a grid
     * item on the transport row itself; see .player-toggle.
     */
    align-items: flex-start;
    gap: 16px;
}

/*
 * The markup order is fixed by getPlayerHTML() in music-player.js: track,
 * progress, controls, volume, queue, list. Grid areas re-order it for the eye
 * without the JS having to know anything about the layout.
 */
.player-content {
    flex: 1 1 auto;
    min-width: 0;
    display: grid;
    grid-template-columns: minmax(150px, 260px) auto minmax(140px, 1fr) auto auto auto;
    grid-template-areas:
        "track controls progress volume queue toggle"
        "list  list     list     list   list  list";
    align-items: center;
    gap: 0 20px;
}

.track-info {
    grid-area: track;
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
}

.player-controls {
    grid-area: controls;
    display: flex;
    align-items: center;
    gap: 4px;
}

.progress-container {
    grid-area: progress;
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.volume-control {
    grid-area: volume;
    display: flex;
    align-items: center;
    gap: 8px;
}

.playlist-header    { grid-area: queue; }
.playlist-container { grid-area: list; }

/* --- track --------------------------------------------------------- */

.track-cover {
    width: 40px;
    height: 40px;
    flex: 0 0 auto;
    border-radius: var(--re-radius-xs, 4px);
    overflow: hidden;
    background: rgba(var(--re-brand-400-rgb), 0.18);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--re-brand-100);
    font-size: 15px;
}

.track-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.track-details {
    min-width: 0;
}

/*
 * Silkscreen at a legible size, and one line only. Track titles run long -
 * "Parasite Eve - Somnia Memorias" - and a bar that grows a second line when
 * the track changes makes the whole page shift under the reader.
 */
.track-title {
    font-family: var(--re-font-ui);
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--re-text-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/*
 * The artist takes the UI face like the title above it. It was the one line
 * of body text left in the player's own chrome, sitting directly under an
 * uppercase Silkscreen title, which read as an accident rather than a
 * contrast. Uppercase and tracked for the same reason: that is how this face
 * is set everywhere else on the site, and 8px Silkscreen lowercase is the
 * least legible thing it can be asked to do.
 *
 * No size here - silkscreen-pixel-grid.css settles every Silkscreen run at
 * 8px, so a number in this rule would only be a lie about what renders.
 */
.track-artist {
    font-family: var(--re-font-ui);
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--re-text-color);
    opacity: 0.5;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-top: 3px;
}

/* --- transport ----------------------------------------------------- */

.control-btn {
    width: 34px;
    height: 34px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    border-radius: 50%;
    padding: 0;
    cursor: pointer;
    color: var(--re-text-color);
    opacity: 0.72;
    font-size: 15px;
    transition: opacity 0.16s ease, background 0.16s ease;
}

.control-btn:hover  { opacity: 1; background: rgba(var(--re-brand-400-rgb), 0.18); }
.control-btn:active { transform: scale(0.94); }
.control-btn.active { opacity: 1; color: var(--re-brand-200); }

.control-btn-play {
    width: 40px;
    height: 40px;
    font-size: 19px;
    opacity: 1;
    background: rgba(var(--re-brand-400-rgb), 0.2);
}

.control-btn-play:hover {
    background: rgba(var(--re-brand-400-rgb), 0.34);
}

/* --- progress ------------------------------------------------------ */

.time-current,
.time-total {
    font-family: var(--re-font-ui);
    font-size: 0.56rem;
    letter-spacing: 0.06em;
    color: var(--re-text-color);
    opacity: 0.45;
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
}

.progress-bar {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    height: 4px;
    border-radius: 2px;
    background: rgba(var(--re-brand-400-rgb), 0.28);
    cursor: pointer;
}

/* A larger invisible target than the 4px line, so the bar is grabbable. */
.progress-bar::before {
    content: '';
    position: absolute;
    inset: -9px 0;
}

.progress-fill {
    height: 100%;
    width: 0;
    border-radius: 2px;
    background: var(--re-brand-200);
}

.progress-handle {
    position: absolute;
    top: 50%;
    left: 0;
    width: 11px;
    height: 11px;
    margin-left: -5.5px;
    border-radius: 50%;
    background: var(--re-brand-100);
    transform: translateY(-50%) scale(0);
    transition: transform 0.16s ease;
}

.progress-bar:hover .progress-handle,
.progress-handle:active {
    transform: translateY(-50%) scale(1);
}

/* --- volume -------------------------------------------------------- */

/*
 * Built to read as the same control as .progress-bar above: a 4px rail, the
 * played/filled part in brand-200, and an 11px round handle in brand-100.
 *
 * It did not look like that before, and the reason is worth writing down. The
 * rail was set as the INPUT's own background. With -webkit-appearance: none a
 * range input's box is as tall as its thumb - 11px here, not the 4px the rule
 * asked for - so the browser painted an 84x11 slab with a 2px corner radius.
 * Hence the rectangle. The rail has to live on the track pseudo-element, with
 * the input itself transparent.
 */

.volume-slider {
    -webkit-appearance: none;
    appearance: none;
    width: 84px;
    /* Room for the thumb. The visible rail is drawn by the track below. */
    height: 11px;
    background: none;
    outline: none;
    cursor: pointer;

    /*
     * How much of the rail is filled. Kept in step with the audio by
     * paintVolume() in music-player.js; this value is only the fallback for
     * the instant between markup and the first paint, and matches the
     * value="70" the input is created with.
     */
    --volume-pct: 70%;
}

/*
 * Chrome, Edge, Safari. There is no ::-webkit-range-progress, so the filled
 * part is a hard-stopped gradient rather than a real element.
 */
.volume-slider::-webkit-slider-runnable-track {
    height: 4px;
    border-radius: 2px;
    background: linear-gradient(
        to right,
        var(--re-brand-200) 0%,
        var(--re-brand-200) var(--volume-pct),
        rgba(var(--re-brand-400-rgb), 0.28) var(--volume-pct),
        rgba(var(--re-brand-400-rgb), 0.28) 100%
    );
}

.volume-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    border: none;
    background: var(--re-brand-100);
    cursor: pointer;
    /* (4px rail - 11px thumb) / 2, to sit the handle on the line rather than
       hanging off the top of it. */
    margin-top: -3.5px;
}

/* Firefox draws the filled portion itself, so it needs no gradient. */
.volume-slider::-moz-range-track {
    height: 4px;
    border-radius: 2px;
    background: rgba(var(--re-brand-400-rgb), 0.28);
}

.volume-slider::-moz-range-progress {
    height: 4px;
    border-radius: 2px;
    background: var(--re-brand-200);
}

.volume-slider::-moz-range-thumb {
    width: 11px;
    height: 11px;
    border-radius: 50%;
    border: none;
    background: var(--re-brand-100);
    cursor: pointer;
}

.volume-slider:focus-visible {
    outline: 2px solid var(--re-brand-200);
    outline-offset: 4px;
}

/* --- queue --------------------------------------------------------- */

.playlist-toggle-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: none;
    border: none;
    padding: 8px 4px;
    cursor: pointer;
    font-family: var(--re-font-ui);
    font-size: 0.58rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--re-text-color);
    opacity: 0.6;
    white-space: nowrap;
    transition: opacity 0.16s ease;
}

.playlist-toggle-btn:hover {
    opacity: 1;
}

/*
 * Closed by default and opened by .open, which music-player.js toggles.
 * max-height rather than display, so it can animate and so the chevron has
 * something to point at.
 */
.playlist-container {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.28s ease;
}

/*
 * The gap belongs to the OPEN state, not to the grid.
 *
 * A row-gap on .player-content would also apply while the queue is collapsed -
 * grid still reserves the gap around a zero-height row - and the closed bar
 * would carry a strip of dead space under the transport line. Four pixels was
 * not enough to separate a track list from the track playing above it; the
 * two read as one run of rows.
 */
.playlist-container.open {
    max-height: 46vh;
    overflow-y: auto;
    border-top: 1px solid var(--home-rule, rgba(130, 107, 125, 0.22));
    margin-top: 14px;
    padding-top: 4px;
}

.playlist {
    /* The container's own bottom padding carries the rest of the room under
       the last row, so this only has to clear the rule above the first. */
    padding: 10px 0 4px;
}

.playlist-item {
    display: grid;
    /* cover, title/artist, the playing marker, the remove button.
       Four, not three: the fourth child of a three-column grid is placed on a
       row of its own, which is how a stray X came to sit underneath the track
       that was playing. */
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 12px;
    padding: 9px 12px;
    border-radius: var(--re-radius-xs, 4px);
    cursor: pointer;
    transition: background 0.16s ease;
}

.playlist-item:hover  { background: rgba(var(--re-brand-400-rgb), 0.14); }
.playlist-item.active { background: rgba(var(--re-brand-400-rgb), 0.22); }

.playlist-item-cover {
    width: 30px;
    height: 30px;
    border-radius: var(--re-radius-xs, 4px);
    overflow: hidden;
    background: rgba(var(--re-brand-400-rgb), 0.18);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--re-brand-100);
    font-size: 12px;
}

.playlist-item-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.playlist-item-info {
    min-width: 0;
}

/* The queue reads as chrome, not prose - same face as the player above it. */
.playlist-item-title {
    font-family: var(--re-font-ui);
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--re-text-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.playlist-item-artist {
    font-family: var(--re-font-ui);
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--re-text-color);
    opacity: 0.5;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.playlist-item-playing {
    color: var(--re-brand-200);
    font-size: 14px;
}

/*
 * Take a track out of the queue.
 *
 * These styles live here rather than in music-player-v2.css, where they were
 * written first and did nothing: that file was superseded by this one and its
 * <link> taken out of templates/header.php, so the rules were never served to
 * the site. The button fell back to the browser's own - a grey box with a 2px
 * outset border - which is what "X's that do not fit the theme" was.
 *
 * Quiet until the row is hovered, for the reason the admin icons on the music
 * page are: a column of bright crosses reads as a list of things to delete
 * rather than a list of music.
 */
.playlist-item-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    flex-shrink: 0;
    border: 1px solid transparent;
    border-radius: var(--re-radius-xs, 4px);
    background: none;
    color: var(--re-text-color);
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.16s ease, background 0.16s ease,
                border-color 0.16s ease, color 0.16s ease;
}

.playlist-item:hover .playlist-item-remove,
.playlist-item-remove:focus-visible {
    opacity: 0.65;
}

.playlist-item-remove:hover {
    opacity: 1;
    color: #ffb4a8;
    background: rgba(224, 106, 90, 0.18);
    border-color: rgba(224, 106, 90, 0.5);
}

/* Touch has no hover, and the queue is pruned on a phone more than anywhere. */
@media (hover: none) {
    .playlist-item-remove { opacity: 0.5; }
}

/*
 * "This list is yours, and it is not following the default any more."
 *
 * Shown only on a queue that has been edited. Without it, a queue that has
 * stopped tracking the featured set - and the order set for it on the admin
 * page - looks identical to one that is still following, and the difference
 * only shows up as an ordering that appears to be ignored.
 */
.playlist-note {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin: 0 2px 8px;
    padding: 7px 10px;
    border: 1px solid rgba(var(--re-brand-400-rgb), 0.28);
    border-radius: var(--re-radius-xs, 4px);
    background: rgba(var(--re-brand-400-rgb), 0.10);
    font-family: var(--re-font-ui);
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--re-text-color);
    opacity: 0.8;
}

.playlist-reset {
    flex: 0 0 auto;
    padding: 5px 10px;
    border: 1px solid rgba(var(--re-brand-400-rgb), 0.45);
    border-radius: var(--re-radius-xs, 4px);
    background: none;
    color: var(--re-brand-100);
    font-family: var(--re-font-ui);
    letter-spacing: 1px;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 0.16s ease, border-color 0.16s ease;
}

.playlist-reset:hover {
    background: rgba(var(--re-brand-400-rgb), 0.26);
    border-color: var(--re-brand-200);
}

/* An empty queue is somewhere you can now end up, so it says so. */
.playlist-empty {
    margin: 0;
    padding: 18px 12px;
    text-align: center;
    font-family: var(--re-font-ui);
    letter-spacing: 1px;
    line-height: 1.8;
    color: var(--re-text-color);
    opacity: 0.55;
}

.playlist-empty a {
    color: var(--re-brand-200);
    text-decoration: underline;
}

/* --- minimize ------------------------------------------------------ */

.player-toggle {
    /*
     * A grid item on the transport row, so .player-content's align-items
     * centres it against the play button automatically.
     *
     * It used to be a flex sibling of the grid, positioned by hand: first
     * centred against the whole container - which put it halfway down the
     * open queue - and then pushed down by a fixed 7px, which was the right
     * offset for the 45px expanded row and 6px too much for the 32px
     * minimized one. There is no number here to get wrong now.
     */
    grid-area: toggle;
    width: 30px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    border-radius: 50%;
    padding: 0;
    cursor: pointer;
    color: var(--re-text-color);
    opacity: 0.5;
    font-size: 14px;
    transition: opacity 0.16s ease, background 0.16s ease;
}

.player-toggle:hover {
    opacity: 1;
    background: rgba(var(--re-brand-400-rgb), 0.18);
}

/*
 * Minimized used to mean an 80px round puck floating in the middle of the
 * screen. Docked, it can just be a shorter bar: cover, title, play. Nothing
 * is hidden that you would reach for while a track is running, and the page
 * behind it gains 20px rather than losing a card.
 */
/*
 * Minimized sets its own padding rather than inheriting the full bar's. With
 * a 32px play button the container above would come to 60px, which is not
 * "minimized" by much.
 */
.persistent-player.minimized .player-container {
    padding: 8px 24px;
}

.persistent-player.minimized .progress-container,
.persistent-player.minimized .volume-control,
.persistent-player.minimized .playlist-header,
.persistent-player.minimized .playlist-container,
.persistent-player.minimized .control-btn:not(.control-btn-play) {
    display: none;
}

.persistent-player.minimized .player-content {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas: "track controls toggle";
}

.persistent-player.minimized .track-cover  { width: 30px; height: 30px; font-size: 12px; }
.persistent-player.minimized .track-artist { display: none; }
.persistent-player.minimized .control-btn-play { width: 32px; height: 32px; font-size: 16px; }

/* ------------------------------------------------------------------ *
 * Narrow screens
 * ------------------------------------------------------------------ */

@media (max-width: 900px) {
    .footer-inner {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: 32px;
    }

    .footer-identity {
        grid-column: 1 / -1;
    }
}

@media (max-width: 768px) {
    body.has-player-bar {
        /* Two rows on a phone - transport, then the progress line - so the
           bar is taller here than on a desktop, not shorter. Measured at 81px;
           the rest is clearance so the last line of a page is not sitting on
           the bar. */
        padding-bottom: 92px;
    }

    .footer-inner { padding: 36px 16px 24px; }
    .footer-base  { padding: 16px 16px 22px; }

    .footer-legal {
        margin-left: 0;
        gap: 18px;
    }

    .player-container {
        padding: 10px 14px 12px;
        gap: 10px;
    }

    /*
     * Two rows on a phone: identity and transport on top, the progress line
     * beneath them running the full width. Volume goes - there is a hardware
     * rocker for that - and the queue label collapses to its icon.
     */
    .player-content {
        grid-template-columns: minmax(0, 1fr) auto auto auto;
        grid-template-areas:
            "track    controls queue    toggle"
            "progress progress progress progress"
            "list     list     list     list";
        gap: 4px 10px;
    }

    .volume-control {
        display: none;
    }

    .playlist-toggle-btn span {
        display: none;
    }

    .control-btn      { width: 30px; height: 30px; font-size: 14px; }
    .control-btn-play { width: 36px; height: 36px; font-size: 17px; }

    /* Shuffle and loop are the two a thumb is least likely to reach for
       mid-track, and the row cannot hold five buttons plus a title at
       375px. */
    #randomBtn,
    #loopBtn {
        display: none;
    }

    .track-cover  { width: 34px; height: 34px; }
    /* The two font-size overrides that were here are gone: both lines are
       Silkscreen now and held at 8px site-wide, so they had no effect. */

    .persistent-player.minimized .player-container {
        padding: 7px 14px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .footer-dot {
        animation: none;
    }

    .footer-links a,
    .footer-legal a,
    .playlist-container,
    .progress-handle {
        transition: none;
    }
}

@media print {
    .persistent-player {
        display: none;
    }
}

/* ------------------------------------------------------------------ *
 * Header controls strip - narrow screens
 * ------------------------------------------------------------------ */

/*
 * header-controls-panel.css stacks this strip into a column below 768px, and
 * splits the user block into a second column below 480px. That made it 169px
 * tall on a 375px phone - theme toggle on one line, name and role on another,
 * a register link on a third, a full-width language box on a fourth - sitting
 * on top of another 60px of wrapper padding. Before any page began, a phone
 * had spent nearly a third of its screen on a theme switch and a language
 * picker.
 *
 * One row instead. Nothing is removed: the switch keeps its label, the
 * account links and the language picker keep their targets, they simply sit
 * beside each other the way they already do on a desktop.
 *
 * No !important is needed - these selectors are the same weight as the ones
 * they correct, and this file loads afterwards. That also means this block
 * outranks that file's own 480px rules, which is deliberate: the two-column
 * split it describes is the layout being replaced.
 */
@media (max-width: 768px) {
    .header-controls-panel {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        flex-wrap: nowrap;
        gap: 10px;
        padding: 8px 4px;
    }

    .header-controls-theme {
        justify-content: flex-start;
        padding: 0;
        border-bottom: none;
        flex: 0 0 auto;
    }

    /*
     * Only the mode you are in. Both labels plus the switch will not fit
     * beside the account links at 375px, and the switch on its own is
     * ambiguous - the label is what says which way it is set.
     */
    .header-controls-theme .theme-label:not(.is-active) {
        display: none;
    }

    .header-controls-user {
        flex-direction: row;
        align-items: center;
        justify-content: flex-end;
        gap: 10px;
        padding: 0;
        text-align: right;
        min-width: 0;
    }

    .header-controls-lang,
    .header-controls-lang-toggle {
        width: auto;
    }

    /*
     * Scale, not just fit.
     *
     * Making this one row solved the height and left everything in it sized
     * for a mouse. Measured on a 375px screen the tap targets were 88x26,
     * 107x22, 68x28 - and Register at 43x7. Seven pixels tall. Every
     * platform's guidance puts the floor near 44px and nothing here was close,
     * so the strip technically fitted and could not really be used.
     *
     * The type is left alone. 8px Silkscreen is the site's UI size, set as
     * --re-size-ui and applied to this panel by theme-color-fix.css, and it is
     * what the footer, the breadcrumbs and every meta line already use. Making
     * this one strip bigger would not be scaling it, it would be breaking step
     * with everything around it.
     *
     * Register is the exception, and only because it does not follow that
     * size: it is pinned to 7px here, a size nothing else on the site uses.
     * !important to match, not to escalate - the declaration it corrects is
     * itself !important, so specificity cannot reach it.
     */
    .header-controls-register {
        font-size: var(--re-size-ui, 8px) !important;
    }

    /*
     * A real target under each control. The label stays its own size; this is
     * the box around it, which is what a thumb actually lands on.
     */
    .header-controls-theme > a,
    .header-controls-profile,
    .header-controls-register,
    .header-controls-lang-toggle {
        display: inline-flex;
        align-items: center;
        min-height: 34px;
    }

    .header-controls-register {
        /* !important for the same reason as the size above: the padding it
           corrects is `padding: 0 !important`, so nothing weaker reaches it. */
        padding: 0 6px !important;
    }

    /*
     * The role badge goes.
     *
     * It renders "guest | Guest" - the role beside a username that, for a
     * signed-out visitor, is the same word. 39px to say Guest twice, on the
     * screen with the least room to say anything. Signed in it reads
     * "administrator | Frank", where the name alone is what identifies you
     * and the role is not news to the person holding the account.
     */
    .header-controls-panel .user-role-display {
        display: none;
    }
}

/*
 * Below 380px - iPhone SE and the narrower Androids - the language picker's
 * border and chevron are more furniture than the row can spare. The globe and
 * the code still say what it is and still open the same menu.
 */
@media (max-width: 380px) {
    .header-controls-lang-toggle {
        border-color: transparent;
        padding-left: 4px;
        padding-right: 4px;
    }

    .header-controls-lang-toggle::after {
        display: none;
    }
}

/* ------------------------------------------------------------------ *
 * Header controls strip - pointer screens
 * ------------------------------------------------------------------ */

/*
 * The same problem above 768px, quieter.
 *
 * Measured at 1280px the strip's targets were 152x26, 107x22, 68x28 - and
 * Register at 48x8. A mouse is far more forgiving than a thumb, but WCAG's
 * floor is 24x24 and half of these sat under it. An eight-pixel-high link is
 * not a target, it is a line of text you have to aim at.
 *
 * 28px, which is what the language pill already measures. Choosing the
 * tallest control means the panel's own height does not move: its content box
 * is 29px and the pill was already filling it, so every control grows into
 * space the strip had already reserved. Nothing shifts, nothing reflows, and
 * the difference is only felt by the pointer.
 *
 * The labels are untouched. 8px Silkscreen is --re-size-ui, the size the
 * footer and the breadcrumbs use, and this strip should look like they do.
 */
@media (min-width: 769px) {
    .header-controls-theme > a,
    .header-controls-profile,
    .header-controls-register,
    .header-controls-lang-toggle {
        min-height: 28px;
    }

    /* Register carries no background, so its target is exactly its text.
       A little padding is the only thing that widens it. */
    .header-controls-register {
        padding: 0 6px !important;
    }

    /*
     * The role badge is the desktop's 7px outlier - the same fault Register
     * had, from user-role-colors.css rather than from here. It sits inside
     * the profile link at a size nothing else on the site uses; 8px puts it
     * back in step with the strip it is printed on.
     */
    .header-controls-panel .user-role-display {
        font-size: var(--re-size-ui, 8px);
    }
}

/* ------------------------------------------------------------------ *
 * Soft navigation
 * ------------------------------------------------------------------ */

/*
 * A page change no longer reloads the browser, so the browser no longer shows
 * that anything is happening - no spinner in the tab, no progress bar. On a
 * slow connection a click would look ignored, and the second click would be
 * a second request.
 *
 * This is that missing feedback and nothing more: a hairline across the top
 * in the palette's own accent, only while a fetch is in flight.
 */
html.is-softnav::after {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    height: 2px;
    width: 100%;
    z-index: 20000;
    background: linear-gradient(90deg,
        transparent 0%,
        var(--re-brand-200) 40%,
        var(--re-brand-100) 50%,
        var(--re-brand-200) 60%,
        transparent 100%);
    animation: re-softnav-sweep 1s linear infinite;
    pointer-events: none;
}

@keyframes re-softnav-sweep {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
}

/* The bar is a progress hint, not decoration: hold it still rather than
   removing it, so it still says "working" without the movement. */
@media (prefers-reduced-motion: reduce) {
    html.is-softnav::after { animation: none; }
}

/* While the new page is being fetched the old one is still interactive, which
   invites a second click on a link that is already loading. */
html.is-softnav a { cursor: progress; }
