/* Rhythm Mode gameplay layer. See docs/rhythm-mode-gameplay-layer.md */
.game-container, .highway-perspective { position: relative; z-index: 1; }

html.hud-side .game-hud,
html.hud-side .play-dock {
    position: fixed;
    top: var(--hit-mid, 70%);
    transform: translateY(-50%);
    z-index: 70;
    margin: 0;
    padding: 0;
    width: auto;
    background: none;
    border: none;
    pointer-events: none;
    display: block;
}

html.hud-side .game-hud { left: 0; width: var(--gutter-left, 200px); }
html.hud-side .play-dock { left: var(--track-right, 70%); width: var(--gutter-right, 200px); }

html.hud-side .score-section,
html.hud-side .focus-meter,
html.hud-side .health-section {
    background: rgba(12, 16, 22, .82);
    border: 1px solid rgba(255, 215, 0, .22);
    border-radius: 12px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, .55);
    padding: 10px 14px;
    margin: 0 0 8px;
    width: calc(100% - 24px);
    margin-left: 12px;
    backdrop-filter: blur(3px);
}

html.hud-side .score-value { font-size: 30px; font-weight: 800; line-height: 1.05; }
html.hud-side .score-label { font-size: 10px; letter-spacing: .16em; opacity: .55; }

html.hud-side .multiplier-display {
    font-size: 34px;
    font-weight: 900;
    line-height: 1;
    color: #FFD700;
    text-shadow: 0 0 18px rgba(255, 215, 0, .5);
}
html.hud-side .streak-count { font-size: 11px; letter-spacing: .1em; opacity: .7; margin-top: 2px; }
html.hud-side .health-label { font-size: 10px; letter-spacing: .16em; opacity: .55; }
html.hud-side .health-percentage { font-size: 13px; opacity: .8; }

html.hud-side .hud-song,
html.hud-side .health-pause-hint { display: none; }

html.hud-side .highway-perspective { transform: translateY(-84px); }

.hud-side #lyricRibbon,
#lyricRibbon {
    position: fixed;
    top: calc(var(--hit-mid, 70%) + 62px);
    bottom: auto;
    left: 50%;
    transform: translateX(-50%);
    width: min(900px, 92vw);
    z-index: 65;
    text-align: center;
}

#lyricCurrent { font-size: 26px; line-height: 1.2; }
#lyricNext { font-size: 14px; margin-top: 2px; opacity: .75; }

#lyricSection {
    position: static;
    display: block;
    text-align: center;
    margin: 0 0 2px;
    font-family: "IBM Plex Mono", ui-monospace, monospace;
    font-size: 10px;
    letter-spacing: .18em;
    text-transform: uppercase;
    opacity: .5;
}

.hit-feedback {
    position: absolute !important;
    left: 50% !important;
    bottom: 26% !important;
    top: auto !important;
    right: auto !important;
    transform: translateX(-50%) !important;
    z-index: 75;
    pointer-events: none;
}

@media (max-width: 1100px) {
    .game-hud, .play-dock { position: static; }
}

html.hud-side .score-section { text-align: left; }
html.hud-side .score-section .multiplier-display {
    font-size: 40px !important;
    font-weight: 900;
    line-height: 1;
    margin-top: 6px;
    color: #FFD700;
    text-shadow: 0 0 20px rgba(255, 215, 0, .55);
}
html.hud-side .score-section .streak-count {
    font-size: 11px !important;
    letter-spacing: .12em;
    opacity: .65;
    margin-top: 1px;
}
html.hud-side .score-value { font-size: 32px !important; }

#lyricCurrent { color: rgba(232, 237, 245, .5); }
.lyr-done { color: rgba(232, 237, 245, .68); }

html.hud-deck.hud-side .game-hud {
    top: auto;
    bottom: calc(100vh - var(--eq-top, 70%) + 6px);
    transform: none;
    left: var(--eq-left-l, 0px);
    width: var(--eq-left-w, 240px);
}
html.hud-deck.hud-side .play-dock {
    top: auto;
    bottom: calc(100vh - var(--eq-top, 70%) + 6px);
    transform: none;
    left: var(--eq-right-l, 70%);
    width: var(--eq-right-w, 240px);
}

html.hud-deck.hud-side .score-section,
html.hud-deck.hud-side .focus-meter,
html.hud-deck.hud-side .health-section {
    margin: 0 10px 6px;
    border-radius: 12px 12px 4px 4px;
    border-bottom-color: rgba(255, 215, 0, .40);
}

