/* ============================================================
   lyric-lines.css — lyrics as timed lines (issue #72)
   One element per line so a player can follow them. Two surfaces
   render the same markup — the song panel (staff + hub, styled in
   features/dashboards/songs/songs.css) and the public song page
   (css/portfolio.css) — so what is *about the lines themselves*
   lives here in the global layer, and what is about the container
   they sit in stays with each surface. Each surface sets its own
   `max-height` on the scrolling state; everything else is shared.

   The rhythm has to read the same as the paragraphs it replaced:
   lines sit tight, and the space comes back where the writer left
   a blank one.
   ============================================================ */
.song-lyrics-content--lines {
    /* The highlight is a rounded fill, so it needs room to either side of the
       words. The block starts that much further left and each line pays it
       back as padding, which keeps the text itself aligned with the heading
       above it. */
    margin-left: -0.55em;
    margin-right: -0.55em;
}

.song-lyrics-content--lines p {
    margin: 0;
    padding: 0 0.55em;
}

.song-lyrics-line--gap {
    margin-top: 1em !important;
}

.song-lyrics-section {
    color: var(--text-muted);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
}

/* Clickable only while the timings apply to what is playing — the JS keeps
   `data-synced` honest as the listener switches version. */
.song-lyrics-content--lines[data-synced="1"] .song-lyrics-line {
    cursor: pointer;
    border-radius: 6px;
    transition: background var(--dur-fast) var(--ease-soft),
                color var(--dur-fast) var(--ease-soft);
}

.song-lyrics-content--lines[data-synced="1"] .song-lyrics-line:hover {
    background: var(--tint-soft);
}

.song-lyrics-content--lines[data-synced="1"] .song-lyrics-line:focus-visible {
    outline: var(--focus-outline);
    outline-offset: 2px;
}

/* A fill, not coloured body text: `--accent-strong` is coral-500 and fails AA
   at this size (design-language §1). The tint carries the emphasis and the
   words stay readable. */
.song-lyrics-line.is-playing {
    background: var(--tint-20);
    color: var(--link);
    font-weight: 600;
}

/* While the words are following the audio they become a window that scrolls
   with them rather than a block that runs past the viewport. How tall that
   window is belongs to the surface, so only the mechanics are here. */
/* No scroll fade (#201, owner 2026-08-31 on the fullscreen sheet): these
   words are being read while they play, and the line at the edge is as
   likely to be the one being sung as any other. Fullscreen already drops
   the panel's own fade for the same reason — everything that is not the
   words steps out of the way. */
.song-lyrics-content--lines[data-synced="1"] {
    overflow-y: auto;
    scroll-behavior: smooth;
    scrollbar-width: thin;
    overscroll-behavior: contain;
}

@media (prefers-reduced-motion: reduce) {
    .song-lyrics-content--lines[data-synced="1"] {
        scroll-behavior: auto;
    }

    .song-lyrics-content--lines[data-synced="1"] .song-lyrics-line {
        transition: none;
    }
}

/* An approved line (#184) — these exact words are final.

   **Only in fullscreen** (owner, 2026-08-31). In the panel preview a column of
   green bars was noise over something meant to be read as a lyric, so approval
   lives behind the panel's `view approval status` button, which opens this same
   block full-height. `.song-lyrics-panel--stage` is that state (songs.css), and
   scoping to it is enough because fullscreen here is the panel itself rather
   than a second surface — the DOM never moves, so nothing has to be rendered
   twice. The public page and the preview card are never given approvals at all
   (`docs/lyrics/annotations.md` §5), so nothing there can match this either.

   Light, and a fill only: no rule down the left edge. `--success` is a fill for
   the same reason `--accent-strong` is on `.is-playing` — it fails AA as body
   text at this size (design-language §1). `.is-playing` still outranks it, so a
   line that is both reads as playing: the live state over the standing one. */
.song-lyrics-panel--stage .song-lyrics-line--approved {
    background: color-mix(in srgb, var(--success) 7%, transparent);
}

