/* Rhythm Mode gameplay layer. See docs/rhythm-mode-gameplay-layer.md */
#lyricRibbon {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 6px;
    width: min(1100px, 96%);
    text-align: center;
    pointer-events: none;
    z-index: 60;
    opacity: 0;
    transition: opacity .25s ease;
    font: 600 26px/1.35 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
#lyricRibbon.on { opacity: 1; }

#lyricSection {
    font: 600 11px/1 system-ui, sans-serif;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: rgba(232, 237, 245, .40);
    margin-bottom: 5px;
}

/* Chart lyrics are stored lower-case throughout, which put "lord", "god" and
   "jesus" on screen that way in 52 of the 63 songs. Casing is a presentation
   choice, not a data migration: setting it here covers the whole catalogue and
   every song added later, and matches the mode's other type, which is uppercase
   throughout. */
#lyricCurrent,
#lyricNext,
.note-syl,
.hold-grab-syl {
    text-transform: uppercase;
}

/* Caps set tighter than lower-case at the same tracking. */
#lyricCurrent,
#lyricNext { letter-spacing: .03em; }

#lyricCurrent { color: rgba(232, 237, 245, .42); text-shadow: 0 2px 14px rgba(0, 0, 0, .85); }
#lyricNext {
    margin-top: 5px;
    font-size: 17px;
    color: rgba(232, 237, 245, .20);
    text-shadow: 0 2px 10px rgba(0, 0, 0, .8);
}

.lyr { transition: color .09s linear, text-shadow .09s linear; }
.lyr-done { color: rgba(232, 237, 245, .60); }
.lyr-on {
    color: #FFD700;
    text-shadow: 0 0 18px rgba(255, 215, 0, .55), 0 2px 14px rgba(0, 0, 0, .9);
}