/* Deck mode used to drop the particles to 18% and the bars to 55%, presumably so
   they would not compete with the HUD chips. But the chips sit ABOVE the bars in
   the gutter, never on them, and particles render behind the highway at z-index 0 -
   so nothing was actually being protected. Since particles, bloom and the bars are
   the only three channels this mode routes music reactivity into (backgrounds and
   camera deliberately stay fixed), dimming two of them to near-nothing left the
   gameplay flat. */
html.hud-deck canvas.fx-particles { opacity: .62; }
html.hud-deck .fx-eq { opacity: .85; }

html.hud-side .score-section {
    display: flex;
    align-items: baseline;
    gap: 10px;
    white-space: nowrap;
}
html.hud-side .score-section .score-label { font-size: 9px !important; letter-spacing: .14em; }
html.hud-side .score-section .score-value { margin-right: 2px; }
html.hud-side .score-section .multiplier-display { margin-top: 0 !important; }
html.hud-side .score-section .streak-count { margin: 0 !important; }
html.hud-deck.hud-side .focus-meter { text-align: center; }
html.hud-deck.hud-side .health-percentage { font-size: 15px !important; opacity: .9; }

/* Out-of-focus used to drain the WHOLE playfield (saturate .25 / brightness .78),
   notes and keys included. No Fail is only ever on for someone who is struggling -
   a student, or a player who just failed - so dimming the very thing they are
   trying to read made recovery harder for the people the mode exists to protect.
   The decor dims; the notes and keys stay exactly as bright as they were. */
.highway-perspective.focus-drained .track,
.highway-perspective.focus-drained .fret-lines {
    filter: saturate(.3) brightness(.7);
}

.highway-perspective .track,
.highway-perspective .fret-lines {
    transition: filter .45s ease;
}

/* The signal moves to the frame instead: a warm edge that says "penalised"
   without touching readability. */
.highway-perspective {
    position: relative;
}

.highway-perspective::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    transition: opacity .45s ease;
    box-shadow: inset 0 0 90px 10px rgba(224, 60, 60, 0.28);
}

.highway-perspective.focus-drained::after {
    opacity: 1;
}

/* Persistent, quiet, and it tells you how to get out. */
/* Was bottom:96px, which put it straight over the lyric ribbon and, at z-index
   130 against the ribbon's 65, won. The words are continuous and this chip is
   transient, so the chip moves. Top centre is clear of the score box, the notes'
   landing zone and the ribbon alike. */
.focus-locked {
    position: fixed;
    left: 50%;
    top: 74px;
    bottom: auto;
    transform: translateX(-50%);
    z-index: 130;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 14px;
    border-radius: 999px;
    border: 1px solid rgba(224, 60, 60, 0.45);
    background: rgba(6, 10, 16, 0.88);
    color: var(--sh-text, #F1F5FB);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.14em;
    pointer-events: none;
    white-space: nowrap;
}

.focus-locked[hidden] {
    display: none !important;
}

.focus-locked b {
    color: #FF9E4A;
    font-variant-numeric: tabular-nums;
}

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

    .highway-perspective .track,
    .highway-perspective .fret-lines,
    .highway-perspective::after {
        transition: none;
    }
}

.focus-flash {
    position: fixed;
    left: 50%;
    top: 22%;
    transform: translateX(-50%);
    z-index: 90;
    padding: 10px 20px;
    border-radius: 10px;
    font: 600 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
    letter-spacing: .06em;
    color: #FFE9A8;
    background: rgba(18, 14, 6, .88);
    border: 1px solid rgba(255, 210, 90, .45);
    box-shadow: 0 10px 30px -12px rgba(0, 0, 0, .8);
    pointer-events: none;
    animation: focusFlash 1.6s ease forwards;
}
@keyframes focusFlash {
    0%   { opacity: 0; transform: translate(-50%, 8px); }
    12%  { opacity: 1; transform: translate(-50%, 0); }
    75%  { opacity: 1; }
    100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .focus-flash { animation: none; opacity: 1; }
    .highway-perspective, .highway-perspective.focus-drained { transition: none; }
}

#focusBadge {
    font: 600 10px/1 "IBM Plex Mono", ui-monospace, monospace;
    letter-spacing: .14em;
    color: #FFB4A2;
    border: 1px solid rgba(255, 120, 90, .5);
    border-radius: 3px;
    padding: 2px 7px;
    margin-left: 8px;
}