/* Lines sit tight in fullscreen, the way they do in the studio's list — which
   is what lets a run of approved ones read as one block instead of a stack of
   bars with white between them.

   `songs.css` puts `margin: 0 0 1em` on every paragraph in the panel and wins
   on source order over this file's `margin: 0`, so this is scoped to the staged
   panel rather than fought over globally: the preview keeps the spacing it has
   always had. The stanza break and the section headers below are what carry the
   structure once the per-line margin is gone. */
.song-lyrics-panel--stage .song-lyrics-content--lines p {
    margin-bottom: 0;
}

.song-lyrics-panel--stage .song-lyrics-section {
    margin-top: 1.6em;
}

.song-lyrics-panel--stage .song-lyrics-content--lines > .song-lyrics-section:first-child {
    margin-top: 0;
}

/* A run of approved lines is one block, not a stack of bars: square by default,
   and the corners come back only at the two ends of a run.

   A stanza break ends a run as well as a non-approved line does — `--gap` puts
   real space above that line, and rounding across the space would draw one
   block with a hole in it. A section header ends a run by simply not being an
   approved sibling. */
.song-lyrics-panel--stage .song-lyrics-line--approved:not(.song-lyrics-line--approved + .song-lyrics-line--approved),
.song-lyrics-panel--stage .song-lyrics-line--approved.song-lyrics-line--gap {
    border-top-left-radius: 6px;
    border-top-right-radius: 6px;
}

.song-lyrics-panel--stage .song-lyrics-line--approved:not(:has(+ .song-lyrics-line--approved)),
.song-lyrics-panel--stage .song-lyrics-line--approved:has(+ .song-lyrics-line--gap) {
    border-bottom-left-radius: 6px;
    border-bottom-right-radius: 6px;
}

/* ── a note about a passage (#185) ────────────────────────────────────────
   The Genius gesture: the lines somebody wrote about are highlighted as one
   block, and the note opens from a dot at the end of it.

   Two differences forced by what is already here. The passage cannot open by
   clicking the words, because on a synced page a line is already a seek
   control — hence the dot, which is also the only version of this a keyboard
   or a thumb can reach. And the run boundaries are classes from the server,
   not `+` selectors: two different notes on adjacent lines would otherwise
   draw as one passage.

   Shared by all three surfaces — the song panel, the public song page and the
   preview card (owner, 2026-08-31: a note is written for whoever is reading
   the lyric) — so it lives in the global layer beside the lines themselves. */
.song-lyrics-note-hint {
    margin: 0 0 0.7em;
    font-size: 13px;
    color: var(--text-muted);
}

/* A gradient layer rather than a background colour: `background-color` is
   where an approved line's green lives (above), so as an image the pink lays
   *over* it and a line that is both reads as both. */
.song-lyrics-line--noted {
    background-image: linear-gradient(var(--tint-soft), var(--tint-soft));
}

/* The two ends of the passage get the corners, and a stanza break inside one
   ends the block early — `--gap` puts real space above that line, and rounding
   across the space would draw one block with a hole in it. Same rule the
   approved runs follow, for the same reason. */
.song-lyrics-line--note-first,
.song-lyrics-line--noted.song-lyrics-line--gap {
    border-top-left-radius: 6px;
    border-top-right-radius: 6px;
}

.song-lyrics-line--note-last,
.song-lyrics-line--noted:has(+ .song-lyrics-line--gap) {
    border-bottom-left-radius: 6px;
    border-bottom-right-radius: 6px;
}

/* Sized as a target, drawn as a dot: 1.4em square around an 8px circle is
   ~24px at body size, which is a thumb, while what you see is a full stop that
   happens to be pink. `--link` rather than `--accent` because it sits on the
   passage's own tint, where coral-300 has nothing to hold on to. */
.lyric-note-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.4em;
    height: 1.4em;
    margin-left: 0.15em;
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    background: none;
    color: var(--link);
    vertical-align: middle;
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-soft),
                background var(--dur-fast) var(--ease-soft);
}

.lyric-note-mark svg {
    width: 8px;
    height: 8px;
    fill: currentColor;
    transition: transform var(--dur-fast) var(--ease-soft);
}

