.song-path {
    /* Takes over the scrolling the old .song-grid did: its container is
       overflow:hidden, so without this the road is clipped and unreachable. */
    position: relative;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: thin;
    scrollbar-color: rgba(201, 169, 111, 0.3) transparent;
}

.song-path__inner {
    position: relative;
    /* Was capped at 940px inside a 1120px shell, which stacked two different content
       widths and left a visible step under the header. The road is a map, not a
       column of prose - it takes the shell's full width and the shell caps it. */
    margin: 0 auto;
    padding: 4px 0 56px;
}

.song-path::-webkit-scrollbar {
    width: 8px;
}

.song-path::-webkit-scrollbar-thumb {
    background: rgba(201, 169, 111, 0.3);
    border-radius: 4px;
}

.song-path__line {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
    pointer-events: none;
    overflow: visible;
}

.song-path__act {
    position: relative;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 18px 20px 12px;
}

/* The header needs a dark ground under its text, but a filled bar (and a
   backdrop-filter rectangle) drew two hard edges straight across the artwork.
   This carries the same darkness on a taller layer that fades out past the
   header, so the text keeps its ground and the art keeps its continuity. */
.song-path__act::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: -34px;
    bottom: -34px;
    z-index: -1;
    pointer-events: none;
    background: linear-gradient(180deg,
            transparent 0,
            rgba(9, 14, 24, 0.88) 34px,
            rgba(9, 14, 24, 0.88) calc(100% - 34px),
            transparent 100%);
    background: linear-gradient(180deg,
            transparent 0,
            color-mix(in srgb, var(--sh-ground) 88%, transparent) 34px,
            color-mix(in srgb, var(--sh-ground) 88%, transparent) calc(100% - 34px),
            transparent 100%);
}

.song-path__act-no {
    flex: none;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.2em;
    color: var(--sh-gold);
    border: 1px solid rgba(217, 184, 124, 0.42);
    border: 1px solid color-mix(in srgb, var(--sh-gold) 42%, transparent);
    border-radius: 99px;
    padding: 3px 10px;
}

.song-path__act-title {
    flex: none;
    margin: 0;
    font-size: 13px;
    font-weight: 650;
    letter-spacing: 0.15em;
    color: var(--sh-text);
}

.song-path__act-sub {
    flex: 1 1 auto;
    margin: 0;
    font-size: 11.5px;
    color: var(--sh-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.song-path__act-tally {
    flex: none;
    font-size: 11px;
    letter-spacing: 0.1em;
    color: var(--sh-dim);
    font-variant-numeric: lining-nums tabular-nums;
}

.song-path__row {
    position: relative;
    z-index: 2;
    /* Tracks the disc, which now scales with the frame. A fixed 84px row left only
       6px between 78px discs on a wide screen. */
    height: clamp(92px, 5.9vw, 112px);
}

.song-path__node {
    position: absolute;
    left: var(--lane-left, auto);
    right: var(--lane-right, auto);
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    gap: 12px;
    background: none;
    border: 0;
    padding: 6px;
    margin: -6px;
    border-radius: 14px;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    max-width: 46%;
}

.song-path__node[style*="--lane-left"] {
    margin-left: -38px;
}

.song-path__node[style*="--lane-right"] {
    margin-right: -38px;
}

.song-path__node--right {
    flex-direction: row-reverse;
    text-align: right;
}

.song-path__node:focus-visible {
    outline: 2px solid var(--sh-gold);
    outline-offset: 3px;
}

.song-path__disc {
    position: relative;
    flex: none;
    width: clamp(58px, 4.4vw, 78px);
    height: clamp(58px, 4.4vw, 78px);
    border-radius: 50%;
    background-color: var(--sh-card);
    background-size: cover;
    background-position: center;
    border: 2px solid var(--sh-line-2);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.55);
    transition: transform 140ms ease, box-shadow 140ms ease;
}

.song-path__node:hover .song-path__disc {
    transform: scale(1.06);
}

.song-path__node[aria-expanded="true"] .song-path__disc {
    transform: scale(1.06);
}

.song-path__node--played .song-path__disc {
    border-color: rgba(217, 184, 124, 0.7);
    border-color: color-mix(in srgb, var(--sh-gold) 70%, transparent);
}

.song-path__node--complete .song-path__disc {
    border-color: var(--sh-gold);
    box-shadow: 0 0 0 3px rgba(217, 184, 124, 0.16),
        0 4px 14px rgba(0, 0, 0, 0.55);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--sh-gold) 16%, transparent),
        0 4px 14px rgba(0, 0, 0, 0.55);
}