.start-header__iron {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border: 1px solid rgba(255, 120, 90, .35);
    border-radius: 999px;
    font: 600 11px/1 "IBM Plex Mono", ui-monospace, monospace;
    letter-spacing: .12em;
    color: #FFB4A2;
    cursor: pointer;
    user-select: none;
}
.start-header__iron input { accent-color: #FF7A5A; margin: 0; cursor: pointer; }
.start-header__iron:hover { filter: brightness(1.15); }
.start-header__iron:focus-within { outline: 2px solid #FF7A5A; outline-offset: 2px; }

/* A school session sees the setting its teacher chose and cannot move it. It stays
   legible rather than greyed to unreadable: the state is information the student
   needs, it is only the control that is unavailable. */
.start-header__iron.is-locked,
.start-header__iron.is-locked input { cursor: not-allowed; }
.start-header__iron.is-locked { border-style: dashed; opacity: .82; }
.start-header__iron.is-locked:hover { filter: none; }

.stage-tabs { position: relative; scroll-behavior: smooth; scroll-snap-type: x proximity; }
.stage-tab { scroll-snap-align: start; }

.stage-tabs::-webkit-scrollbar { display: block !important; height: 6px; }
.stage-tabs::-webkit-scrollbar-track { background: transparent; }
.stage-tabs::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, .22);
    border-radius: 3px;
}
.stage-tabs::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, .38); }
.stage-tabs { scrollbar-width: thin !important; scrollbar-color: rgba(255,255,255,.22) transparent; }