.lyric-note-mark:hover {
    color: var(--link-hover);
    background: var(--tint-20);
}

.lyric-note-mark:focus-visible {
    outline: var(--focus-outline);
    outline-offset: 1px;
}

/* Open is a standing state, so the dot says so without being hovered. */
.lyric-note-mark[aria-expanded="true"] {
    background: var(--tint-20);
}

.lyric-note-mark[aria-expanded="true"] svg {
    transform: scale(1.35);
}

/* A `<span>` laid out as a block: it sits inside the last line's `<p>`, which
   no block element may do — and a sibling element between two lines would
   break the passage runs above.

   Drawn on `--paper` rather than a tint, because it sits *inside* the
   highlighted passage: a second wash of the same hue would read as one muddy
   block instead of a note under a marked passage.

   `white-space: normal` is not decoration. `.song-lyrics` sets `pre-line` on
   some surfaces (README §7) and the note already carries its own `<br>`s, so
   without this every blank line in one renders twice. */
.lyric-note {
    display: block;
    /* The card fills the passage; the *words* inside it are what gets a measure
       (`.lyric-note-words`). Capping the card instead left a band of the
       passage's own tint down its right-hand side, which reads as the note
       having failed to fill something rather than as a deliberate width. */
    margin: 0.5em 0 0.7em;
    padding: 0.7em 0.9em;
    border-left: 2px solid var(--accent);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: var(--paper);
    font-size: 15px;
    line-height: var(--lh-body);
    font-weight: 400;
    font-style: normal;
    white-space: normal;
    color: var(--text-soft);
    text-align: left;
    cursor: auto;
}

.lyric-note[hidden] {
    display: none;
}

/* A measure, not a container. The panel is 1100px wide; prose set across that
   is prose nobody finishes a line of. The lyric itself keeps the full width —
   its lines are short by nature. */
.lyric-note-words {
    display: block;
    max-width: 62ch;
}

/* The note is inside a line, so a playing line would otherwise recolour and
   embolden the prose about it along with the words. The highlight is for the
   lyric. */
.song-lyrics-line.is-playing .lyric-note {
    color: var(--text-soft);
    font-weight: 400;
}

@media (prefers-reduced-motion: reduce) {
    .lyric-note-mark,
    .lyric-note-mark svg {
        transition: none;
    }
}

/* Chords over the words (#266) — the fourth mark.

   A charted line is cut into segments by `attach_chords()`, each carrying the
   symbol that lands on its first character. That is what a character offset
   means, and it is the only shape that puts a chord mid-word, which real
   charts do.

   **Every segment carries a `.lyric-chord`, empty ones included.** An empty
   span still takes its line, so the words sit on one baseline whether or not a
   chord happens to start that segment — without it the first segment of a line
   whose chord arrives late would ride up above the rest.

   `pre-wrap` because the spaces *are* the chart: the run of spaces before a
   chord is where it was written, and collapsing them moves the chord.

   **Off until somebody asks** (owner, 2026-09-14). A charted lyric carries a
   `chords` toggle (the kit's quiet pill, `[data-lyric-chords-toggle]`),
   unpressed, and everything that
   makes room for a chord is scoped to the class it sets on the lyric,
   `.song-lyrics-content--chords`. Without it the runs are plain inline text and
   the symbols take no space at all, so the unpressed lyric reads as the words
   did before there was a chart. */
/* The toggle sits over the top-right corner of the words, not on a row of its
   own (owner, 2026-09-14). Where a line does reach under it,
   `lyrics_chords_toggle.js` adds `--clear` and sets `--lyric-tools-clear` to
   the button's height and a gap, and the words step down by that — the one
   case a row was ever needed for. */
.song-lyrics-charted {
    position: relative;
}

.song-lyrics-tools {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 2;
}

/* The public song page's chord tools, held back while its `lyrics.` tab is
   open (#267): `display: flex` below would otherwise outrank `hidden`. */
.song-lyrics-tools[hidden] {
    display: none;
}