.song-path__node--fresh:not(.song-path__node--hero) .song-path__disc {
    filter: saturate(0.42) brightness(0.62);
}

/* The stop you are on is a card, not a disc. The cover art is the best thing in
   this product and a 78px circle showed it at 8% of the area the old grid did -
   which is the whole reason the road read as less impressive than the page it
   replaced. One card per screen, exactly where the eye should land. */
.song-path__node--hero .song-path__disc {
    width: clamp(220px, 21vw, 320px);
    height: clamp(124px, 11.8vw, 180px);
    border-radius: 16px;
    filter: none;
    border-color: var(--sh-gold);
    border-color: color-mix(in srgb, var(--sh-gold) 85%, var(--sh-text));
    box-shadow: 0 0 0 4px rgba(217, 184, 124, 0.2),
        0 0 34px 8px rgba(217, 184, 124, 0.22),
        0 14px 34px rgba(0, 0, 0, 0.62);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--sh-gold) 20%, transparent),
        0 0 34px 8px color-mix(in srgb, var(--sh-gold) 22%, transparent),
        0 14px 34px rgba(0, 0, 0, 0.62);
}

/* The row has to make room for it; every other row keeps its own rhythm. */
.song-path__row--hero {
    height: clamp(150px, 13.4vw, 208px);
}

/* The hero is almost always an unplayed stop, so it was picking up the "not started"
   muting and the focal point of the screen ended up its dimmest text. */
.song-path__node--hero .song-path__name {
    font-size: clamp(16px, 1.35vw, 21px);
    font-weight: 680;
    color: var(--sh-text);
    white-space: normal;
}

.song-path__node--hero .song-path__disc {
    filter: none;
}

.song-path__node--hero .song-path__stars {
    opacity: 1;
}

.song-path__node--hero .song-path__stars .star-icon.empty .star-fill {
    fill: rgba(226, 235, 248, 0.6);
}

.song-path__node--hero .song-path__cue {
    margin-top: 8px;
    padding: 7px 16px;
    font-size: 11px;
}

.song-path__node--hero .song-path__mark {
    right: -4px;
    bottom: -4px;
    width: 24px;
    height: 24px;
}

.song-path__mark {
    position: absolute;
    right: -3px;
    bottom: -3px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 11px;
    font-weight: 800;
    line-height: 1;
    border: 2px solid var(--sh-ground);
    background: var(--sh-ok);
}