.lyr-hold { border-bottom: 2px solid rgba(255, 215, 0, .45); padding-bottom: 1px; }
.lyr-on.lyr-hold { border-bottom-color: #FFD700; }

.note-syl {
    position: absolute;
    left: 50%;
    top: -19px;
    transform: translateX(-50%);
    font: 700 12px/1 system-ui, sans-serif;
    color: #e8edf5;
    background: rgba(10, 13, 18, .82);
    border: 1px solid rgba(255, 215, 0, .35);
    border-radius: 9px;
    padding: 3px 7px;
    white-space: nowrap;
    pointer-events: none;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}
.note.note-hold .note-syl { border-color: #FFD700; box-shadow: 0 0 10px rgba(255, 215, 0, .35); }

.note-syl { font-size: 14px; }

.note-syl { max-width: 190px; overflow: hidden; text-overflow: ellipsis; }

.note.note-tie::before {
    content: "";
    position: absolute;
    bottom: calc(100% + 2px);
    left: 50%;
    transform: translateX(-50%);
    width: 2px; height: 9px;
    background: rgba(255, 215, 0, .5);
    border-radius: 1px;
}

.note.note-hold { box-shadow: 0 0 0 2px rgba(255, 215, 0, .85), 0 0 18px rgba(255, 215, 0, .45); }

#holdLayer { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.hold-tail {
    position: absolute;
    top: 0;
    border-radius: 5px 5px 0 0;
    background: linear-gradient(180deg, rgba(255,215,0,.10) 0%, rgba(255,215,0,.45) 100%);
    border: 1px solid rgba(255,215,0,.45);
    overflow: hidden;
    -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 12%);
    mask-image: linear-gradient(180deg, transparent 0, #000 12%);
    will-change: transform, height;
}
.hold-tail.tail-holding {
    background: linear-gradient(180deg, rgba(255,215,0,.35) 0%, rgba(255,215,0,.85) 100%);
    border-color: #FFD700;
    box-shadow: 0 0 22px rgba(255,215,0,.55);
}

/* Three strands running down the inside of a held tail. A solid bar says the note
   is long; strands moving through it say the key is down RIGHT NOW, which is the
   one thing a bar that only shortens cannot say. Anchored to the bottom, because
   the bottom is pinned to the hit line while the top shortens every frame - hang
   them off the top and the whole pattern crawls as the tail drains. */
.hold-tail::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(100% + 48px);
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='28' height='48' viewBox='0 0 28 48' preserveAspectRatio='none'><g fill='none' stroke-linecap='round'><path d='M7 0 C 3.7 16 10.3 32 7 48' stroke='rgba(255,246,205,0.72)' stroke-width='1.9'/><path d='M14 0 C 17.3 16 10.7 32 14 48' stroke='rgba(255,253,242,0.95)' stroke-width='2.3'/><path d='M21 0 C 17.7 16 24.3 32 21 48' stroke='rgba(255,246,205,0.72)' stroke-width='1.9'/></g></svg>");
    background-repeat: repeat-y;
    background-position: center bottom;
    background-size: 100% 48px;
    opacity: 0;
    pointer-events: none;
    transition: opacity .1s ease-out;
}
.hold-tail.tail-holding::before {
    opacity: .9;
    animation: holdFlowStrands .45s linear infinite;
}
/* One tile per cycle, so the loop has no seam. */
@keyframes holdFlowStrands {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(0, 48px, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .hold-tail.tail-holding::before { animation: none; opacity: .55; }
}
/* A tail that is over plays itself out instead of vanishing on the next frame.
   Only opacity, colour and shadow may animate here: the element carries its
   position in `transform`, so a transform keyframe would fling it off the lane. */
.hold-tail.tail-dropped {
    background: rgba(150,160,178,.38);
    border-color: rgba(190,200,215,.6);
    box-shadow: none;
    animation: holdTailDrop .46s ease-out forwards;
}
.hold-tail.tail-dropped::before { opacity: 0; }
/* Grey first, gone second. A tail that only fades is read as a tail that ended,
   which is the one thing a drop is not. */
@keyframes holdTailDrop {
    0%   { opacity: 1;  background-color: rgba(214,221,233,.6); border-color: rgba(226,232,241,.85); }
    30%  { opacity: .85; background-color: rgba(150,160,178,.4); }
    100% { opacity: 0;  background-color: rgba(120,130,145,.2); }
}

.hold-tail.tail-held {
    background: linear-gradient(180deg, rgba(255,255,255,.55) 0%, rgba(255,236,150,.95) 100%);
    border-color: #FFF6D0;
    animation: holdTailHeld .16s ease-out forwards;
}
@keyframes holdTailHeld {
    0%   { opacity: 1; box-shadow: 0 0 22px rgba(255,215,0,.55); }
    100% { opacity: 0; box-shadow: 0 0 54px 14px rgba(255,255,255,.7); }
}

/* Kept above the hit zone, which `#holdLayer` at z-index 3 can never be. */
#holdTopLayer { position: absolute; inset: 0; pointer-events: none; z-index: 60; }

/* The pump on a completed hold: a white flash on the key, then a ring off it.
   Front-loaded on purpose. This is the cue to GET OFF THE KEY, and the player's
   next note can be 200 ms away, so the brightest instant is frame one and the
   whole thing is over in a third of a second. A bloom that ramps up reads as
   "still holding" for exactly as long as it takes to ramp. */
.hold-charge {
    position: absolute;
    top: 0; left: 0;
    border-radius: 12px;
    pointer-events: none;
    animation: holdCharge .34s cubic-bezier(.15, .85, .3, 1) forwards;
}
@keyframes holdCharge {
    0%   { background: rgba(255,255,255,.9);
           box-shadow: 0 0 0 3px rgba(255,255,255,1), 0 0 34px 10px rgba(255,238,170,.95);
           opacity: 1; }
    18%  { background: rgba(255,246,200,.35);
           box-shadow: 0 0 0 8px rgba(255,215,0,.5), 0 0 48px 14px rgba(255,215,0,.6);
           opacity: 1; }
    60%  { background: rgba(255,215,0,0);
           box-shadow: 0 0 0 16px rgba(255,215,0,.14), 0 0 56px 18px rgba(255,215,0,.25);
           opacity: .6; }
    100% { background: rgba(255,215,0,0);
           box-shadow: 0 0 0 24px rgba(255,215,0,0), 0 0 60px 20px rgba(255,215,0,0);
           opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .hold-charge { animation-duration: .18s; }
}

.lyr-unplayed { opacity: .55; }
.lyr-unplayed.lyr-on { opacity: 1; }

.hold-grab {
    position: absolute;
    top: 0; left: 0;
    /* Width comes from the syllable inside it, between the min and max the layer
       sets per frame. A fixed width tied to the key clipped the word on a phone. */
    width: max-content;
    padding: 0 9px;
    border-radius: 10px;
    background: radial-gradient(circle at 50% 40%, #FFF4C2 0%, #FFD700 55%, #E8B800 100%);
    border: 2px solid #fff;
    box-shadow: 0 0 0 4px rgba(255,215,0,.35), 0 0 34px 8px rgba(255,215,0,.65);
    display: flex; align-items: center; justify-content: center;
    overflow: hidden;
    pointer-events: none;
    z-index: 8;
    animation: holdBreathe .9s ease-in-out infinite;
}

.hold-grab-syl {
    position: relative; z-index: 2;
    font: 800 14px/1 system-ui, sans-serif;
    color: #0d1016;
    text-shadow: 0 1px 0 rgba(255,255,255,.55);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.hold-grab-fill {
    position: absolute; left: 0; right: 0; top: 0;
    background: rgba(10,13,18,.55);
    z-index: 1;
}
@keyframes holdBreathe {
    0%,100% { box-shadow: 0 0 0 4px rgba(255,215,0,.30), 0 0 30px 6px rgba(255,215,0,.55); }
    50%     { box-shadow: 0 0 0 7px rgba(255,215,0,.45), 0 0 44px 12px rgba(255,215,0,.80); }
}

.hold-grab.pulse { animation: holdPulse .16s ease-out; }
@keyframes holdPulse {
    0%   { box-shadow: 0 0 0 4px rgba(255,255,255,.95), 0 0 40px 10px rgba(255,215,0,.9); }
    100% { box-shadow: 0 0 0 4px rgba(255,215,0,.35),  0 0 34px 8px rgba(255,215,0,.65); }
}

.hit-key.holding {
    box-shadow: 0 0 0 3px rgba(255,215,0,.8), 0 0 26px 6px rgba(255,215,0,.6) !important;
    filter: brightness(1.5);
}

.note-syl .note-key-letter {
    color: #FFD700;
    text-decoration-color: #FFD700;
    text-underline-offset: 2px;
    font-weight: 800;
}

.note.note-shifted { transform-origin: 50% 50%; }