.song-lyrics-charted--clear > .song-lyrics-content {
    padding-top: var(--lyric-tools-clear, 2.2em);
}

.lyric-chord {
    display: none;
}

.song-lyrics-content--chords .song-lyrics-line--charted {
    white-space: pre-wrap;
}

.song-lyrics-content--chords .lyric-chord-run {
    display: inline-block;
    vertical-align: bottom;
}

/* A chord on the last beat has no letters of its own, so its run is only the
   symbol — and held to the bottom like the rest it drops onto the words' line
   and reads as a subscript. It hangs from the top instead, level with every
   other chord on the line. */
.song-lyrics-content--chords .lyric-chord-run--end {
    vertical-align: top;
}

/* A row of chords with no words (#266 P6) — an intro, a solo, an outro — drawn
   as it was written (`partials/_chord_row.html`). Shown only where the chords
   are, so a lyric not showing them does not grow a line of empty bars.

   The bar lines are borders rather than `|` characters, so a row that wraps on
   a phone breaks between bars and each piece still reads as bars. The chords
   are the over-the-words chord's face, a size up: here they are the line. */
.song-lyrics-row {
    display: none;
}

.song-lyrics-content--chords .song-lyrics-row {
    display: flex;
    flex-wrap: wrap;
    row-gap: 0.3em;
    margin: 0.25em 0;
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.7;
    letter-spacing: var(--tracking-label);
    text-transform: none;
    color: var(--link);
}

.lyric-bar {
    display: inline-flex;
    gap: 0.9em;
    min-width: 3em;
    padding: 0 0.85em;
    border-left: 1.5px solid var(--text-muted);
}

.lyric-bar:last-child {
    border-right: 1.5px solid var(--text-muted);
}

/* A row written with no bar lines keeps none (owner, 2026-09-15): it does not
   say how long each chord lasts, so it is not drawn as if it did. After the
   bar rules on purpose — same specificity. */
.song-lyrics-row--unbarred .lyric-bar,
.song-lyrics-row--unbarred .lyric-bar:last-child {
    padding-left: 0;
    border: 0;
}

/* Mono, because a chord symbol is notation rather than prose — the same reason
   it is never translated (`docs/attune/README.md` §2) — and a mono micro-label
   is the device the design language already has for this (design-language §3).
   `--link` is coral-800 and AA on white: `--accent-strong` is a fill token and
   fails at this size, which is the rule `.is-playing` follows two blocks up. */
/* `text-transform: none` because a section header is set in capitals, and `Am`
   in capitals is `AM` — a different chord. */
.song-lyrics-content--chords .lyric-chord {
    display: block;
    height: 1.35em;
    color: var(--link);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: var(--tracking-label);
    line-height: 1.35;
    text-transform: none;
    white-space: nowrap;
}

/* A playing line takes the whole run with it, chords included — the live state
   reads as one thing rather than words that highlight under symbols that do
   not. */
.song-lyrics-content--chords .song-lyrics-line.is-playing .lyric-chord {
    color: inherit;
}

/* Key and tempo changes, and chords read as functions (#307).

   **A change is a tag at its letter**, in a lane above the chords that only a
   line carrying a change has (`.song-lyrics-line--changes`), and only while the
   chords show. Every run on that line has the lane, empty or not, so the chords
   keep one baseline. The lane is `width: 0` and the tag hangs out of it, so a
   long "F♯ mixolydian" never prises the letters under it apart.

   **`functions` swaps each chord's symbol for its numeral.** Both are in the
   HTML (`dd_chords`), so pressing it fetches nothing, and the numeral is set in
   the chord's own face: it is the same notation, read another way. */
.lyric-changes,
.lyric-chord--function,
.lyric-row-chord--function {
    display: none;
}

/* The lane's own line box, or the tag sits on the words' taller one and hangs
   down over the chord under it. */
.song-lyrics-content--chords .lyric-changes {
    display: block;
    width: 0;
    height: 19px;
    font-size: 10px;
    line-height: 17px;
    white-space: nowrap;
}