/* Drawn, not typed: a text tick renders differently on Windows and iOS. */
.song-path__mark::after {
    content: '';
    width: 3px;
    height: 7px;
    margin-top: -2px;
    border: solid var(--sh-on-light);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

.song-path__mark--half::after {
    width: 5px;
    height: 5px;
    margin: 0;
    border: 0;
    border-radius: 50%;
    background: var(--sh-on-light);
    transform: none;
}

/* Gold tick: played and the passage heard. Green tick (the base) is played. The
   half dot now only means the rarer case of scripture taken without playing. */
.song-path__mark--full {
    background: var(--sh-gold);
    box-shadow: 0 0 10px color-mix(in srgb, var(--sh-gold) 55%, transparent);
}

.song-path__mark--full::after {
    border-color: var(--sh-on-light);
}

.song-path__mark--half {
    background: var(--sh-gold);
    color: var(--sh-on-light);
}

.song-path__meta {
    min-width: 0;
}

.song-path__name {
    display: block;
    font-size: 13px;
    font-weight: 620;
    color: var(--sh-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.song-path__node--fresh:not(.song-path__node--hero) .song-path__name {
    color: var(--sh-dim);
}

.song-path__stars {
    display: flex;
    gap: 2px;
    margin-top: 4px;
    line-height: 1;
}

/* The double-dim lives inside .star-icon.empty itself (fill alpha 0.2 AND
   opacity 0.3, rhythm_overlays.css:1191-1197) - about 6% before the row is touched.
   Scaling the row could only ever make that worse, so override the glyph directly.
   Measures 5.38:1 on --sh-ground; the WCAG floor for non-text UI is 3:1. */
.song-path__stars .star-icon.empty .star-fill {
    fill: rgba(226, 235, 248, 0.55);
    opacity: 1;
}

.song-path__stars--none {
    opacity: 1;
}

.song-path__cue {
    display: inline-block;
    margin-top: 6px;
    padding: 5px 12px;
    border-radius: 99px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.14em;
    color: var(--sh-on-light);
    /* Solid first. Without it a UA that cannot parse color-mix drops the whole
       gradient to transparent and renders this near-black on the dark road. */
    background: var(--sh-gold);
    background: linear-gradient(180deg, color-mix(in srgb, var(--sh-gold) 55%, #FFFFFF), var(--sh-gold));
}

.song-path__end {
    position: relative;
    z-index: 2;
    text-align: center;
    padding-top: 20px;
    font-size: 11px;
    letter-spacing: 0.18em;
    color: var(--sh-faint);
}

.song-path__jump {
    /* Absolute inside #beatmapContainer (which is position:relative), not sticky and
       not fixed. Sticky never engaged because this is a sibling of the scroller, and
       fixed resolves against .start-screen-inner's transform rather than the
       viewport. Out of flow either way, so toggling it cannot resize the road. */
    position: absolute;
    left: 50%;
    bottom: calc(14px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    z-index: 6;
    width: max-content;
    padding: 9px 18px;
    border: 1px solid rgba(217, 184, 124, 0.45);
    border: 1px solid color-mix(in srgb, var(--sh-gold) 45%, transparent);
    border-radius: 99px;
    background: rgba(var(--sh-scrim-rgb), 0.92);
    color: var(--sh-gold);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.14em;
    cursor: pointer;
    backdrop-filter: blur(6px);
}

.song-path__jump:focus-visible {
    outline: 2px solid var(--sh-gold);
    outline-offset: 2px;
}

.song-path__jump[hidden] {
    display: none !important;
}

.song-detail {
    position: fixed;
    inset: 0;
    /* Above the road and below everything that can open FROM this panel:
       .overlay-screen (results and game over) is 1000 and is appended to <body>, so
       it wins any tie on DOM order; .scriptures-modal-overlay sits at 1200 to clear
       it, .unlock-modal-overlay is 2000, and the limit, premium and settings modals
       share 10000. Raising this above 1200 would put the scriptures modal behind
       the panel that launched it. */
    z-index: 900;
    display: grid;
    place-items: center;
    padding: 20px;
    background: rgba(var(--sh-scrim-rgb), 0.72);
    backdrop-filter: blur(3px);
}

.song-detail[hidden] {
    display: none !important;
}

.song-detail__panel {
    position: relative;
    width: min(420px, 100%);
    max-height: calc(100vh - 40px);
    max-height: calc(100dvh - 40px);
    overflow-y: auto;
    background: var(--sh-panel);
    border: 1px solid var(--sh-line-2);
    border-radius: 14px;
    padding: 16px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}

.song-detail__head {
    margin: 0 0 12px;
    padding-right: 34px;
}

.song-detail__act {
    margin: 0 0 3px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.2em;
    color: var(--sh-gold);
}

.song-detail__verse {
    font-family: var(--font-sans);
    font-variant-numeric: lining-nums tabular-nums;
    margin: 6px 0 0;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--sh-dim);
}

.song-detail__verse[hidden] {
    display: none !important;
}

.song-detail__unplayed {
    color: var(--sh-faint) !important;
    font-style: italic;
}

.song-detail__song {
    margin: 0;
    font-size: 19px;
    font-weight: 650;
    line-height: 1.2;
    color: var(--sh-text);
    text-wrap: balance;
}

/* The title also sits inside the tile, over the artwork, where a bright song
   background can swallow it. The panel heading is the reliable one. */
body.rhythm-mode .song-detail .beatmap-title {
    display: none;
}

/* The panel opens on the difficulty picker, so inside it the tile is a launch
   card rather than a two-step. These trim what the shared tile brings that this
   context has already said or no longer needs. */
.song-detail .beatmap-tile .beatmap-title {
    display: none;                      /* the panel head already names the song */
}

.song-detail .beatmap-tile .difficulty-back-button {
    display: none;                      /* nothing to go back to; the picker is the view */
}

.song-detail .beatmap-tile.is-picking .beatmap-difficulty-card {
    grid-template-columns: minmax(0, 1fr);
}

/* Scripture and preview stay reachable; only Play goes, because choosing a
   difficulty is what starts the song now. */
.song-detail .beatmap-tile.is-picking:not(.is-launching) .beatmap-buttons {
    display: flex;
    justify-content: center;
    gap: 8px;
    margin-top: 10px;
}

/* Secondary, and shaped like it: full-width pills alongside the difficulty row
   read as two more primary actions. */
.song-detail .beatmap-tile.is-picking .beatmap-buttons .tile-button {
    flex: 0 0 auto;
    width: 40px;
    min-width: 0;
    padding: 0;
}

.song-detail .beatmap-tile .beatmap-buttons .play-button {
    display: none;
}

/* Three buttons labelled Easy/Medium/Hard do not say that pressing one starts
   the song. One line removes the guess. */
.song-detail .beatmap-tile.is-picking .beatmap-difficulty-card::before {
    content: 'Choose a difficulty to start';
    display: block;
    margin-bottom: 8px;
    text-align: center;
    font: 600 11px/1 var(--font-sans, system-ui, sans-serif);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--sh-dim, #9FB0C7);
}

/* Was a bordered card with a dt/dd grid. One fact does not need a container, and
   a boxed panel floating halfway down a tall column is what made the space under
   it read as a hole rather than as margin. It now sits with the title and the
   reference as the third line of one block. */
.song-detail__best {
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 6px 0 12px;
    font-family: var(--font-sans);
    font-size: 12.5px;
    color: var(--sh-dim);
    font-variant-numeric: lining-nums tabular-nums;
}

.song-detail__best[hidden] {
    display: none !important;
}

.song-detail__best-stars {
    display: inline-flex;
    gap: 2px;
}

.song-detail__best-figures {
    letter-spacing: 0.02em;
}

/* The primary action took the song's own accent at full strength, which made a
   saturated slab the loudest thing in a gold-and-navy panel. The accent stays as a
   hairline; the button itself matches the rest of the interface. */
body.rhythm-mode .song-detail .play-button {
    background: linear-gradient(180deg, #F0D9A8, var(--sh-gold));
    border-color: var(--sh-gold);
    color: var(--sh-on-light);
    border-radius: 999px;
    box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--song-accent, var(--sh-gold)) 60%, transparent);
}

body.rhythm-mode .song-detail .tile-button:not(.play-button) {
    border-radius: 999px;
}

.song-detail__close {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: 1px solid var(--sh-line-2);
    background: rgba(var(--sh-control-rgb), 0.9);
    color: var(--sh-text);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
}

.song-detail__close:focus-visible {
    outline: 2px solid var(--sh-gold);
    outline-offset: 2px;
}

/* The tile carries clamp(280px, 30vw, 340px) on width AND min-width AND max-width,
   plus width:320px + aspect-ratio:16/9 under 520px. The old .song-grid rule unset
   all of it; moving the tile into a modal dropped that, which overflowed every
   phone by 32-72px and left dead space on the right at desktop widths. */
.song-detail .beatmap-tile {
    width: 100%;
    min-width: 0;
    max-width: none;
    aspect-ratio: auto;
    margin: 0 auto;
}

@media (max-width: 720px) {
    .song-path__row {
        height: 76px;
    }

    .song-path__disc {
        width: 50px;
        height: 50px;
    }

    /* Beats the custom properties without needing a re-render, which is the whole
       reason the lane geometry is not written as inline left/right any more. */
    .song-path__node,
    .song-path__node--right,
    .song-path__node[style*="--lane-left"],
    .song-path__node[style*="--lane-right"] {
        flex-direction: row;
        text-align: left;
        max-width: none;
        left: 18px;
        right: auto;
        margin-left: 0;
        margin-right: 0;
    }

    .song-path__act-sub {
        display: none;
    }

    .song-path__disc {
        width: 50px;
        height: 50px;
    }

    .song-path__node--hero .song-path__disc {
        width: 188px;
        height: 106px;
    }

    .song-path__row--hero {
        height: 132px;
    }
}

@media (prefers-reduced-motion: reduce) {

    .song-path__disc,
    .song-path__node:hover .song-path__disc {
        transition: none;
        transform: none;
    }
}

/* A chart that failed to load. The stop stays on the road so the act tally and the
   song count stay honest, but it cannot be opened. */
.song-path__node--missing {
    cursor: default;
    opacity: 0.4;
}

.song-path__node--missing .song-path__disc {
    filter: grayscale(1) brightness(0.5);
}

.song-path__node--missing:hover .song-path__disc {
    transform: none;
}

/* Tutorial cards for the panel are top-anchored, so the panel moves to the bottom
   for the duration. Centred, it sat exactly where the card renders. */
body.tutorial-active .song-detail {
    place-items: end center;
    padding-bottom: 24px;
}

/* Tutorial cards for the panel are pinned at top:20px and run 300-360px tall. On a
   short viewport a full-height panel still reached them, so it is capped for the
   duration - the cards only need the buttons visible, not the whole tile. */
@media (max-height: 820px) {
    body.tutorial-active .song-detail__panel {
        max-height: 46vh;
        max-height: 46dvh;
    }
}

/* On narrow screens the nodes run the full width, so the rescue pill would sit over
   the bottom-most node's title and swallow its click. */
@media (max-width: 720px) {
    .song-path {
        padding-bottom: 56px;
    }
}

/* ---- Start-screen header ------------------------------------------------- */

/* The header sat flush to the panel edge, so the back link and the gear touched
   the border. It also declared z-index:1, which opens a stacking context that
   trapped its own tooltips BELOW the road's act headers (z-index 3). */
body.rhythm-mode .start-header {
    padding-left: clamp(12px, 2vw, 22px);
    padding-right: clamp(12px, 2vw, 22px);
    z-index: 30 !important;
}

/* Pills rather than boxes. 6px on a 44px control reads as a square with the
   corners knocked off; these are round enough to look deliberate. */
body.rhythm-mode .start-header .bible-progress-link {
    border-radius: 999px;
    padding: 8px 16px;
}

body.rhythm-mode .start-header__btn {
    border-radius: 999px;
    border-color: var(--sh-line-2);
    background: rgba(var(--sh-control-rgb), 0.55);
}

body.rhythm-mode .start-header__btn:hover {
    background: rgba(var(--sh-control-hi-rgb), 0.75);
}

/* The road's act header is sticky-feeling and blurred; nothing from the chrome
   above it should ever be able to fall behind it. */
body.rhythm-mode .start-header [data-tip]::after,
body.rhythm-mode .start-header [data-tip]::before {
    z-index: 200;
}

/* The menu shell caps at 1120px, which suits a form or a list. The road is a map:
   at 1920 that cap left 80% of the screen empty and the road stranded mid-frame. */
body.rhythm-mode .start-screen-inner {
    width: min(1440px, 100%);
}

/* Left over from the grid layout, which wanted a reading-width column. It capped
   the road 380px narrower than its own header and produced the visible step. */
body.rhythm-mode #beatmapContainer {
    max-width: none;
    padding-left: 0;
    padding-right: 0;
}

/* 0.16em tracking on the label pushed the colon a space away from the word, so it
   read "DURATION : 1:49". Trailing tracking belongs before the colon, not after. */
body.rhythm-mode .song-detail .beatmap-meta-label {
    margin-right: -0.16em;
}

/* themes.css declares `h1..h6 { color: var(--text-primary) !important }` for the
   light themes. Rhythm Mode is a dark shell that sets no data-theme, so bare :root
   applies and --text-primary is #2A3342 - dark ink on a dark panel. An !important
   on a bare element selector beats every author rule regardless of specificity, so
   EVERY heading in this mode was losing its colour: the results title, both stats
   card headings, the settings and calibration titles, the game-over title, the
   premium and stage-banner titles. Measured between 1.08:1 and 1.52:1.
   `inherit` hands each heading back to its container, which in this shell is
   already the right light colour. */
body.rhythm-mode :is(h1, h2, h3, h4, h5, h6) {
    color: inherit !important;
}

/* Headings whose author rule asked for something other than the container colour,
   restored at matching importance. */
body.rhythm-mode .song-detail__song,
body.rhythm-mode .song-path__act-title,
body.rhythm-mode .stats-title,
body.rhythm-mode .gameover-title,
body.rhythm-mode .rhythm-settings-title,
body.rhythm-mode .premium-modal-title,
body.rhythm-mode .stage-banner__title,
body.rhythm-mode .calibration-title {
    color: var(--sh-text) !important;
}

body.rhythm-mode .fx-settings-section h4 {
    color: #8888ff !important;
}

/* themes.css also forces `header a` and `header span` to #FFFFFF with a #C9A96F
   border. The start screen uses a semantic <header>, so the back link was handed a
   gold ring it never asked for (2.24:1 white-on-gold) and every span in the header
   - the daily-limit readout, the No Fail label - was repainted pure white. */
body.rhythm-mode header.start-header a,
body.rhythm-mode header.start-header span {
    color: inherit !important;
    border-color: inherit !important;
}

body.rhythm-mode header.start-header .bible-progress-link {
    color: var(--sh-dim) !important;
    border-color: var(--sh-line) !important;
}

body.rhythm-mode header.start-header .start-header__iron span {
    color: #FFB4A2 !important;
}

/* Stacked, the artwork took 61% of the panel's height and the panel stayed 420px
   at every screen size. Side by side, the art keeps its presence, the information
   sits beside it instead of under it, and the whole card is shorter and wider. */
@media (min-width: 760px) {

    .song-detail__panel {
        width: min(680px, 100%);
        display: grid;
        /* Art gets the larger share - it is the recognisable thing. Measured against
           the longest titles in the catalogue: `auto` on the info column collapses
           the art to 140px for "These troubles that Im walking through", and 1.3fr
           costs an extra wrapped line on two songs. */
        grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
        /* A spacer row above and below centres the title block against the art.
           Top-aligned, the column was a short heading with a tall empty space
           under it, which reads as something missing rather than as margin. */
        grid-template-areas:
            "art topgap"
            "art head"
            "art stats"
            "art botgap";
        grid-template-rows: 1fr auto auto 1fr;
        column-gap: 20px;
        align-items: start;
        padding: 18px;
    }

    .song-detail__head {
        grid-area: head;
        margin: 0 0 12px;
    }

    .song-detail__best {
        grid-area: stats;
        align-self: start;
        margin: 2px 0 0;
    }

    .song-detail__body {
        grid-area: art;
        align-self: stretch;
    }

    .song-detail__song {
        font-size: 22px;
    }

}

/* themes.css protects the site navbar and footer by forcing #3A5A78 with
   !important onto the BARE `header` and `footer` element selectors. Rhythm Mode's
   start screen uses semantic <header> and <footer>, so it was inheriting the site
   chrome's blue slab and white text on top of its own dark shell - which is why
   the header read as a pale blue bar rather than part of this screen.
   Equal importance, higher specificity. */
body.rhythm-mode header.start-header,
body.rhythm-mode footer.start-footer {
    background-color: transparent !important;
    color: var(--sh-dim) !important;
    border-color: var(--sh-line) !important;
}

body.rhythm-mode header.start-header .start-header__title {
    color: var(--sh-text) !important;
}

body.rhythm-mode footer.start-footer .start-footer__hint {
    color: var(--sh-faint) !important;
}

/* ---- modal consistency --------------------------------------------------- */

/* Ten modals, no shared shell: radii of 0, 10 and 14px, and three backdrops.
   --sh-radius is 10px and every other modal already uses it. The tutorial's
   lighter scrim is deliberately left alone - you have to see the UI it points at. */
body.rhythm-mode .song-detail__panel {
    border-radius: var(--sh-radius);
}

body.rhythm-mode .song-detail {
    background: rgba(4, 8, 13, 0.9);
}

body.rhythm-mode .stats-content {
    border-radius: var(--sh-radius);
}

/* Four close-button treatments across the modals that have one: 40x40 r6,
   40x40 r10 on a white tint, 36x36 r6, and a 30x30 circle. One shape. */
body.rhythm-mode .song-detail__close,
body.rhythm-mode .scriptures-modal-close,
body.rhythm-mode .tutorial-close-btn,
body.rhythm-mode .rhythm-settings-close {
    width: 36px;
    height: 36px;
    border-radius: var(--sh-radius-sm);
    background: rgba(var(--sh-control-rgb), 0.8);
    border: 1px solid var(--sh-line);
    color: var(--sh-dim);
}

body.rhythm-mode .song-detail__close {
    position: absolute;
    top: 12px;
    right: 12px;
}

/* themes.css also forces two of its own utility classes with !important, and both
   are class names Rhythm Mode happens to use.

   `.selected` -> background: rgba(58,90,120,.1) + color: #2A3342. On the difficulty
   picker that composited to light text on a near-white fill: about 1.06:1. The
   control that tells you which difficulty you are about to play was invisible.

   `.back-to-selection-btn` -> bg var(--bg-secondary) + color var(--text-primary),
   i.e. a white button with dark ink dropped into a dark results screen. */
body.rhythm-mode .difficulty-toggle-option.selected {
    background: linear-gradient(180deg, #F0D9A8, var(--sh-gold)) !important;
    color: var(--sh-on-light) !important;
    border-color: var(--sh-gold) !important;
}

body.rhythm-mode .difficulty-toggle-option.selected span {
    color: var(--sh-on-light) !important;
}

body.rhythm-mode .back-to-selection-btn {
    background: rgba(var(--sh-control-rgb), 0.85) !important;
    color: var(--sh-dim) !important;
    border: 1px solid var(--sh-line) !important;
}

body.rhythm-mode .back-to-selection-btn:hover {
    background: rgba(var(--sh-control-hi-rgb), 0.9) !important;
    color: var(--sh-text) !important;
}

/* Each act carries an atmosphere blurred from its own songs' cover art, so the
   ground beside the road takes that act's colour instead of staying flat black.
   The source images already sit at luminance 17-23/255; the gradient keeps the
   seam between acts soft so the road reads as continuous, not as eight panels. */
/* Part one of making the preview discoverable: the badge appears the instant the
   pointer lands, before the dwell has elapsed. Without it a 200ms hover teaches
   nothing and the delay reads as lag; with it, every near-miss says "there is
   sound here, stay a moment". Works for every player, every time, with nothing
   stored and nothing to dismiss. */
.song-path__disc::after {
    content: '';
    position: absolute;
    right: -6px;
    bottom: -6px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: rgba(9, 14, 24, 0.92) url('/static/icons/headphones.png') center / 13px 13px no-repeat;
    border: 1px solid color-mix(in srgb, var(--sh-gold, #D9B87C) 60%, transparent);
    opacity: 0;
    transform: scale(0.7);
    transition: opacity .13s ease, transform .13s ease;
    pointer-events: none;
}

.song-path__node:hover .song-path__disc::after,
.song-path__node:focus-visible .song-path__disc::after,
.song-path__node.is-previewing .song-path__disc::after {
    opacity: 1;
    transform: scale(1);
}

.song-path__node.is-previewing .song-path__disc::after {
    background-color: var(--sh-gold, #D9B87C);
    animation: songPathBadgeBeat 1.1s ease-in-out infinite;
}

@keyframes songPathBadgeBeat {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.14); }
}

/* The audio is fetched over the network, so there is a moment between the stop
   lighting up and any sound arriving. The badge the player is already watching
   reports it, rather than a second floating label. */
.song-path__node.is-preview-loading {
    cursor: progress;
}

.song-path__node.is-preview-loading .song-path__disc::after {
    opacity: 1;
    transform: scale(1);
    background-color: rgba(9, 14, 24, 0.92);
    animation: songPathBadgeWaiting 0.9s ease-in-out infinite;
}

@keyframes songPathBadgeWaiting {
    0%, 100% { opacity: 0.4; }
    50%      { opacity: 1; }
}

/* Part two: the nudge that gets them to hover in the first place. Sits at the head
   of the road, where a first-time player starts, and leaves for good the first
   time a preview actually plays. */
.song-path__tip {
    position: absolute;
    z-index: 5;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 0;
    width: max-content;
    max-width: 46%;
    padding: 9px 17px;
    border: 1px solid var(--sh-gold, #D9B87C);
    border-radius: 999px;
    background: rgba(9, 14, 24, 0.92);
    color: var(--sh-gold, #D9B87C);
    font: 700 12px/1.3 var(--font-sans, system-ui, sans-serif);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
    pointer-events: none;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--sh-gold, #D9B87C) 14%, transparent),
                0 6px 20px rgba(0, 0, 0, 0.5);
    animation: songPathTipBreathe 2.4s ease-in-out infinite;
}

/* It is competing with cover art on a lit band, so it breathes rather than sits.
   Slow and small: enough to catch the eye, not enough to nag while reading. */
/* Pinned rather than sitting beside the first stop, so it is reachable wherever
   the road happens to be scrolled to. */
.song-path__tip--action {
    position: fixed;
    top: auto;
    left: 50%;
    right: auto;
    bottom: 30px;
    transform: translateX(-50%);
    z-index: 1100;
    pointer-events: auto;
    cursor: pointer;
    font-family: inherit;
}

.song-path__tip--action:hover {
    filter: brightness(1.12);
}

.song-path__tip--action:focus-visible {
    outline: 2px solid var(--sh-gold, #D9B87C);
    outline-offset: 3px;
}

@keyframes songPathTipBreathe {
    0%, 100% {
        box-shadow: 0 0 0 4px color-mix(in srgb, var(--sh-gold, #D9B87C) 14%, transparent),
                    0 6px 20px rgba(0, 0, 0, 0.5);
    }
    50% {
        box-shadow: 0 0 0 9px color-mix(in srgb, var(--sh-gold, #D9B87C) 0%, transparent),
                    0 6px 20px rgba(0, 0, 0, 0.5);
    }
}

.song-path__tip img { width: 15px; height: 15px; opacity: 0.95; }

.song-path__tip--going {
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity .4s ease, transform .4s ease;
}

@media (prefers-reduced-motion: reduce) {
    .song-path__node.is-previewing .song-path__disc::after { animation: none; }
    .song-path__tip { animation: none; }
    .song-path__tip--going { transition: none; }
}

/* A stop that is currently auditioning. Ring plus a soft pulse on the art, so
   it is obvious which of the 63 you are hearing without reading anything. */
.song-path__node.is-previewing .song-path__disc {
    box-shadow: 0 0 0 2px var(--sh-gold, #D9B87C),
                0 0 22px color-mix(in srgb, var(--sh-gold, #D9B87C) 55%, transparent);
    animation: songPathPreviewPulse 1.6s ease-in-out infinite;
}

.song-path__node.is-previewing .song-path__name {
    color: var(--sh-gold, #D9B87C);
}

@keyframes songPathPreviewPulse {
    0%, 100% { filter: brightness(1); }
    50%      { filter: brightness(1.18); }
}

@media (prefers-reduced-motion: reduce) {
    .song-path__node.is-previewing .song-path__disc { animation: none; }
}

.song-path__band {
    position: relative;
    /* rhythm_path.js sets this per act from the act's own atmosphere. Declared
       here so the token has a home in the stylesheet and a band without one
       simply has no artwork rather than an invalid background-image. */
    --act-atmos: none;
}

/* The atmosphere is painted by this layer rather than by the section itself so it
   can hang past the section's own edges. Each act's art reaches 90px into the acts
   either side of it and fades over 180px, so neighbouring acts cross-fade through
   each other. Painting it on the section instead left both images falling to bare
   ground at the join, which read as a flat strip between every act. */
.song-path__band::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: -90px;
    bottom: -90px;
    z-index: 0;
    pointer-events: none;
    background-image: var(--act-atmos);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    opacity: 0.72;
    -webkit-mask-image: linear-gradient(180deg,
            transparent 0,
            #000 180px,
            #000 calc(100% - 180px),
            transparent 100%);
    mask-image: linear-gradient(180deg,
            transparent 0,
            #000 180px,
            #000 calc(100% - 180px),
            transparent 100%);
}

.song-path__band > * {
    position: relative;
    z-index: 1;
}