.stage-rail { position: relative; }
.stage-rail__edge {
    position: absolute;
    top: 0;
    bottom: 8px;
    width: 56px;
    pointer-events: none;
    opacity: 0;
    transition: opacity .18s ease;
    z-index: 2;
}
.stage-rail__edge--l { left: 0;  background: linear-gradient(90deg, var(--rhythm-bg, #0b0f14) 12%, transparent); }
.stage-rail__edge--r { right: 0; background: linear-gradient(270deg, var(--rhythm-bg, #0b0f14) 12%, transparent); }
.stage-rail.has-left  .stage-rail__edge--l,
.stage-rail.has-right .stage-rail__edge--r { opacity: 1; }

.stage-rail__arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, .22);
    background: rgba(12, 16, 22, .92);
    color: #E8EDF2;
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    display: none;
    align-items: center;
    justify-content: center;
}
.stage-rail__arrow:hover { background: rgba(30, 38, 48, .96); }
.stage-rail__arrow:focus-visible { outline: 2px solid #E2A33F; outline-offset: 2px; }
.stage-rail__arrow--l { left: 2px; }
.stage-rail__arrow--r { right: 2px; }
.stage-rail.has-left  .stage-rail__arrow--l,
.stage-rail.has-right .stage-rail__arrow--r { display: flex; }

.catalogue-bar {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem 1.5rem;
    flex-wrap: wrap;
    padding: 0.5rem 0.25rem 0.75rem;
    border-bottom: 1px solid var(--sh-line);
    margin-bottom: 0.5rem;
}

/* The rhythm shell is always dark, whatever theme the user has chosen, so this
   bar takes the shell palette. Theme tokens here render dark-on-dark for anyone
   on a light theme. */
.catalogue-bar__title {
    margin: 0;
    font-size: 1.35rem;
    letter-spacing: 0.08em;
    color: var(--sh-text);
}

.catalogue-bar__sub {
    margin: 0.15rem 0 0;
    font-size: 0.8rem;
    letter-spacing: 0.04em;
    color: var(--sh-dim);
}

.catalogue-door {
    flex: 0 0 auto;
    padding: 0.55rem 1.1rem;
    border: 1px solid var(--sh-line-2);
    border-radius: 999px;
    background: var(--sh-card);
    color: var(--sh-dim);
    font: inherit;
    font-size: 0.82rem;
    letter-spacing: 0.05em;
    cursor: pointer;
    transition: filter 0.15s ease, border-color 0.15s ease;
}

.catalogue-door:hover {
    filter: brightness(1.25);
    border-color: var(--sh-gold);
    color: var(--sh-text);
}

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

.stage-tab--all {
    color: var(--sh-dim);
}

@media (max-width: 640px) {
    .catalogue-bar { align-items: flex-start; }
    .catalogue-door { width: 100%; text-align: center; }
}

/* Focus offered on the failure screen. Deliberately quiet: it sits under the
   message as an option, not a prompt, so a player who wants the classic run can
   ignore it and press R. */
.gameover-focus-offer {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    max-width: 30rem;
    margin: 0 auto 1.1rem;
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--sh-line-2);
    border-radius: var(--sh-radius-sm);
    background: var(--sh-card);
    color: var(--sh-dim);
    font-size: 0.82rem;
    line-height: 1.45;
    text-align: left;
    cursor: pointer;
}

.gameover-focus-offer input {
    margin-top: 0.15rem;
    flex: none;
    cursor: pointer;
}

.gameover-focus-offer strong {
    display: block;
    color: var(--sh-text);
    font-size: 0.9rem;
    margin-bottom: 0.15rem;
}

.gameover-focus-offer:hover {
    border-color: var(--sh-gold);
}

.gameover-focus-offer.is-on {
    border-color: var(--sh-gold);
    color: var(--sh-text);
}

/* Instant tooltip. The native title attribute has a ~1s delay the browser owns
   and CSS cannot shorten, so anything that should feel immediate uses data-tip.
   Shows on hover AND on keyboard focus, so it is not mouse-only. */
[data-tip] {
    position: relative;
}

[data-tip]::after,
[data-tip]::before {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    position: absolute;
    z-index: 80;
    transition: opacity 90ms ease;
}

[data-tip]::after {
    content: attr(data-tip);
    top: calc(100% + 9px);
    left: 50%;
    transform: translateX(-50%);
    width: max-content;
    max-width: 17rem;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--sh-line-2);
    border-radius: var(--sh-radius-sm);
    background: var(--sh-panel);
    color: var(--sh-text);
    font: 400 0.72rem/1.4 inherit;
    letter-spacing: 0.01em;
    text-align: left;
    text-transform: none;
    white-space: normal;
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.55);
}

/* the little pointer */
[data-tip]::before {
    content: "";
    top: calc(100% + 3px);
    left: 50%;
    transform: translateX(-50%);
    border: 6px solid transparent;
    border-bottom-color: var(--sh-line-2);
}

[data-tip]:hover::after,
[data-tip]:hover::before,
[data-tip]:focus-visible::after,
[data-tip]:focus-visible::before,
[data-tip]:focus-within::after,
[data-tip]:focus-within::before {
    opacity: 1;
    visibility: visible;
}

/* Tooltips near the right edge would overflow the viewport centred. */
[data-tip][data-tip-align="right"]::after {
    left: auto;
    right: 0;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    [data-tip]::after,
    [data-tip]::before { transition: none; }
}

/* Text for screen readers only. The visual tooltip is a pseudo-element, which
   assistive tech does not reliably announce, so the description lives here too. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---- deck HUD corrections ------------------------------------------------ */

/* The deck sized .game-hud to the visualizer bar beneath it, but the score line is
   white-space:nowrap and needs ~322px. Anything narrower spilled the text straight
   out of its box. Let the dock take the width it needs, with the bar's width only
   as a floor, and cap it so it can never crowd the highway. */
/* Bounded by the playfield, not a guessed pixel width. --track-left / --track-right
   are measured from the real hit-keys element every frame (rhythm_hud_v2.js), so the
   dock can take all the gutter it has and not one pixel of the lanes. A fixed 380px
   cap overlapped the lanes at 1440. */
html.hud-deck.hud-side .game-hud {
    width: auto;
    min-width: var(--eq-left-w, 240px);
    max-width: calc(var(--track-left, 240px) - var(--eq-left-l, 16px) - 14px);
}

html.hud-deck.hud-side .play-dock {
    width: auto;
    min-width: var(--eq-right-w, 240px);
    max-width: calc(100vw - var(--track-right, 1200px) - (100vw - var(--eq-right-l, 1160px) - var(--eq-right-w, 240px)) - 14px);
}

/* The right dock is anchored from the left, so a wider box would grow off-screen. */
html.hud-deck.hud-side .play-dock {
    left: auto;
    right: calc(100vw - var(--eq-right-l, 70%) - var(--eq-right-w, 240px));
}

/* The dock's own 20px side padding was the last few pixels of the squeeze. */
html.hud-deck.hud-side .game-hud {
    padding-left: 8px;
    padding-right: 8px;
}

/* Wrap rather than clip. The dock is wide enough for one line at any normal size,
   but if it is ever squeezed the score drops to a second line instead of having its
   last characters cut off. */
html.hud-deck.hud-side .score-section {
    justify-content: flex-start;
    flex-wrap: wrap;
    white-space: normal;
    row-gap: 2px;
}

/* Last resort on a genuinely tiny dock: drop the streak rather than clip the score. */
@media (max-width: 900px) {

    html.hud-side .score-section .streak-count {
        display: none;
    }
}

/* The song title was hidden outright in the side layout. It is the only thing on
   screen naming what you are playing, so it moves to the top-right corner instead,
   clear of both docks. */
html.hud-side .hud-song {
    display: block;
    position: fixed;
    top: 14px;
    right: 18px;
    left: auto;
    max-width: min(38vw, 340px);
    text-align: right;
    pointer-events: none;
    z-index: 120;
}

/* The fill sat on the same 6px line as the groove but bloomed a 14px glow and a
   12px playhead past it, so it read as floating above the track rather than
   running inside it. Keep the marker, lose the bloom. */
.progress-container::before {
    border-radius: 3px;
}

.progress-bar {
    border-radius: 3px;
    box-shadow: 0 0 6px rgba(226, 74, 139, 0.55);
    box-shadow: 0 0 6px color-mix(in srgb, var(--song-accent) 55%, transparent);
}

.progress-bar::after {
    top: -2px;
    height: 10px;
    width: 2px;
    border-radius: 1px;
    box-shadow: 0 0 6px #fff;
}

/* ---- legibility on the bright half of the ramp -------------------------- */

/* The key and note letters are #F7F4EB on a 12-lane gradient that runs from deep
   blue to lime and yellow. On lanes 2-7 that measured 1.29-1.51:1 with up to 90%
   of the glyph area below threshold - the letters you most need to read were the
   least readable. The existing text-shadow is a blur, which cannot rescue white on
   yellow; a hard stroke can, and it costs nothing on the dark lanes. */
.key-top,
.note-key {
    -webkit-text-stroke: 0.6px rgba(6, 10, 16, 0.92);
    paint-order: stroke fill;
    text-shadow:
        0 1px 0 rgba(6, 10, 16, 0.55),
        0 2px 5px rgba(7, 12, 18, 0.75);
}

/* ---- results screen ----------------------------------------------------- */

/* White on a light accent-mix measured 1.69:1, and its keyboard hint 2.05:1 with
   every pixel below threshold. The fill is light, so the text has to be dark. */
.next-song-button {
    color: var(--sh-on-light) !important;
}

.next-song-button .keyboard-hint {
    color: rgba(11, 17, 25, 0.72) !important;
}

/* ---- keyboard hints ----------------------------------------------------- */

/* 0.56 alpha at ~10px over control-tinted chips measured 2.05-3.83:1 across every
   modal - the smallest type on screen was also the faintest. */
.keyboard-hint,
.pause-modal .keyboard-hint,
.unlock-modal .keyboard-hint,
.stats-screen .keyboard-hint {
    color: rgba(226, 235, 248, 0.82);
}

/* #hudSong now lives on <body> so its position:fixed resolves against the viewport
   rather than the transformed .game-hud. The cost is that hiding .game-container no
   longer hides it, and updateHudSong() un-hides it whenever currentBeatmapFilename
   is still set - which it is after a song ends. So the song chip followed the player
   back to the road and sat over the No Fail toggle.
   Tied to the body class instead of to a call order that can be re-entered. */
body.rhythm-mode:not(.gameplay) #hudSong {
    display: none !important;
}

/* ---- figures ------------------------------------------------------------- */

/* Every live value in the mode inherited Georgia, whose figures are old-style:
   3, 4, 5, 7 and 9 sit below the baseline and 6 and 8 above it. A multiplier and
   a streak are read mid-song at a glance, a timer is scanned, and a daily count
   is a number before it is a sentence. None of them are prose, so none of them
   want a prose face. Grepping the stylesheets for this finds nothing, because
   these elements never set a family - they inherit one. */
body.rhythm-mode #healthPercentage,
body.rhythm-mode #multiplier,
body.rhythm-mode #streak,
body.rhythm-mode .rhythm-daily-limit-used,
body.rhythm-mode .rhythm-daily-limit-total,
body.rhythm-mode .rhythm-daily-limit-remaining-count,
body.rhythm-mode #scriptureCurrentTime,
body.rhythm-mode #scriptureDuration,
body.rhythm-mode .health-percentage,
body.rhythm-mode .multiplier-value,
body.rhythm-mode .streak-value {
    font-family: var(--font-sans);
    font-variant-numeric: lining-nums tabular-nums;
}