.lyric-change {
    display: inline-block;
    vertical-align: top;
    margin-right: 4px;
    padding: 0 0.5em;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    line-height: 15px;
    letter-spacing: var(--tracking-label);
    text-transform: none;
    color: var(--text-muted);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
}

.song-lyrics-content--chords .lyric-chord--function,
.song-lyrics-content--functions .lyric-chord--symbol,
.song-lyrics-content--functions .lyric-row-chord--symbol {
    display: none;
}

.song-lyrics-content--functions .lyric-chord--function {
    display: block;
}

.song-lyrics-content--functions .lyric-row-chord--function {
    display: inline;
}

/* `chords` first, and under it the rest (#307): `functions` and − / +, in a
   row that opens with the chords. On a song's surfaces the row is a card that
   grows out around the button (owner, 2026-09-17) — at first sight a reader
   sees one button. The card's padding is there shut as well as open, and taken
   back with a negative margin, so the button does not move when it opens. */
.song-lyrics-tools {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
}

.song-lyrics-tools--card {
    margin: -6px;
    padding: 6px;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    transition: background-color var(--dur-base) var(--ease-soft),
                border-color var(--dur-base) var(--ease-soft),
                box-shadow var(--dur-base) var(--ease-soft);
}

.song-lyrics-tools--card.is-open {
    background-color: var(--surface);
    border-color: var(--border);
    box-shadow: var(--shadow-md);
}

.song-lyrics-tools-more {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--dur-base) var(--ease-soft);
}

.song-lyrics-tools.is-open > .song-lyrics-tools-more {
    grid-template-rows: 1fr;
}

.song-lyrics-tools-more-inner {
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 0;
    overflow: hidden;
    opacity: 0;
    transition: opacity var(--dur-base) var(--ease-soft);
}

.song-lyrics-tools.is-open .song-lyrics-tools-more-inner {
    /* Room for the buttons' focus rings, which the clip would cut. */
    padding: 6px 3px 3px;
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .song-lyrics-tools--card,
    .song-lyrics-tools-more,
    .song-lyrics-tools-more-inner {
        transition: none;
    }
}

/* − and + (#307), around the name of the key the chart is being read in. */
.lyric-transpose {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.lyric-transpose-step {
    --dd-btn-pad-x: 0.6em;
    min-width: 2.2em;
}

.lyric-transpose-key {
    min-width: 5.5em;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: var(--tracking-label);
    text-align: center;
    color: var(--text-muted);
    white-space: nowrap;
}

.lyric-transpose.is-shifted .lyric-transpose-key {
    color: var(--link);
}

/* A key line's "+N" (#307) — the hub header, the public song page and an
   article's chart. Opens in the flow, under the line, rather than as a popover
   that a phone would clip. */
.key-changes {
    display: inline;
    margin-left: 0.5em;
}

/* A key and a tempo, each with its own "+N" (owner, 2026-09-17): a row on its
   baseline, so a list opening under one count leaves the rest of the row
   where it was. */
.key-tempo-line,
.song-key-tempo.key-tempo-line {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 0.4em;
    row-gap: 0.2em;
}

.key-tempo-line > .key-changes {
    margin-left: 0;
}

.key-changes-count {
    display: inline-block;
    padding: 0 0.5em;
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--link);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    cursor: pointer;
    list-style: none;
}

.key-changes-count::-webkit-details-marker {
    display: none;
}

.key-changes[open] > .key-changes-count {
    border-color: var(--accent);
}

/* In the hub header's editable line the count joins its chip instead
   (`.song-fact-group`, songs.css). */

.key-changes-count:focus-visible {
    outline: var(--focus-outline);
    outline-offset: 2px;
}

.key-changes-list {
    margin: 0.4em 0 0;
    padding: 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-muted);
    list-style: none;
}

.key-changes-list li::before {
    content: "↳ ";
}

.key-changes-to {
    font-weight: 500;
    color: var(--text);
}

.blog-chords-key {
    margin: 0 0 0.6em;
    font-size: 14px;
    color: var(--text-muted);
}
