body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    /* 19.15 - the page drops away from the cards on it. Everything here
       sat within three points of lightness of everything else, so nothing
       read as raised; darkening the GROUND separates every surface at once
       rather than lifting each of them by hand. */
    background-color: var(--page);
    color: #eeedef;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 26px 16px 60px;
    margin: 0;
}


/* The chord IS the title now. It changes as you move, which is the point.

   THE NAME ALONE ON THE TITLE LINE. It used to read "Cm7 · minor seventh" on
   one line, which made a 34px heading carry two different registers: the
   symbol you read at a glance while playing, and a description you read once
   and then never again. The name is what you are looking at mid-bar, so it
   gets the line to itself and the words move down to sit with the moods,
   which is where the rest of the prose already was. */
#chordLine {
    text-align: center; margin: 0 0 4px;
    font-size: 34px; font-weight: 700; letter-spacing: -.5px;
}
/* 15.16 — THE CHORD CHANGE, ANNOUNCED.
   The title used to be rewritten silently, which is fine when you are looking
   at it and useless when you are looking at your hands -- and looking at your
   hands is the entire premise of focus mode.
   The new name arrives large and faint and settles. TRANSFORM AND OPACITY
   ONLY: D106 established that `filter` is never composited, and while a
   repaint once a bar would survive, there is no reason to spend it. */
#chordLine .cn { display: inline-block; will-change: transform; }
/* D151 - THE NAME GLOWS, in its own colour: the same halo the root wears on
   the board, so the title and the note it names are lit the same way. Two
   layers at rest, and on a change it BLOOMS -- the halo opens wide and
   bright and closes back over most of a second, well after the scale has
   settled, so the change is something you catch out of the corner of your
   eye rather than something you have to be looking at. Text-shadow is
   repainted, not composited, but this is once a chord, not once a frame. */
#chordLine .cn {
    text-shadow: 0 0 10px color-mix(in srgb, currentColor 70%, transparent),
                 0 0 28px color-mix(in srgb, currentColor 42%, transparent),
                 0 0 60px color-mix(in srgb, currentColor 0%, transparent);
}
@keyframes chordGlow {
    0%   { text-shadow: 0 0 6px currentColor,
                        0 0 34px currentColor,
                        0 0 90px color-mix(in srgb, currentColor 70%, transparent); }
    100% { text-shadow: 0 0 10px color-mix(in srgb, currentColor 70%, transparent),
                        0 0 28px color-mix(in srgb, currentColor 42%, transparent),
                        0 0 60px color-mix(in srgb, currentColor 0%, transparent); }
}
#chordLine .cn.arrive {
    /* The easing spreads the travel rather than dumping it in the first
       frames. cubic-bezier(.18,.9,...) reached 90% of the way back in a
       sixth of its own duration, so lengthening the animation would not have
       helped on its own -- the movement has to be spread as well as given
       longer. */
    animation: chordArrive var(--chord-arrive, 200ms) cubic-bezier(.2, .55, .3, 1),
               chordGlow 950ms cubic-bezier(.2, .6, .3, 1);
}
@keyframes chordArrive {
    from { transform: scale(1.22); opacity: .3; }
    to   { transform: scale(1);    opacity: 1; }
}
/* A title that jumps is exactly what someone who asked for less motion asked
   to be spared. The name still changes; it just does not swell. */
@media (prefers-reduced-motion: reduce) {
    #chordLine .cn.arrive { animation: none; }
}

#chordTag {
    text-align: center; margin: 0 0 12px;
    display: flex; flex-direction: column; gap: 1px;
}
#chordTag .ct-full {
    font-size: 14.5px; color: #c7c4cc; letter-spacing: .2px;
}
#chordTag .ct-moods {
    font-size: 13px; color: #8c809c; letter-spacing: .3px;
}

/* A compact, playable copy of the spelling row from the card. Notes only -
   no sound description, no change-one-note, no memory tips. Those stay at
   the bottom where there is room for them. */
/* The key, then the notes it spells. Centred as a pair, so the row still
   reads as one thing rather than as a control with a diagram after it. */
.spell-row {
    display: flex; align-items: center; justify-content: center;
    flex-wrap: wrap; gap: 12px; margin: 0 0 14px;
}
.spell-row #keySelect { margin: 0; min-width: 72px; }

.mini-spell {
    display: flex; align-items: center; justify-content: center;
    flex-wrap: wrap; gap: 2px; margin: 0;
}
.mini-spell .tok { gap: 0; }
/* 40% down from the 52px it was ACTUALLY rendering at. The block used to
   read `.mini-spell .dot`, which has the same specificity as the card's
   `.tok .dot` further down the file and therefore lost to it - so this row
   was silently wearing the card's size and its own numbers did nothing.
   Naming the .tok in between is what makes it bind.

   It is a reminder of the spelling, not the subject of the page; the card at
   the bottom is where you go to actually read it. */
.mini-spell .tok .dot {
    width: 34px; height: 34px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-weight: 500; line-height: 1;
    box-shadow: 0 2px 6px rgba(0,0,0,.4);
    cursor: pointer; transition: transform .08s, box-shadow .08s;
}
.mini-spell .tok .dot:hover { transform: scale(1.08); box-shadow: 0 4px 12px rgba(0,0,0,.55); }
.mini-spell .tok .dot:active { transform: scale(.96); }
.mini-spell .tok .dot.sounding { box-shadow: 0 0 0 2px rgba(255,255,255,.9); }
/* THE SAME DOT AS THE BOARD, IN HTML.                              (11.h)

   The board's is SVG and this one is a div, and they had drifted into two
   different pictures of the same thing: there, the degree straddles the
   bottom rim as a dark glyph in a white halo and the name sits on the
   optical centre; here, both labels were stacked inside the circle and the
   name took a per-pitch ink colour, so half of them read dark and half
   light.

   White for every name now, with a thin black outline doing the work
   noteInk() used to -- which is what lets one colour work on the yellow
   fills and the deep blue ones alike. The degree drops onto the rim and
   inverts, exactly as D53 argued it should over there: black on white,
   because the rim is the surface it mostly sits on.

   The outline is a text-shadow rather than -webkit-text-stroke: a stroke
   eats INTO the glyph and thins it, and the point here was to make the
   letters thinner and larger, not to spend the width twice. */
.mini-spell .tok .dot { position: relative; overflow: visible; }
.mini-spell .tok .dot .n {
    font-size: 14.5px; font-weight: 500; color: #fff; letter-spacing: -.2px;
    text-shadow: 0 1px 0 #000, 0 -1px 0 #000, 1px 0 0 #000, -1px 0 0 #000,
                 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000,
                 -1px 1px 0 #000;
}
.mini-spell .tok .dot .d {
    position: absolute; left: 0; right: 0; bottom: -5px;
    font-size: 9px; font-weight: 600; opacity: .92;
    color: #110d17; letter-spacing: 0;
    text-shadow: 0 1px 0 rgba(255,255,255,.92), 0 -1px 0 rgba(255,255,255,.92),
                 1px 0 0 rgba(255,255,255,.92), -1px 0 0 rgba(255,255,255,.92),
                 1px 1px 0 rgba(255,255,255,.92), -1px -1px 0 rgba(255,255,255,.92),
                 1px -1px 0 rgba(255,255,255,.92), -1px 1px 0 rgba(255,255,255,.92);
}
.mini-spell .gap {
    /* The 4-3-4 shape is the reason the row is worth having, so it has to be
       readable rather than merely present. */
    color: #77688c; font-size: 8px; min-width: 15px; text-align: center;
    margin: 0; font-family: "JetBrains Mono", Consolas, monospace;
}

.panel {
    background-color: #2a2038;
    border: 1px solid #3a2e4b;
    /* Without this, `max-width: 100%` caps the CONTENT box and the 40px of
       horizontal padding is added on top -- so every panel overflowed its
       parent by exactly its own padding. Visible on a phone as the four
       pixels of panel background bleeding off the left edge. */
    box-sizing: border-box;
    padding: 12px 20px;
    border-radius: 8px;
    margin-bottom: 14px;
    display: flex;
    gap: 16px;
    align-items: center;
    flex-wrap: wrap;
    justify-content: center;
    box-shadow: 0 4px 15px rgba(0,0,0,0.45),
                inset 0 1px 0 rgba(255,255,255,.05);
    max-width: 100%;
}

select, button, input[type="range"] {
    padding: 7px 11px; font-size: 15px; border-radius: 6px;
    border: 1px solid #524466; background-color: #312640; color: white;
    cursor: pointer; transition: background 0.2s;
}
/* ====================================================================
   THE BRAND COLOUR                                                (11.h)

   Violet, and it comes from the mark: the soundhole in logo.svg is lit
   #a855f7 and so is a note the microphone has just heard, so the identity,
   the icon and the one thing the app does that nothing else does are all the
   same colour.

   GREEN DID NOT SIMPLY BECOME VIOLET. It was doing three jobs under one hex
   and only one of them was identity:

     brand      the primary button, toggles, pips, meters, badges  -> violet
     CORRECT    drill marks, verdicts, a hit target, the high band -> stays
     the notes  the pitch-class palette in colours.js              -> untouched

   The second is a meaning rather than a style -- green has meant "right" for
   longer than this app has existed, and repainting it violet would have cost
   a signal to gain a coat of paint. The third is data.                    */
/* 19.14 — THE PALETTE.

   Violet leads, and until now it led nowhere: the brand colour was on the
   Play button and the profile pill and absolutely nothing else, while every
   surface in the app was PURE GREY. A dark interface built out of neutral
   greys reads as unstyled however carefully it is laid out, because grey is
   what you get when nobody chose.

   So every neutral was retinted toward this hue, keeping its lightness
   exactly -- no contrast ratio moved and no step of the hierarchy changed,
   the greys simply stopped being grey. Strongest at the dark end, almost
   nothing at the light end: a violet-black page reads as designed and
   violet body text reads as a mistake.

   THREE HUES AND NO MORE. Violet for what you press, and two supports far
   enough from it and from each other that a section is recognisable before
   you read its heading. The board already spends twelve hues on pitch
   classes, so the chrome has to be disciplined or the screen becomes a
   swatch book.

   Orange, green and red are NOT in this palette and are not section
   colours: they mean warning, right and wrong, and a fourth section in
   orange would quietly turn a hint into a heading. */
:root {
    --brand:      #a855f7;   /* the primary action */
    --brand-dim:  #9333ea;   /* its hover */
    --brand-soft: #d0b3ff;   /* brand text on a dark ground */
    --brand-deep: #35244d;   /* the ground of an "on" toggle */
    --brand-edge: rgba(168, 92, 247, .40);
    --brand-wash: rgba(168, 92, 247, .16);
    /* The ground everything sits on. Named because 19.20 needs to paint it
       OVER a border to interrupt it, and a hard-coded copy of the body
       colour in one other place is how two grounds come to disagree. */
    --page:       #130e1c;

    /* The two supports. Cyan sits nearly opposite violet and rose just
       along from it, so the three are told apart at a glance without any
       two of them arguing. */
    --accent-1:      #4cc9d4;   /* Exercises, drills */
    --accent-1-dim:  #2f7f88;
    /* Dusty rose, not candy pink. The first try was hsl(338, 75%, 74%),
       which next to violet read as its louder sibling rather than as a
       different section; dropping thirty points of saturation and eight of
       lightness keeps it warm and stops it shouting. */
    --accent-2:      #cd8494;   /* Songs */
    --accent-2-dim:  #6e4551;
    --accent-2-soft: #e5b3bd;
}

button { background-color: var(--brand); border: none; font-weight: bold; }
button:hover { background-color: var(--brand-dim); }
button.active { background-color: #e74c3c; }
button.active:hover { background-color: #c0392b; }
button.subtle { background-color: #524466; font-weight: normal; font-size: 13px; padding: 6px 11px; }
button.subtle:hover { background-color: #635478; }
/* Destructive, and it was never actually styled as such until now. */
button.subtle.danger { background-color: #5a3030; }
button.subtle.danger:hover { background-color: #7a3a3a; }
button.subtle:disabled { opacity: .35; cursor: default; }
/* THE ELEVENTH TIME THIS NEEDED SAYING, so it is said once.

   `hidden` is an attribute and `display` is a declaration, and a declaration
   always wins -- so any element given `display: flex` or `display: grid`
   ignores `hidden` completely and renders as if the attribute were not there.
   This file carried ten separate `.thing[hidden] { display: none }` patches,
   one per element that had ever been caught, and the routine editor became
   the eleventh: an empty bordered box sitting on the Routines tab forever,
   768px wide inside a 730px panel.

   Patching the next one and the one after is not a strategy. `!important`
   because the whole point is to beat a `display` on the element itself, and
   nothing legitimately wants to be hidden and laid out at the same time. */
[hidden] { display: none !important; }

label.check { cursor: pointer; display: flex; align-items: center; gap: 5px; }
/* 11.c6 - min-width:0 because a flex item refuses to shrink below its
   content by default, and .panel centres its children. On a phone the Backing
   select ("Comp + drums + walking bass") made its .ctl 394px wide inside a
   383px panel, so it overflowed BOTH edges and the word "Backing" was clipped
   off the left of the screen. Centring hides this in a way left-alignment
   would not: the overflow is split, so neither end looks like the end. */
.ctl { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #cbc9cf;
       min-width: 0; max-width: 100%; }
.ctl select, .ctl input { min-width: 0; }
/* display:flex beats the hidden attribute's display:none. The third control
   in this file to need saying out loud. */

/* --- transport buttons: icon only ------------------------------
   Play is the wide one and the metronome is square, so the pair is
   distinguishable by SHAPE before you have read either icon - which is the
   point of dropping the words. */
.tbtn {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0; border-radius: 7px; height: 38px;
}
.tbtn-wide   { width: 78px; }
.tbtn-square { width: 42px; }
.tbtn .ico { width: 17px; height: 17px; }

/* --- the two repeat toggles ---------------------------------------
   Identical to each other on purpose, and deliberately not the green of a
   primary action: they are a state you set, not a thing you do. */
.rpt {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 28px; padding: 0; border-radius: 6px;
    background: #312640; border: 1px solid #433755; color: #8b7f9b;
}
.rpt:hover:not(:disabled) { background: #3d304e; color: #cfccd2; }
.rpt.on { background: var(--brand-deep); border-color: var(--brand); color: var(--brand-soft); }
.rpt:disabled { opacity: .45; cursor: default; }
.rpt .ico { width: 15px; height: 15px; }

/* --- 1.e  SVG fretboard, movable window ------------------------- */
.board-wrap { position: relative; width: 100%; max-width: 792px; margin-bottom: 4px; }
#board {
    width: 100%; height: auto; display: block; border-radius: 6px;
    /* 19.15 - the ring is the key you are in, set by app.js on every key
       change. A shadow rather than a border so it costs no layout: the
       diagram must not move a pixel when the harmony does.

       19.16 - AND QUIETER. The first one was two solid pixels of a fully
       saturated hue with an eighteen-pixel bloom behind it, which framed
       the diagram by shouting over the notes inside it. Same hue, mixed
       here from --key-hue rather than taken finished from noteFill, so the
       ring can carry an alpha: a little over half strength, and the bloom
       tightened and halved. It still says the key at a glance and no
       longer asks to be looked at first. */
    /* D150 - the ring is gone; the key's colour is on the rules above and
       below the neck now (see RULES, NOT BOXES at the end of this file). */
    box-shadow: 0 10px 30px rgba(0,0,0,0.6);
    touch-action: manipulation;
}
#board:focus { outline: 2px solid var(--brand); outline-offset: 3px; }

/* 18.x - no stroke. The board was a rounded rect with a lighter brown edge,
   which made the neck read as an OBJECT sitting on the page rather than as
   the fretboard itself. The fill stays: the inlays and the violet rings are
   both drawn for contrast against dark wood, and taking that away is a
   different change with two things to re-check. */
.wood     { fill: #2c1a0e; stroke: none; }
.string   { stroke: #cfccd2; }
.fretwire { stroke: #988fa5; stroke-width: 3; }
.nut      { stroke: #e8e2d0; stroke-width: 8; stroke-linecap: round; }
.inlay    { fill: #e0dfe1; opacity: .2; }
.fretnum  { fill: #8b7f9b; font-size: 13px; font-weight: 700; text-anchor: middle; }
.fretnum.marked { fill: #cfccd2; }
.strlabel { fill: #d4d2d6; font-size: 14px; font-weight: 700; text-anchor: end;
            dominant-baseline: central; }

/* --- notes --- */
.note { opacity: 0; pointer-events: none; }
.note.visible { opacity: 1; pointer-events: auto; cursor: pointer; }
.note .inner { transform-box: fill-box; transform-origin: center;
               transition: transform .09s ease-out; }
/* ONE INK, AND AN OUTLINE INSTEAD OF A SECOND INK.                 (D49)
   Text used to flip between white and near-black depending on how light the
   note colour was -- correct for contrast, but it meant the same label
   changed colour as you moved across the neck, and the two inks never looked
   like one system.

   paint-order is what makes this work: the stroke is painted BEFORE the fill,
   so it reads as an outline around the glyph rather than a thickening of it.
   Without it the stroke grows inward and 8.5px text turns to mud. A blurred
   drop-shadow was the other option and loses at this size -- it softens the
   very edges that carry the letterform. Black at .62 rather than solid: solid
   at this width makes a 13px glyph look like a sticker. */
.note text { text-anchor: middle; pointer-events: none; user-select: none;
             font-family: inherit; font-weight: 600;
             fill: #ffffff;
             paint-order: stroke fill;
             stroke: rgba(0, 0, 0, .58);
             stroke-linejoin: round;
             stroke-width: 2.1px; }
.note .nm  { font-size: 13px; }          /* 1.c note name  — primary   */
/* THE DEGREE INVERTS THE NAME'S TREATMENT.                         (D53)
   Now that it straddles the rim, the rim is the surface it mostly sits on --
   so black on white is the natural way round, and the white halo simply
   continues the rim across the part of the glyph that overhangs into the
   coloured fill or the dark board. The two labels end up reading as two
   different KINDS of thing rather than one label in two sizes.

   Slightly heavier than the white-on-dark version was: dark type on a light
   ground looks thinner than light type on a dark one at the same weight, so
   500 became 600 to land in the same place optically. */
.note .dg  { font-size: 8.5px; opacity: .92; font-weight: 600;
             fill: #110d17;
             stroke: rgba(255, 255, 255, .9);
             stroke-width: 1.8px; }

.note.playing .inner { transform: scale(1.3); }
.note.playing .body  { stroke: #ffffff !important; stroke-width: 3 !important;
                       stroke-dasharray: none !important; }

/* 0.d — a pitch playable in more than one place */
/* 1.f - the degree, as a ring outside the note's white edge. Painted per
   note in fretboard.js because the colour is the degree's, not a state. */

/* The active copy of a doubled pitch used to wear a white ring. It is gone:
   the GREY GHOST at the other position is already visible and already says
   this pitch is playable twice, so the ring was a second answer to a
   question that was already answered - and a third band around a note that
   now has an edge and a degree ring. The class is still set on the element,
   because the DOM staying self-describing is worth more than the two lines
   it costs. */

/* The greyed twin of a doubled pitch: click it to move the fingering there.

   🔴 It hides while the transport runs.                             (D44f)
   Originally because the arriving note was drawn grey AND dashed, so the two
   marks shared one face. The dash has since come off the arriving note
   (D44l), so the collision is gone and the dash is once again unique to this
   twin -- but it stays hidden anyway, because picking a fingering is
   something you do before you play, not mid-phrase. It returns on stop. */
.note .dash { fill: none; stroke: #6c5d81; stroke-width: 2;
              stroke-dasharray: 4 3; opacity: 0; }
#board.is-playing .note.dup-off {
    opacity: 0;
    pointer-events: none;
    transition: opacity 260ms ease-out;
}
.note.dup-off .dash  { opacity: 1; }
.note.dup-off        { opacity: .58; }
.note.dup-off .body  { fill: #423654 !important; stroke: none !important; }
.note.dup-off text   { fill: #8b7f9b !important; }
.note.dup-off .inner { transform: scale(.86); }

/* IT IS CLICKABLE, SO IT SHOULD LOOK CLICKABLE.                     (D55)
   The greyed twin has always been the control for moving a fingering, and
   nothing about it said so -- it read as a note that had been switched off
   rather than one waiting to be switched on. It brightens under the cursor
   now: the same mark, waking up, rather than a new visual language.

   This cannot leak into playback. `#board.is-playing .note.dup-off` carries
   an id, so it outranks these on specificity and the twin stays hidden and
   unhoverable while the transport runs. */
.note.dup-off,
.note.dup-off .body,
.note.dup-off .dash { transition: opacity 140ms ease-out, fill 140ms ease-out,
                                  stroke 140ms ease-out; }
.note.dup-off:hover        { opacity: .92; }
.note.dup-off:hover .body  { fill: #5c4e70 !important; }
.note.dup-off:hover .dash  { stroke: #cfccd2; }
.note.dup-off:hover text   { fill: #e8e7e9 !important; }

/* --- the root, made obvious ---------------------------------------
   The board is dark brown wood, which is itself a dark desaturated gold, so
   the halo reads by LIGHTNESS rather than by hue. That is exactly why the
   dark rim matters: it replaces the white one, so the glow always sits
   against something dark instead of against the note's own colour. Without
   it the halo loses on D and G, the two warm notes, and a cue that works on
   ten of the twelve is not a cue.

   The 8% size bump is deliberately a NON-colour cue, so the root is still
   findable on a bad monitor, in a bright room, or by someone who cannot
   separate the hues at all.

   Three exclusions, each for a reason:
     bare      the SEE drill hides identity on purpose; marking the root
               there would hand over the answer
     dup-off   a muted unison duplicate is not being played, so it should
               not glow
     arriving  nor is a chord that has not arrived yet             (D44e)
     playing   that state owns the scale (1.3) and the white stroke        */
.note.visible:not(.bare):not(.dup-off):not(.arriving)[data-interval="R"] .body {
    stroke: #241a0e;
    stroke-width: 2.5;
    filter: drop-shadow(0 0 4px  hsla(44, 90%, 64%, .95))
            drop-shadow(0 0 12px hsla(42, 82%, 56%, .80));
}
.note.visible:not(.bare):not(.dup-off):not(.arriving):not(.playing)[data-interval="R"] .inner {
    transform: scale(1.08);
}

#countIn {
    position: absolute; inset: 0; display: none;
    align-items: center; justify-content: center;
    font-size: 110px; font-weight: bold; color: rgba(255,255,255,0.88);
    text-shadow: 0 0 30px rgba(0,0,0,0.95); z-index: 50; pointer-events: none;
}
#countIn.on { display: flex; }

/* The status bar under the board. The loop readout runs the full width of
   the diagram, because it is the thing you glance at mid-bar and it should
   be findable without aiming. What is under your hand sits beneath it, which
   is the right order: that line rarely changes, the loop changes every bar. */
.statusbar {
    /* Follows the board rather than the page, because it belongs to the
       board - a status bar wider than the thing it describes reads as a
       separate panel. */
    width: 100%; max-width: 792px; margin: 6px 0 14px;
    display: flex; flex-direction: column; gap: 7px;
}

.sb-loop {
    /* Extra at the top for the form legend sitting on this border. */
    width: 100%; padding: 17px 14px 11px; border-radius: 6px;
    /* Without this the padding lands outside the 880px cap and the bar ends
       up wider than the board it belongs to. */
    box-sizing: border-box;
    background: #1b1424; border: 1px solid #312640;
    font-family: "JetBrains Mono", Consolas, monospace;
    text-align: center;
    display: flex; flex-direction: column; justify-content: center;
}
.sb-loop .l1 { font-size: 21px; font-weight: 700; color: #e8e7e9; line-height: 1.3; }
.sb-loop .l2 { font-size: 15px; color: #c3c0c8; margin-top: 3px; }
/* Set apart, because "what is coming" is a different question from "where am
   I", and running them together is how you end up reading neither. It is also
   the line you read while still playing the current chord, so it is sized to
   be caught with a glance rather than a look. */
.sb-loop .l3 { font-size: 16px; color: #a7a0b0; margin-top: 14px; }
/* 15.12 - under the lap count, quieter than it: the section coming is
   something you glance at, not something you read. */
.sb-loop .l4 { font-size: 14px; color: #877b99; margin-top: 4px; }
.sb-loop .l-off { font-size: 13px; color: #6c5d81; letter-spacing: 1px; }
/* With the loop off there is nothing to frame, and reserving the running
   height would hand back the vertical space the tab strip just gave up.
   The panel disappears and leaves one dim line. */
.sb-loop.off {
    background: transparent; border-color: transparent;
    padding: 2px 0; box-shadow: none;
}

/* The loop chart doubles as the status bar's headline. A shade larger there
   than in the settings panel, because this is the one you read mid-bar. */
.sb-chart { justify-content: center; margin-top: 8px; }
.sb-chart .lc-box { padding: 5px 15px; }
.sb-chart .lc-box b { font-size: 17px; }
.sb-chart .lc-box i { font-size: 10.5px; }

/* 15.10/15.11 - the form, grouped. A section is a box of capsules with its
   letter on it; the one you are in is lit and the one after it is outlined,
   so "where am I" and "what is next" are both answerable at a glance rather
   than by counting along a row of thirty-two. */
.sb-sec {
    display: flex; align-items: center; gap: 7px;
    padding: 5px 8px 5px 6px; border-radius: 9px;
    border: 1px solid transparent; background: rgba(255,255,255,.02);
}
.sb-sec-lab {
    font-family: "JetBrains Mono", Consolas, monospace;
    font-size: 12px; font-weight: 700; color: #6c5d81;
    min-width: 15px; text-align: center;
}
.sb-sec-boxes { display: flex; align-items: stretch; gap: 6px; flex-wrap: wrap; }
.sb-sec.on {
    background: rgba(176,138,111,.10); border-color: rgba(176,138,111,.45);
}
.sb-sec.on .sb-sec-lab { color: #d8a882; }
/* Outlined rather than filled: what is coming should be legible without
   competing with what is under your hands. */
.sb-sec.next { border-color: rgba(255,255,255,.13); }
.sb-sec.next .sb-sec-lab { color: #988fa5; }
.sb-dim  { color: #877b99; font-weight: 400; }
.sb-sep  { color: #57496b; font-weight: 400; }

.sb-info {
    color: #7a6b8f; font-size: 12.5px; line-height: 1.45; text-align: center;
}

@media (max-width: 620px) {
    .sb-loop { min-height: 0; }
    .sb-loop .l1 { font-size: 17px; }
    .sb-loop .l3 { font-size: 14px; }
}
.hint { color: #e67e22; font-size: 12.5px; }
.volwrap { position: relative; display: inline-flex; }
.volbtn {
    display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
    background: #1f1729; border: 1px solid #312640; border-radius: 8px;
    color: #cfccd2; padding: 7px 12px; font: inherit; font-size: 12px;
}
.volbtn:hover, .volbtn[aria-expanded="true"] { border-color: #524466; color: #fff; }
.volbtn svg { fill: currentColor; }
/* The two arcs are strokes; the cone is a fill. One <svg>, two treatments. */
.volbtn svg .wv { fill: none; stroke: currentColor; stroke-width: 1.7;
                  stroke-linecap: round; }
.volbtn.muted svg .wv { display: none; }

/* 11.c6 - SIX VERTICAL FADERS SIDE BY SIDE ARE 303px WIDE, anchored to the
   left of a button that sits wherever the row happens to put it. On a 320px
   screen that ran 160px off the right edge -- half the mix unreachable, and
   invisible to viewport testing because the preview pane will not go below
   405.

   Two changes. It wraps, so on a narrow screen six faders become two rows of
   three rather than one row off the edge. And it is capped to the viewport,
   so it can never be wider than the screen it is drawn on whatever the
   button's position. */
.volpop {
    box-sizing: border-box;
    position: absolute; bottom: calc(100% + 9px); left: 0; z-index: 40;
    display: flex; flex-wrap: wrap; justify-content: center;
    gap: 15px; padding: 15px 17px 11px;
    /* An absolutely positioned box shrinks to fit its CONTAINING BLOCK, and
       this one's is the volume button -- about 92px. Without a stated width,
       adding flex-wrap collapsed six faders into six rows 92px wide instead
       of letting them overflow. max-content is the natural single row; the
       max-width below is what makes it wrap, and only on a screen too narrow
       to hold it. */
    width: max-content;
    /* Only the screen, no invented number. A 303px cap looked right and was
       three pixels under what six faders need once border-box counts the
       padding, so it wrapped to six rows on a desktop -- a cap that fixes a
       phone by breaking everything wider is not a fix. */
    max-width: calc(100vw - 24px);
    background: #1b1424; border: 1px solid #382c48; border-radius: 10px;
    box-shadow: 0 12px 34px rgba(0, 0, 0, .6);
}
/* And keep it on screen when the button it hangs from is near the right
   edge: anchored right rather than left once there is no room to its right. */
@media (max-width: 460px) { .volpop { left: auto; right: 0; } }
.vp-ch {
    display: flex; flex-direction: column; align-items: center; gap: 9px;
    font-size: 9.5px; letter-spacing: .7px; text-transform: uppercase;
    color: #8a7e9a; font-weight: 700;
}
/* writing-mode is the supported way to stand a range on end; the -webkit
   property is the old one and is kept for anything that has not caught up. */
.vp-ch input[type="range"] {
    writing-mode: vertical-lr; direction: rtl;
    -webkit-appearance: slider-vertical;
    width: 20px; height: 92px; padding: 0;
}

.vol { display: flex; align-items: center; gap: 6px; font-size: 13px; color: #bab6c0; }
.vol input[type="range"] { width: 78px; padding: 4px; }
#posLabel { min-width: 104px; text-align: center; font-weight: 700; color: #eeedef; }

/* The key and quality picker. Two controls where there were three, so it no
   longer needs to be the widest thing under the title. */
.chord-pick { padding: 8px 14px; gap: 10px; }
.chord-pick select { padding: 5px 8px; font-size: 13px; }
.chord-pick #keySelect { min-width: 78px; }

/* ================= CHORD CARD ================================== */
.card {
    width: 100%; max-width: 880px; background: #221a31;
    /* The same trap .sb-loop hit: width:100% is the CONTENT width unless you
       say otherwise, so 24px of padding either side lands outside it and the
       card ends up wider than the column holding it. Invisible on a desktop,
       where there is slack to absorb it -- on a 375px phone it is why the
       whole page scrolled sideways. */
    box-sizing: border-box;
    border: 1px solid #3a2e4b; border-radius: 10px;
    padding: 20px 24px 22px; margin-top: 22px;
    /* The inset hairline along the top edge is the whole trick: a dark
       surface reads as lit from above and therefore as sitting ON the page
       rather than being a hole cut in it. */
    box-shadow: 0 6px 22px rgba(0,0,0,.45),
                inset 0 1px 0 rgba(255,255,255,.045);
}
.card h3 {
    margin: 0 0 16px; font-size: 13px; letter-spacing: 1.6px;
    text-transform: uppercase; color: #7c6d91; font-weight: 700;
}

/* The same label, for the boxes that are not cards. Its own rule rather than
   a shared selector because the spacing differs: a card's title sits inside
   the card's padding, and these sit above a panel with the page's own gap
   underneath them.                                                 (15.13) */
.panel-h {
    margin: 18px 0 7px; font-size: 13px; letter-spacing: 1.6px;
    text-transform: uppercase; color: #7c6d91; font-weight: 700;
    width: 100%; max-width: 880px; text-align: left;
}

/* Spelling row: coloured note tokens with semitone gaps between */
.spell { display: flex; align-items: center; justify-content: center;
         flex-wrap: wrap; gap: 4px; margin-bottom: 22px; }
.tok { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.tok .dot {
    width: 52px; height: 52px; border-radius: 50%;
    display: flex; flex-direction: column; align-items: center;
    justify-content: center; font-weight: 700; line-height: 1.05;
    box-shadow: 0 3px 9px rgba(0,0,0,.45);
}
.tok .dot .n { font-size: 19px; }
.tok .dot .d { font-size: 9.5px; opacity: .8; }
.tok .role { font-size: 10.5px; color: #877b99; letter-spacing: .4px; }
.gap { display: flex; flex-direction: column; align-items: center;
       color: #6c5d81; font-size: 11px; margin: 0 2px 18px; min-width: 46px; }
.gap .bar { width: 100%; height: 2px; background: #473a5a; margin-bottom: 3px; }

.cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
        gap: 22px; }
.cols h4 { margin: 0 0 8px; font-size: 12px; letter-spacing: 1.2px;
           text-transform: uppercase; color: var(--accent-1); font-weight: 700; }
.cols p  { margin: 0; font-size: 13.5px; line-height: 1.6; color: #cfccd2; }
.cols ul { margin: 0; padding-left: 17px; font-size: 13.5px;
           line-height: 1.65; color: #cfccd2; }
.cols li { margin-bottom: 7px; }
.chip { display: inline-block; padding: 1px 7px; border-radius: 4px;
        font-weight: 700; font-size: 12.5px; }
.arrow { color: #7c6d91; }

/* ============= 2.c/2.d  key strip and practice loop ==============
   The keys sit UNDER the transport as a horizontal strip, which hands the
   board back the full width and is the arrangement a phone needs anyway.
   D24: the loop panel is one wide row, everything visible at once.        */
/* 19.20 - THE BOARD TAKES THE CENTRE. It used to be one of two things in a
   centred flex row, so the preview beside it pushed the board a hundred
   pixels left of the changes panel underneath -- two things the same width,
   one above the other, not lining up. A three-column grid with equal outer
   columns centres the middle one whatever stands beside it.

   Below the width where both fit, the preview drops under the board rather
   than the board giving up the centre: the alignment is the point and the
   preview is the thing you glance at. */
.stage {
    display: grid;
    grid-template-columns: 1fr minmax(0, 792px) 1fr;
    max-width: none;
    justify-content: center;
    /* Centred against the board rather than hung off its top edge. The board
       is the tall one, so this is what puts the preview opposite the middle
       of the neck -- roughly where your eye already is. */
    align-items: center;
    /* Wrapping is what puts the preview BELOW the board on a phone, with no
       media query and therefore no breakpoint to get wrong: the two simply
       stop fitting and the second one drops.                        (D42a) */
    gap: 12px;
    width: 100%;
    margin-bottom: 4px;
}
.stage > .board-wrap { grid-column: 2; }
.stage > .next-up { grid-column: 3; justify-self: start; }

/* 792 for the board plus twice (210 + 12) for a preview on either side of
   it, plus the page's own 16 either side: below this the two cannot both
   have what they need, so the preview goes underneath. */
@media (max-width: 1275px) {
    .stage { grid-template-columns: minmax(0, 792px); }
    .stage > .board-wrap, .stage > .next-up { grid-column: 1; }
    .stage > .next-up { justify-self: center; }
}
/* A real basis rather than `flex: 1`. With a 0% basis the board would shrink
   towards nothing before it ever wrapped, and the preview would squeeze it on
   every screen instead of dropping below on the narrow ones. */
.stage .board-wrap { flex: 1 1 520px; min-width: 0; margin-bottom: 0; }

/* ---- the next-chord preview --------------------------------- (D42) ---- */
/* 15.14 - THE FORM BADGE. Top right of the board, and absent most of the
   time: it says what changed and then gets out of the way of the thing you
   are actually looking at. Opacity only, so it composites -- the same reason
   the violet ring animates transform and opacity and nothing else. (D106) */
.form-badge {
    position: absolute; top: 10px; right: 12px;
    padding: 7px 11px 8px; border-radius: 8px;
    background: rgba(20,20,20,.82); border: 1px solid rgba(168,85,247,.35);
    display: flex; flex-direction: column; gap: 1px;
    pointer-events: none;                 /* never in the way of the board */
    opacity: 0; transition: opacity .45s ease;
}
.form-badge.on { opacity: 1; }
.form-badge b { font-size: 19px; font-weight: 700; color: #c9a6f5;
                line-height: 1.05; letter-spacing: .5px; }
.form-badge i { font-style: normal; font-size: 11px; color: #988fa5; }
.form-badge u { text-decoration: none; font-size: 11px; color: #7c6d91; }
.form-badge u b { font-size: 11px; color: #a855f7; letter-spacing: 0; }
/* D148 - the ending, in the red the strip uses for the sounding key: the
   one colour on the page that means "now, look". */
.form-badge.ending { border-color: rgba(255,23,68,.6);
                     box-shadow: 0 0 14px -4px rgba(255,23,68,.7); }
.form-badge.ending b { color: #ff5c7a; font-size: 15px;
                       text-transform: uppercase; letter-spacing: 2px; }
.form-badge.ending i { color: #f3dde2; font-size: 12.5px; }

/* 19.26 - THE JOB. Three colours that are not the key's and not the
   ending's, so a glance tells the three apart: the head in the cool
   accent, the solo in the warm one, the head out in the red the ending
   already uses -- because "melody, last time" is the same kind of news as
   "ending", and a player who has stopped counting needs the loudest one. */
.form-badge { --role: #c9a6f5; }
.form-badge.role-head { --role: var(--accent-1); }
.form-badge.role-solo { --role: var(--accent-2); }
.form-badge.role-out  { --role: #ff5c7a; }
.fb-role {
    font-size: 12px; font-weight: 700; letter-spacing: 2px;
    text-transform: uppercase; color: var(--role); line-height: 1.2;
    margin-bottom: 2px;
}
.form-badge.role-head, .form-badge.role-solo, .form-badge.role-out {
    border-color: color-mix(in srgb, var(--role) 60%, transparent);
    box-shadow: 0 0 14px -4px color-mix(in srgb, var(--role) 70%, transparent);
}
/* The countdown to it reads like the countdown to the end: the word,
   uppercase, and the bars left under it. */
.form-badge.cue b { color: var(--role); font-size: 15px;
                    text-transform: uppercase; letter-spacing: 2px; }
.form-badge.cue i { color: #f3eef8; font-size: 12.5px; }

@media (max-width: 560px) {
    /* On a phone the board is most of the screen and the badge is over it. */
    .form-badge { top: 6px; right: 7px; padding: 5px 8px 6px; }
    .form-badge b { font-size: 15px; }
}

.next-up {
    flex: 0 0 210px;
    box-sizing: border-box;
    background: #1f1729; border-radius: 9px;
    padding: 10px 11px 11px;
    display: flex; flex-direction: column; gap: 6px;
    /* 19.17 - edged in the chord it is announcing, the way the board is
       edged in the key. Weaker than the board's ring on purpose: this panel
       is read out of the corner of your eye while you look at the diagram,
       and two rings of equal weight would make you choose between them. */
    border: 1px solid hsl(var(--next-hue, 270) 55% 55% / .5);
    box-shadow: 0 0 10px -6px hsl(var(--next-hue, 270) 70% 55% / .55);
    transition: border-color .3s ease, box-shadow .3s ease;
}
.next-up.off { display: none; }
.nu-k {
    font-size: 10px; letter-spacing: 1.4px; text-transform: uppercase;
    color: var(--accent-1); font-weight: 700;
    text-align: center;
}
.nu-name {
    font-family: "JetBrains Mono", Consolas, monospace;
    font-size: 17px; font-weight: 700; line-height: 1.1;
    text-align: center;
}
.nu-note { text-align: center; }
.nu-board { line-height: 0; }
.nu-board .mini-board { width: 100%; height: auto; }
.nu-note { font-size: 10.5px; color: #77688c; font-style: italic; line-height: 1.4; }

.sidebar {
    width: 100%; max-width: 1050px;
    box-sizing: border-box;                      /* see .card */
    background: #21192d;
    border: 1px solid #362a46;
    border-radius: 9px;
    padding: 11px 14px 12px;
    margin-bottom: 14px;
}
.side-head {
    font-size: 10.5px; letter-spacing: 1.4px; text-transform: uppercase;
    color: #7c6d91; font-weight: 700; margin-bottom: 8px;
    display: flex; gap: 8px; align-items: baseline;
}
.side-head span { color: #534567; letter-spacing: .6px; }

/* Twelve keys in a row, each still its own permanent colour. */
/* Row gap and top padding are for the live marker: it sits ABOVE the button,
   and with a flat 5px gap it would spear whichever row wrapped above it. */
/* 11.c6 - A GRID, NOT A WRAPPING FLEX. The keys carried `flex: 1 1 auto`,
   so at any width where eleven fit on the first row the twelfth wrapped ALONE
   and grew to fill the whole second one -- a single key the width of the
   panel, which reads as a rendering fault rather than as a key.

   Six columns because that is what the strip is FOR: the first row is C F Bb
   Eb Ab Db, which is "Six keys - first half", and the second is the other
   half. The pedagogy was already there and the layout can simply say it. */
.keycol {
    display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 14px 5px; padding-top: 8px;
}
@media (max-width: 620px) {
    .keycol { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* 19.15 - EACH KEY IS ITS OWN COLOUR. The strip used to be twelve dark
   buttons with tinted text, which is the one place in the app where the
   twelve hues are being compared directly and the one place they were
   hardest to tell apart. The colour is the button now.

   The name is WHITE, not the colour's own ink: noteInk flips between black
   and white through the yellow-green arc, so a strip using it would have
   some names dark and some light and read as an accident.

   D148 - A GLOW, NOT A STROKE. The stroke that kept white legible over the
   pale hues also thickened every letter, and at 13px a thickened letter is
   a smudged one. A dark glow behind the name does the same job without
   touching the letterforms: on the violets and blues it disappears, on the
   yellow and lime it is the dark ground the white was missing.

   D149 - the glow is the page's own dark violet rather than black, so it
   reads as shadow on this page and not as ink from another one; and the
   dark edge every key had is gone -- the colour runs to the corner. The
   border stays as a transparent pixel because the sounding key's ring and
   the hover edge are drawn on it. */
.keybtn {
    position: relative;                 /* the live marker anchors to this */
    display: flex; align-items: center;
    padding: 6px 11px; border-radius: 6px; cursor: pointer;
    background: var(--k, #2c223a);
    border: 1px solid transparent;
    color: #ffffff; font-size: 13.5px; font-weight: 700;
    text-shadow: 0 0 3px rgba(34,10,64,.95), 0 0 7px rgba(52,18,96,.8),
                 0 1px 1px rgba(28,8,52,.6);
    font-family: inherit;
    transition: border-color .12s, box-shadow .12s, filter .12s;
    justify-content: center; min-width: 0;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.22);
}
/* Out of the loop: drained rather than recoloured, so it is plainly the
   same key with the life taken out of it. One filter covers all twelve
   without a second palette to keep in step. */
.keybtn.off {
    filter: saturate(.18) brightness(.52);
    box-shadow: none;
}
.keybtn.live {
    border-color: #ffffff;
    /* Keeps its own colour and gains a white edge -- it is the key that is
       SOUNDING, not a different kind of key. */
    box-shadow: 0 0 0 2px rgba(255,255,255,.75),
                inset 0 1px 0 rgba(255,255,255,.3);
    color: #ffffff;
}

/* A marker over the key that is sounding, rather than dimming the eleven
   that are not. Dimming made the whole strip say "these are switched off",
   which is a different fact and one it was already saying with .off.

   Electric red on purpose: nothing else on this page is that colour, so it
   is the one thing your eye finds without looking for it - and the strip is
   read at a glance, mid-bar, while your hands are busy. */
/* 19.21 - HOVER LIGHTS THE EDGE, NOT THE KEY. Brightening the fill moved
   the one thing this strip exists to show: twelve hues being compared, where
   a key under the cursor read as a slightly different colour from the same
   key a moment earlier. The edge and a glow say "this one" without touching
   what the key IS.

   White, not the key's own colour -- a glow in the same hue as the thing it
   surrounds is invisible, which is the whole difficulty with twelve of them.

   After .off and .live, and naming both, because each sets a box-shadow of
   its own and equal specificity is decided by source order. Without those
   two selectors a drained key would light nothing and the sounding one would
   lose its ring on hover. */
.keybtn:hover, .keybtn.off:hover {
    border-color: rgba(255,255,255,.72);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.22),
                0 0 11px -1px rgba(255,255,255,.4);
}
/* The sounding key keeps its ring and gains the glow behind it. */
.keybtn.live:hover {
    box-shadow: 0 0 0 2px rgba(255,255,255,.75),
                inset 0 1px 0 rgba(255,255,255,.3),
                0 0 13px -1px rgba(255,255,255,.45);
}
/* Drained keys stay drained -- hovering one is a question about whether to
   put it back in, not a preview of it being back in. */

.keybtn.live::before {
    content: ''; position: absolute;
    top: -9px; left: 50%; transform: translateX(-50%);
    width: 0; height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-top: 8px solid #ff1744;
    filter: drop-shadow(0 0 4px rgba(255, 23, 68, .85));
}

.side-bulk { display: flex; gap: 6px; margin-left: auto; }
.side-bulk button { padding: 3px 10px; font-size: 11.5px; }

/* D24 = A: one wide row. Nothing hidden behind a click. */
.loop-panel {
    margin-top: 11px; padding-top: 11px;
    border-top: 1px solid #362a46;
    display: flex; align-items: center; flex-wrap: wrap;
    gap: 8px 18px;
    font-size: 12.5px; color: #b7b2be;
}
.loop-panel .check { font-size: 12.5px; }
.loop-panel .check.strong { font-weight: 700; color: #e8e7e9; font-size: 13px; }
.loop-row { display: flex; align-items: center; gap: 6px; }
/* display:flex above beats the hidden attribute's UA display:none, so the
   per-chord bars row stayed on screen with an empty, unclickable select
   whenever there was no progression to configure. The same trap the loop
   quals and the chart already had to be dug out of. */
.loop-row select { padding: 3px 6px; font-size: 12.5px; }
.loop-row #progSelect { width: auto; min-width: 150px; }

.lc-boxes { display: flex; align-items: stretch; gap: 6px; flex-wrap: wrap; }
.lc-box {
    display: flex; flex-direction: column; align-items: center; gap: 1px;
    padding: 4px 11px; border-radius: 6px;
    background: #292036; border: 1px solid #3b2f4b;
    transition: background .12s, border-color .12s;
}
.lc-box b { font-size: 13px; font-weight: 700; line-height: 1.15; }
.lc-box i { font-style: normal; font-size: 9.5px; color: #7a6b8f;
            letter-spacing: .4px; }
/* The one sounding now. */
.lc-box.on { background: #38383d; border-color: #cfccd2; }
.lc-box.on i { color: #cfccd2; }

/* Left out of the loop: still shown, because knowing what is switched off is
   the point, but plainly not participating. */
.lc-box.out { opacity: .4; }
.lc-box.out b { filter: grayscale(.85); }

/* A capsule is now two controls: the name, which puts that chord on the
   board, and a pip, which decides whether it is in the cycle.       (D35)
   They are siblings inside .lc-cap rather than nested, because a button
   inside a button is not a thing the DOM will give you. */
.lc-cap { position: relative; display: flex; }
.lc-cap > .lc-box { flex: 1; }

.lc-goto { cursor: pointer; font-family: inherit; }
.lc-goto:hover { border-color: #7a6b8f; }
.lc-goto.out:hover { opacity: .7; }

.lc-pip {
    position: absolute; top: -4px; right: -4px;
    width: 13px; height: 13px; padding: 0; border-radius: 50%;
    background: #292036; border: 1px solid #473a5a; cursor: pointer;
}
.lc-pip.in { background: var(--brand); border-color: var(--brand-soft); }
.lc-pip:hover { border-color: #cfccd2; }
.lc-pip:focus-visible { outline: 2px solid var(--accent-1); outline-offset: 1px; }
/* 13px is a fair target for a mouse and a poor one for a thumb, so the hit
   area is grown without growing the dot. It overlaps the capsule's corner
   on purpose: the pip is tested first, so that corner toggles rather than
   navigates, which is the same thing the pixel is drawn to say. */
.lc-pip::after { content: ''; position: absolute; inset: -7px; }
@media (max-width: 620px) {
    .keybtn { min-width: 58px; padding: 6px 5px; font-size: 12px; }
}

/* ================= transport icons ============================== */
.ico {
    width: 13px; height: 13px;
    fill: currentColor; stroke: none;
    vertical-align: -1px; margin-right: 7px;
    flex-shrink: 0;
}
.ico.stroke { fill: none; stroke: currentColor; stroke-width: 1.6;
              stroke-linejoin: round; stroke-linecap: round; }
#playBtn, #metroBtn { display: inline-flex; align-items: center; }

/* ================= 3.a  screens ================================
   The tab strip that used to live here is gone. It cost a full row of
   vertical space on every screen to display four words that change
   rarely, and the profile menu was already the one fixed thing on the
   page. Navigation moved in there. */
.tabpanel { display: none; width: 100%; flex-direction: column; align-items: center; }
.tabpanel.active { display: flex; }

/* 15.3b - a control the transport has taken away. Dimmed rather than
   hidden: it still tells you what the cycle is doing, it just cannot be
   changed until you stop. */
.loop-row.locked { opacity: .5; }
.loop-row.locked select { cursor: not-allowed; }

/* 11.c6 - the challenge list, folded. Same shape as the reference folds on
   the Practice screen: a summary row you can read at a glance and a body you
   open when you want it. */
.ag-ch-fold > summary {
    cursor: pointer; list-style: none; display: flex;
    align-items: center; justify-content: space-between; gap: 10px;
    padding: 7px 0;
}
.ag-ch-fold > summary::-webkit-details-marker { display: none; }
.ag-ch-fold > summary::before {
    content: "A"; color: #6c5d81; font-size: 15px;
    transition: transform .15s; margin-right: 2px;
}
.ag-ch-fold[open] > summary::before { transform: rotate(90deg); }
.ag-ch-fold > summary .ag-ch-k { flex: 1; }
.ag-ch-sum { font-size: 12px; color: #7c6d91; }

/* ================= settings (11.c3) ============================
   One column of grouped rows rather than a wrapped row of controls. These
   are read and considered rather than reached for mid-bar, so they get room
   and a sentence each. */
.settings {
    width: 100%; max-width: 620px; text-align: left;
    display: flex; flex-direction: column; gap: 18px;
}
.set-group {
    background: #21192d; border: 1px solid #362a46; border-radius: 9px;
    padding: 15px 17px;
}
.set-group h3 {
    margin: 0 0 3px; font-size: 11px; letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--accent-1); font-weight: 700;
}
.set-group p { margin: 0 0 12px; font-size: 13px; line-height: 1.55; color: #877b99; }
.set-row {
    display: flex; align-items: center; justify-content: space-between;
    /* The label goes first and the control follows onto its own line rather
       than either being clipped. */
    flex-wrap: wrap; row-gap: 6px;
    gap: 14px; padding: 7px 0; border-top: 1px solid #2e243c;
}
.set-row:first-of-type { border-top: none; }
.set-row > span { font-size: 13.5px; color: #dddbdf; }
.set-row select {
    background: #2a2137; color: #dddbdf; border: 1px solid #413553;
    border-radius: 5px; padding: 5px 8px; font-size: 13px;
    /* A FLOOR, NOT A FLOOR THAT WINS. 190px is the width these want, and a
       plain min-width made the row refuse to shrink past it -- 7px off the
       right edge at 280. `min()` keeps the preferred size where there is room
       and yields where there is not, which is what a minimum should have
       meant. */
    min-width: min(190px, 100%);
}
.set-row select:hover { border-color: #57496b; }

/* ================= routines ==================================== */
.routine-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
    gap: 14px; width: 100%; max-width: 1050px;
}
.routine {
    background: #221a31; border: 1px solid #3a2e4b; border-radius: 9px;
    box-shadow: 0 4px 14px rgba(0,0,0,.35),
                inset 0 1px 0 rgba(255,255,255,.045);
    padding: 15px 17px; text-align: left;
    display: flex; flex-direction: column; gap: 7px;
}
.routine h4 { margin: 0; font-size: 15px; color: #eeedef; font-weight: 700; }
.routine .who {
    font-size: 10.5px; letter-spacing: 1.1px; text-transform: uppercase;
    color: var(--accent-1); font-weight: 700;
}
.routine p { margin: 0; font-size: 13px; line-height: 1.55; color: #afa9b7; }
.routine .keys {
    font-family: "JetBrains Mono", Consolas, monospace;
    font-size: 12px; color: #877b99;
}
/* 15.3 - a routine's own knobs, above its Start button. Set them, then go:
   the selects read as part of the card rather than as page furniture, which
   is why they sit inside the same border and share its left edge. */
.r-params {
    display: flex; flex-wrap: wrap; gap: 6px 14px;
    margin-top: 2px; padding-top: 9px; border-top: 1px solid #312640;
}
.r-param { display: flex; align-items: center; gap: 7px; font-size: 12px; }
.r-param > span { color: #877b99; }
.r-param select {
    font-size: 12px; padding: 3px 6px; border-radius: 5px;
    background: #2a2137; color: #dddbdf; border: 1px solid #413553;
}
.r-param select:hover { border-color: #57496b; }
.routine button { align-self: flex-start; margin-top: 4px; }
.routine.pending { opacity: .58; }
/* 11.c2 - the key, on the badge, since the strip that used to say it is
   gone while a routine runs. Monospaced so it holds its width as the loop
   moves through the keys rather than nudging the x button about. */
.rb-key {
    font-family: "JetBrains Mono", Consolas, monospace;
    font-size: 12px; font-weight: 700; color: #7fc79b;
    margin-left: 2px; min-width: 22px; text-align: center;
}

.routine.mine { border-color: #3d5a47; }
.routine.mine .who { color: #6fb08a; }
.r-btns { display: flex; gap: 8px; align-items: center; margin-top: 4px; }
.r-btns button { margin-top: 0; }
button.r-ghost, button.re-ghost {
    background: transparent; color: #988fa5; border: 1px solid #413553;
    font-size: 12px; padding: 5px 10px; border-radius: 6px; cursor: pointer;
}
button.r-ghost:hover, button.re-ghost:hover { color: #dddbdf; border-color: #635478; }

/* 15.7 - the blank start. A card shaped like the others so it reads as one
   more thing in the list rather than as page furniture beside it. */
.routine-new {
    background: transparent; border: 1px dashed #413553; border-radius: 9px;
    padding: 15px 17px; text-align: left; cursor: pointer; color: #988fa5;
    display: flex; flex-direction: column; gap: 7px; align-items: flex-start;
    font: inherit; min-height: 120px; justify-content: center;
}
.routine-new:hover { border-color: var(--accent-1); color: #dddbdf; }
.routine-new .rn-plus { font-size: 22px; line-height: 1; color: var(--accent-1); }
.routine-new > span:nth-child(2) { font-size: 15px; font-weight: 700; color: #eeedef; }
.routine-new p { margin: 0; font-size: 13px; line-height: 1.5; }

/* 15.10 - the section letter in the status line. Given its own weight
   because it is the one thing on that line you look for rather than read. */
.l2-sec {
    color: var(--accent-2); font-weight: 700;
    font-family: "JetBrains Mono", Consolas, monospace;
}

/* 15.8 - songs, under their own heading. A tune is a different thing to
   choose than an exercise, even though the transport cannot tell. */
.song-head { width: 100%; max-width: 1050px; text-align: left; margin-top: 22px; }
/* The same heading at the top of its own tab has nothing above it to be
   separated from. */
.song-head.routine-head { margin-top: 0; }
.routine-head h3 { color: var(--accent-1); }
.song-head h3 {
    margin: 0 0 2px; font-size: 11px; letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--accent-2); font-weight: 700;
}
.song-head p { margin: 0 0 12px; font-size: 13px; color: #877b99; }
/* 19.4 - the way out to the printable sheets. Quiet, because it leaves the
   app: the only link on the screen that does, so it should not read like
   one of the buttons that do not. */
.song-head .sheet-link {
    display: inline-block; margin: -4px 0 12px; font-size: 12.5px;
    color: var(--accent-2); text-decoration: none; border-bottom: 1px dotted var(--accent-2-dim);
    padding-bottom: 1px;
}
.song-head .sheet-link:hover { color: var(--accent-2-soft); border-bottom-color: var(--accent-2); }
.routine.song { border-color: #4a3d33; }
.routine.song .keys { color: var(--accent-2); }
.routine.song .who { display: none; }

/* ================= the routine editor ========================== */
.routine-editor {
    box-sizing: border-box;
    width: 100%; max-width: 780px; background: #21192d;
    border: 1px solid #362a46; border-radius: 9px; padding: 16px 18px;
    display: flex; flex-direction: column; gap: 12px; text-align: left;
}
.re-top { display: flex; align-items: center; justify-content: space-between; }
.re-top h3 { margin: 0; font-size: 16px; color: #eeedef; }
.re-field { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.re-field > span { color: #877b99; white-space: nowrap; }
.re-field.re-wide { display: flex; }
.re-field.re-wide input { flex: 1; }
.re-field input[type=text] {
    background: #2a2137; color: #dddbdf; border: 1px solid #413553;
    border-radius: 5px; padding: 6px 9px; font: inherit; font-size: 13px;
}
.re-field select {
    background: #2a2137; color: #dddbdf; border: 1px solid #413553;
    border-radius: 5px; padding: 4px 7px; font-size: 12.5px;
}
.re-row { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; }
.re-check { gap: 6px; }
.re-parts { display: flex; flex-direction: column; gap: 10px;
            border-top: 1px solid #312640; padding-top: 12px; }
.re-parts h4 { margin: 0; font-size: 11px; letter-spacing: 1.1px;
               text-transform: uppercase; color: var(--accent-1); font-weight: 700; }
.re-seg { border: 1px solid #362a46; border-radius: 7px; background: #1d1527; }
.re-seg-head { display: flex; align-items: center; gap: 9px;
               padding: 8px 10px; border-bottom: 1px solid #2e243c; }
.re-num { font-family: "JetBrains Mono", Consolas, monospace; font-size: 11px;
          color: #746589; width: 14px; }
.re-kind { flex: 1; }
.re-seg-btns { display: flex; gap: 4px; }
.re-seg-btns button {
    background: #2a2137; color: #bab6c0; border: 1px solid #413553; cursor: pointer;
    width: 26px; height: 26px; border-radius: 5px; font-size: 13px; padding: 0;
}
.re-seg-btns button:hover:not(:disabled) { color: #fff; border-color: #635478; }
.re-seg-btns button:disabled { opacity: .3; cursor: default; }
.re-seg-body { display: flex; flex-wrap: wrap; gap: 8px 16px; padding: 10px; }
.re-chips { display: flex; gap: 4px; }
.re-chip {
    background: #2a2137; color: #857898; border: 1px solid #413553; cursor: pointer;
    border-radius: 5px; padding: 3px 8px; font-size: 12px;
}
.re-chip.on { background: #3a4a5a; color: #dfe9f2; border-color: #5a7a99; }
.re-add {
    align-self: flex-start; background: transparent; color: #988fa5;
    border: 1px dashed #413553; border-radius: 6px; padding: 6px 12px;
    font-size: 12.5px; cursor: pointer;
}
.re-add:hover { color: #dddbdf; border-color: var(--accent-1); }
.re-actions { display: flex; align-items: center; gap: 10px;
              border-top: 1px solid #312640; padding-top: 12px; }
button.re-danger {
    background: transparent; color: #c0705f; border: 1px solid #5a3b34;
    font-size: 12px; padding: 5px 10px; border-radius: 6px; cursor: pointer;
}
button.re-danger:hover { color: #e08b78; border-color: #7a4f45; }
.re-error { color: #e08b78; font-size: 12.5px; }
.routine.pending .badge {
    display: inline-block; font-size: 10.5px; font-weight: 700;
    letter-spacing: .8px; text-transform: uppercase;
    color: #e67e22; border: 1px solid #e67e22; border-radius: 4px;
    padding: 1px 6px; align-self: flex-start;
}

/* 3.b - which routine you are in is CONTEXT, not content, so it floats over
   the top left instead of pushing the whole page down a row. Translucent
   with a blur behind it so it reads as an overlay rather than as another
   panel competing for attention. */
/* 19.30 - the word "Saved", top left under the mark. Fades in and out;
   opacity only, so it composites. */
.saved-toast {
    position: fixed; top: 48px; left: 14px; z-index: 60;
    padding: 3px 10px; border-radius: 999px;
    background: rgba(20,20,20,.72); border: 1px solid var(--brand-edge);
    color: var(--brand-soft); font-size: 11px; letter-spacing: 1px;
    text-transform: uppercase; font-weight: 700;
    opacity: 0; transition: opacity .25s ease; pointer-events: none;
}
.saved-toast.on { opacity: 1; }
@media (max-width: 560px) { .saved-toast { top: 44px; left: 10px; } }

.routine-badge {
    position: fixed; top: 12px; left: 14px; z-index: 60;
    display: flex; align-items: center; gap: 8px;
    padding: 6px 8px 6px 12px; border-radius: 999px;
    background: var(--brand-wash);
    border: 1px solid var(--brand-edge);
    backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
    box-shadow: 0 4px 14px rgba(0,0,0,.35);
    font-size: 12.5px; color: #cfe8d0; max-width: 42vw;
}
.routine-badge .rb-k {
    font-size: 9.5px; letter-spacing: 1.1px; text-transform: uppercase;
    color: var(--brand-soft); opacity: .75; font-weight: 700;
}
.routine-badge b {
    color: #b6e8b9; font-weight: 700; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap;
}
.routine-badge button {
    background: none; border: none; color: var(--brand-soft); font-size: 15px;
    line-height: 1; padding: 2px 5px; border-radius: 50%; font-weight: 700;
}
.routine-badge button:hover { background: var(--brand-wash); color: #fff; }

/* Both the badge and the pill float at the top, so on a narrow screen they
   compete for the same row. The pill gives way first: the section name is
   already in the menu it opens, whereas the routine name has nowhere else
   to live. */
@media (max-width: 720px) {
    .routine-badge { max-width: 50vw; padding-left: 10px; }
    .routine-badge .rb-k { display: none; }
    .profile-pill .sec, .profile-pill .sep { display: none; }
}

.placeholder {
    width: 100%; max-width: 720px; text-align: center;
    box-sizing: border-box;                      /* see .card */
    color: #7a6b8f; font-size: 14px; line-height: 1.7;
    background: #21192d; border: 1px solid #362a46; border-radius: 9px;
    padding: 30px 26px;
}
.placeholder h3 { color: #cfccd2; margin: 0 0 10px; }
.placeholder code { font-size: 12px; }

/* ================= drills ======================================= */
.note.probe { pointer-events: auto; cursor: crosshair; }
.note.probe:not(.visible) { opacity: .1; }
.note.probe:not(.visible):hover { opacity: .4; }
.note.bare .dash { opacity: 0; }

.note.mark-right .body  { stroke: #50fa7b !important; stroke-width: 3.5 !important;
                          stroke-dasharray: none !important; }
.note.mark-wrong .body  { stroke: #ff5555 !important; stroke-width: 3.5 !important;
                          stroke-dasharray: none !important; }
.note.mark-target       { opacity: 1 !important; }
.note.mark-target .body { fill: #50fa7b !important; }
/* 18.5 - the NEXT note of the line, outlined rather than filled: a guide
   tone line is the step from one note to the next, and you can only see
   the step if both ends are on the neck. */
.note.mark-next         { opacity: 1 !important; }
.note.mark-next .body   { stroke: #50fa7b !important; stroke-width: 2.5 !important;
                          stroke-dasharray: 3 2.5 !important; }

.drill-wrap { width: 100%; max-width: 880px; display: flex;
              flex-direction: column; align-items: center; gap: 16px; }

.drill-setup, .drill-summary {
    width: 100%; background: #21192d; border: 1px solid #362a46;
    box-sizing: border-box;                      /* see .card */
    border-radius: 10px; padding: 22px 26px;
}
.drill-setup h3, .drill-summary h3 {
    margin: 0 0 4px; font-size: 17px; color: #eeedef;
}
.drill-setup .sub { color: #877b99; font-size: 13px; margin-bottom: 18px; }
.setup-row { display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: center;
             margin-bottom: 14px; }
.setup-row > .lbl {
    font-size: 10.5px; letter-spacing: 1.2px; text-transform: uppercase;
    color: var(--accent-1); font-weight: 700; width: 100%; margin-bottom: -4px;
}
.angle-pick { display: flex; flex-wrap: wrap; gap: 8px; }
.angle-pick label {
    display: flex; align-items: center; gap: 6px; cursor: pointer;
    background: #2c223a; border: 1px solid #3b2f4b; border-radius: 6px;
    padding: 6px 11px; font-size: 13px; color: #cfccd2;
}
.angle-pick label:hover { background: #382c48; }
.angle-pick input:checked + span { color: #fff; font-weight: 700; }

.drill-run { width: 100%; display: none; flex-direction: column;
             align-items: center; gap: 14px; }
.drill-run.on { display: flex; }
.drill-setup.off, .drill-summary.off { display: none; }

/* 11.c6 - THE ROW THAT COULD NOT SHRINK. "Question 1 of 10", the progress
   bar, "0 correct" and "End session" needed 389px and the row could neither
   wrap nor squeeze, so below about 390px End session was pushed off the right
   edge -- on EVERY angle, and the only way out of a running drill.

   Two changes, because either alone leaves a gap: the bar shrinks rather than
   holding 160px, and the row wraps if that is still not enough. Found by
   constraining the container rather than by resizing the window, because the
   preview pane will not go below 405px and 405 is wider than 389. */
.drill-bar {
    width: 100%; display: flex; align-items: center; gap: 14px;
    flex-wrap: wrap;
    font-size: 13px; color: #988fa5;
    font-family: "JetBrains Mono", Consolas, monospace;
}
.drill-bar .spacer { flex: 1; }
/* 2.7 - the controller's verdict. Quiet on target; a colour when it is
   doing something, so a run of right answers is seen to have consequences. */
.drill-bar .lean { font-size: 11px; letter-spacing: .8px; text-transform: uppercase;
                   padding: 2px 8px; border-radius: 999px; border: 1px solid #3a2e4b;
                   color: #7c6d91; }
.drill-bar .lean[data-lean="1"]  { color: #ffb86c; border-color: #6a4a2a; }
.drill-bar .lean[data-lean="-1"] { color: #8be9fd; border-color: #2a5a6a; }
.drill-bar .prog {
    flex: 1 1 160px; min-width: 48px; max-width: 160px;
    height: 5px; background: #312640; border-radius: 3px; overflow: hidden;
}
.drill-bar .prog i { display: block; height: 100%; background: var(--brand); width: 0; }

.drill-q {
    box-sizing: border-box;
    width: 100%; background: #21192d; border: 1px solid #362a46;
    border-radius: 10px; padding: 20px 24px; text-align: center;
}
.drill-q .angle {
    font-size: 10.5px; letter-spacing: 1.6px; text-transform: uppercase;
    color: var(--accent-1); font-weight: 700; margin-bottom: 8px;
}
.drill-q .prompt { font-size: 20px; color: #f1f0f2; font-weight: 700; margin-bottom: 4px; }
.drill-q .hint   { font-size: 13px; color: #877b99; }

.choices { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center;
           margin-top: 16px; }
.choices button {
    background: #312640; border: 1px solid #473a5a; color: #eeedef;
    font-size: 15px; font-weight: 700; padding: 11px 20px; border-radius: 7px;
    min-width: 108px;
}
.choices button:hover:not(:disabled) { background: #3f3250; }
.choices button.right { background: #2c5c34; border-color: #50fa7b; color: #fff; }
.choices button.wrong { background: #5c2c2c; border-color: #ff5555; color: #fff; }
.choices button:disabled { cursor: default; opacity: .55; }
.choices button.right:disabled, .choices button.wrong:disabled { opacity: 1; }

.verdict { margin-top: 14px; font-size: 14px; min-height: 22px; }
.verdict.ok  { color: #50fa7b; }
.verdict.no  { color: #ff5555; }
.verdict .why { color: #988fa5; display: block; margin-top: 4px; font-size: 13px; }

.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px,1fr));
                gap: 12px; margin: 18px 0; }
.sum-card { background: #1b1424; border: 1px solid #312640; border-radius: 8px;
            padding: 12px 14px; }
.sum-card .k { font-size: 10.5px; letter-spacing: 1.1px; text-transform: uppercase;
               color: #6c5d81; font-weight: 700; }
.sum-card .v { font-size: 22px; font-weight: 700; color: #f1fa8c; margin-top: 3px; }
.sum-card .v small { font-size: 13px; color: #877b99; font-weight: 400; }
.angle-rows { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.angle-row { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.angle-row .nm { width: 74px; color: #cfccd2; font-weight: 700; }
.angle-row .track { flex: 1; height: 7px; background: #2e243c; border-radius: 4px; overflow: hidden; }
.angle-row .track i { display: block; height: 100%; background: var(--brand); }
.angle-row .track i.weak { background: #e67e22; }
.angle-row .track i.bad  { background: #ff5555; }
.angle-row .n { width: 62px; text-align: right; color: #877b99;
                font-family: "JetBrains Mono", Consolas, monospace; font-size: 12px; }

/* ================= mic answering ================================ */
.mic-panel { margin-top: 16px; display: flex; flex-direction: column;
             align-items: center; gap: 12px; }

.mic-targets { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.mic-target {
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    min-width: 62px; padding: 9px 12px; border-radius: 8px;
    background: #292036; border: 1px solid #3b2f4b;
    transition: background .15s, border-color .15s;
}
.mic-target .n { font-size: 17px; font-weight: 700; color: #988fa5; }
.mic-target .d { font-size: 10px; color: #6c5d81; }
.mic-target.hit { background: #2c5c34; border-color: #50fa7b; }
.mic-target.hit .n { color: #ffffff; }
.mic-target.hit .d { color: #b9e8bf; }

.mic-status { display: flex; align-items: center; gap: 10px;
              font-size: 13px; color: #877b99;
              font-family: "JetBrains Mono", Consolas, monospace; }
.mic-level { width: 90px; height: 6px; background: #2e243c;
             border-radius: 3px; overflow: hidden; }
.mic-level i { display: block; height: 100%; width: 0; background: var(--brand);
               transition: width .06s linear; }
.mic-warn { color: #e67e22; }

.drill-shape { margin: 14px auto 4px; max-width: 260px; }
.drill-shape .sh-board { width: 100%; height: auto; }

/* ================= the smooth path (8.2) ========================= */
.sp-row { margin: 14px 0 6px; }
.sp-k {
    font-size: 10px; letter-spacing: 1px; text-transform: uppercase;
    color: #77688c; font-weight: 700; margin-bottom: 7px;
    display: flex; align-items: baseline; gap: 9px;
}
.sp-total { font-size: 15px; letter-spacing: 0; text-transform: none; }
.sp-total.bad  { color: #d9744a; }
.sp-total.good { color: #63c07a; }
.sp-total::after { content: ' semitones'; font-size: 10px; color: #6c5d81; }
/* Scrolls rather than wraps: a path read out of order is not a path, so a
   twelve-bar form stays on one line even at 375px. */
.sp-line {
    display: flex; align-items: center; gap: 6px;
    overflow-x: auto; padding-bottom: 6px;
}
.sp-cell {
    margin: 0; flex: none; width: 118px; cursor: pointer;
    background: #1f1729; border: 1px solid #2f253d; border-radius: 8px;
    padding: 7px 5px 5px;
    transition: border-color 130ms ease-out;
}
.sp-cell:hover { border-color: #544668; }
.sp-cell figcaption { margin-top: 4px; text-align: center; font-size: 11px; color: #cfccd2; }
.sp-cell figcaption span { color: #877b99; font-size: 9.5px; }
.sp-gap {
    flex: none; font-size: 11px; color: #7c6d91; padding: 0 2px;
    font-variant-numeric: tabular-nums;
}
/* The arrow is written literally. Escaped as '\2192' it survived CSS but
   not the shell that wrote the file, which turned it into a control
   character and rendered a tofu box next to the number. */
.sp-gap::before { content: '→ '; color: #524466; }

/* ================= who stays, who moves (8.1) ==================== */
.cc-common { line-height: 1.7; }
.cc-pair { font-size: 15px; font-weight: 700; margin-bottom: 12px; }
.cc-row { display: flex; gap: 12px; align-items: baseline; margin-bottom: 9px; }
.cc-k {
    flex: none; width: 54px; font-size: 10px; letter-spacing: 1px;
    text-transform: uppercase; color: #77688c; font-weight: 700;
}
.cc-v { flex: 1; min-width: 0; }
.cc-deg { color: #877b99; font-size: 12px; }
/* "Nothing is held" is an ANSWER, not an empty state -- it is what happens in
   one transition out of six -- so it is styled as text, not as a placeholder. */
.cc-none { color: #a89a6a; font-style: italic; }
.cc-note { margin-top: 12px; color: #77688c; font-size: 11.5px; }

/* ================= the reference folds (11.b2) =================== */
/* A <details> styled as one of the cards it replaced. The marker is drawn
   here rather than left to the browser, because the default triangle differs
   between engines and sits at a different baseline in each.          (D62) */
.card.fold { padding: 0; overflow: hidden; }
.card.fold > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: flex; align-items: center; gap: 10px;
    padding: 13px 16px;
    transition: background 130ms ease-out;
}
.card.fold > summary::-webkit-details-marker { display: none; }
.card.fold > summary:hover { background: #241c30; }
.card.fold > summary::before {
    content: ''; flex: none; width: 7px; height: 7px;
    border-right: 2px solid #877b99; border-bottom: 2px solid #877b99;
    transform: rotate(-45deg); margin-left: 2px;
    transition: transform 160ms cubic-bezier(.45, .02, .3, 1);
}
.card.fold[open] > summary::before { transform: rotate(45deg); }
.card.fold > summary h3,
.card.fold > summary h4 { margin: 0; }
.card.fold > summary:focus-visible { outline: 2px solid var(--accent-1); outline-offset: -2px; }
.fold-n { margin-left: auto; font-size: 11px; color: #77688c; }

/* The body keeps the padding the summary gave up, so an open fold looks like
   the card it used to be. */
.card.fold > *:not(summary) { margin-left: 16px; margin-right: 16px; }
.card.fold > *:not(summary):last-child { margin-bottom: 15px; }

/* ================= the grip catalog (4.1) ======================= */
.card.shapes { box-sizing: border-box; }
.sh-filter { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0 12px; }
.sh-tab {
    background: #21192d; border: 1px solid #362a46; border-radius: 6px;
    color: #988fa5; font: inherit; font-size: 11.5px; padding: 5px 11px;
    cursor: pointer;
}
.sh-tab:hover { border-color: #4b3e5e; color: #d0cdd3; }
.sh-tab.on { background: #2d233b; border-color: #5a4c6e; color: #fff; }

/* auto-fill rather than a fixed count: fourteen grips at 375px is two
   columns and at 1200px is six, and neither should need a media query. */
.sh-grid {
    display: grid; gap: 10px;
    grid-template-columns: repeat(auto-fill, minmax(126px, 1fr));
}
.sh-cell {
    margin: 0; padding: 8px 6px 6px; cursor: pointer;
    background: #1f1729; border: 1px solid #2f253d; border-radius: 8px;
    transition: border-color 130ms ease-out, background 130ms ease-out;
}
.sh-cell:hover { border-color: #544668; background: #241c30; }
.sh-board { display: block; width: 100%; height: auto; }
.sh-cell figcaption {
    margin-top: 5px; font-size: 10px; line-height: 1.35; color: #8a7e9a;
    text-align: center;
}
.sh-cell figcaption b { color: #c7c4cc; font-weight: 700; }
.sh-x    { fill: #67587c; font-size: 8px; text-anchor: middle; font-weight: 700; }
.sh-deg  { fill: #fff; font-size: 5.4px; text-anchor: middle; font-weight: 700;
           paint-order: stroke fill; stroke: rgba(0,0,0,.55); stroke-width: 1.4px;
           stroke-linejoin: round; }
.sh-fret { fill: #7a6b8f; font-size: 7.6px; text-anchor: middle; }

/* ================= progress ===================================== */
.prog-block { margin-top: 26px; }
.prog-block h4 {
    margin: 0 0 4px; font-size: 12px; letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--accent-1); font-weight: 700;
}
.prog-block .note { margin: 0 0 12px; font-size: 12.5px; color: #7a6b8f;
                    line-height: 1.55; max-width: 640px; }

/* One scale everywhere a mastery figure appears. */
.band-high { background: #4CAF50; }
.band-mid  { background: #f1c40f; }
.band-low  { background: #e67e22; }
.band-none { background: #413553; }
.v.band-high { color: #7fd184; }
.v.band-mid  { color: #f1fa8c; }
.v.band-low  { color: #ffb86c; }
.v.band-none { color: #877b99; }

.angle-row .wt { width: 32px; color: #6c5d81; font-size: 11.5px;
                 font-family: "JetBrains Mono", Consolas, monospace; }
.angle-row .acc { width: 108px; text-align: right; color: #6c5d81; font-size: 11.5px;
                  font-family: "JetBrains Mono", Consolas, monospace; }

.heat { display: flex; flex-direction: column; gap: 3px; max-width: 340px; }
.heat-row { display: flex; align-items: center; gap: 3px; }
.heat-row .hk { width: 34px; font-size: 12px; font-weight: 700; text-align: right;
                padding-right: 6px; }
.heat-row .hq { flex: 1; text-align: center; font-size: 10.5px; color: #6c5d81;
                font-weight: 700; letter-spacing: .6px; }
.heat-head { margin-bottom: 2px; }
.heat-row .hc {
    flex: 1; height: 26px; border-radius: 4px;
    display: flex; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 700; color: rgba(0,0,0,.65);
    cursor: default;
}
.heat-row .hc.band-none { color: #57496b; }

.weak-list { margin: 0; padding: 0; list-style: none;
             display: flex; flex-direction: column; gap: 7px; }
.weak-list li { display: flex; align-items: baseline; gap: 10px; font-size: 13.5px;
                color: #cfccd2; }
.weak-list .p { font-family: "JetBrains Mono", Consolas, monospace;
                color: #f1fa8c; width: 46px; }
.weak-list .why { color: #7a6b8f; font-size: 12.5px; }

/* ============= 2.b  the card notes play when clicked ==============
   Visibly clickable, not hover-revealed: a control that only appears on
   hover is one most people never find.                                  */
.tok .dot { cursor: pointer; transition: transform .08s, box-shadow .08s; }
.tok .dot:hover { transform: scale(1.07); box-shadow: 0 4px 14px rgba(0,0,0,.55); }
.tok .dot:active { transform: scale(.97); }
.tok .dot.sounding { box-shadow: 0 0 0 3px rgba(255,255,255,.9); }

.chip.playable { cursor: pointer; }
.chip.playable:hover { filter: brightness(1.15); }
.chip.sounding { box-shadow: 0 0 0 2px rgba(255,255,255,.9); }

.play-pair {
    background: none; border: 1px solid #473a5a; color: #988fa5;
    border-radius: 5px; font-size: 11px; font-weight: 700;
    padding: 1px 7px; margin-left: 6px; cursor: pointer;
    font-family: inherit;
}
.play-pair:hover { background: #312640; color: #eeedef; border-color: #6c5d81; }

.card .hint-play {
    font-size: 11.5px; color: #6c5d81; margin: -12px 0 16px; text-align: center;
}

/* ===================================================== apply (8th angle) */
.apply-panel { margin-top: 16px; display: flex; flex-direction: column;
               align-items: center; gap: 12px; }

.apply-chords { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.apply-chord {
    min-width: 104px; padding: 10px 14px; border-radius: 8px;
    background: #292036; border: 1px solid #3b2f4b;
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    transition: background .12s, border-color .12s, transform .12s;
}
.apply-chord .rn { font-size: 10px; color: #6c5d81; letter-spacing: .9px; }
.apply-chord .cn { font-size: 18px; font-weight: 700; color: #cfccd2; }
/* Reserves its own line, so a card does not jump when the answer appears. */
.apply-chord .tg { font-size: 11px; color: #7a6b8f; min-height: 14px; }

/* The chord sounding right now. */
.apply-chord.live { border-color: #fff; background: #3b2f4b; transform: translateY(-3px); }
.apply-chord.live .cn { color: #fff; }
/* You found the note over it. */
.apply-chord.hit { background: #2c5c34; border-color: #50fa7b; }
.apply-chord.hit .cn { color: #fff; }
.apply-chord.hit .tg { color: #b9e8bf; }
/* It went past without you. */
.apply-chord.missed { background: #4a2626; border-color: #ff5555; }
.apply-chord.missed .cn { color: #ffdcdc; }
.apply-chord.missed .tg { color: #ffb3b3; }

.apply-bar { width: 280px; height: 5px; background: #2e243c;
             border-radius: 3px; overflow: hidden; }
.apply-bar i { display: block; height: 100%; width: 0; background: var(--brand); }

.apply-actions { display: flex; gap: 8px; align-items: center; }

/* ================= 3.a  profiles: the floating menu ==================
   Fixed rather than in the header, so it is reachable from every tab and
   does not move when a tab changes height. */
.profile-menu { position: fixed; top: 12px; right: 14px; z-index: 60;
                font-size: 13px; }

/* Translucent, because it floats over the page rather than sitting in it and
   an opaque lozenge over a fretboard reads as a hole punched in the neck.

   The blur is what makes the transparency legible: without it, note dots
   passing underneath show through as hard shapes and the text on top becomes
   unreadable at exactly the moment you are looking at the board. With it,
   whatever is behind becomes a wash of the right colour and the pill still
   belongs to the page.

   backdrop-filter is unsupported in a few places; the rgba background is
   opaque enough on its own that they get a slightly darker pill and nothing
   worse.                                                              (D41) */
.profile-pill {
    display: flex; align-items: center; gap: 8px;
    background: rgba(44, 44, 44, .68);
    -webkit-backdrop-filter: blur(12px) saturate(1.3);
    backdrop-filter: blur(12px) saturate(1.3);
    border: 1px solid rgba(255,255,255,.13); color: #e0dfe1;
    font-weight: 600; font-size: 13px; padding: 6px 11px; border-radius: 999px;
    box-shadow: 0 4px 14px rgba(0,0,0,.45);
    transition: background .12s, border-color .12s;
}
/* Reaching for it is a good moment to stop being subtle. */
.profile-pill:hover { background: rgba(58,58,58,.92); border-color: rgba(255,255,255,.28); }
.profile-menu.open .profile-pill {
    background: rgba(58,58,58,.97); border-color: #6c5d81;
}
.profile-pill .dot { width: 11px; height: 11px; border-radius: 50%;
                     box-shadow: 0 0 0 1px rgba(0,0,0,.5) inset; }
.profile-pill .pc { color: #8b7f9b; font-weight: 700; font-size: 11.5px; }
.profile-pill .sep { color: #57496b; }
/* Named on the pill because with the tab strip gone nothing else on the page
   says where you are - and a menu that only shows a name is a menu nobody
   thinks to open when they want a different screen. */
.profile-pill .sec { color: #e0dfe1; }
.profile-pill .chev { color: #8b7f9b; font-size: 10px; }

.profile-panel {
    position: absolute; top: calc(100% + 8px); right: 0; width: 310px;
    /* Same trap as .card: 310px of CONTENT plus 10px padding either side is
       a 332px panel, which fits a 375px phone and runs off a 320px one.
       See the .card comment -- this stylesheet has now been bitten by it
       five times. */
    box-sizing: border-box;
    background: #241c30; border: 1px solid #423654; border-radius: 10px;
    box-shadow: 0 14px 40px rgba(0,0,0,.6); padding: 10px; cursor: default;
}
.pp-head { font-size: 10.5px; letter-spacing: 1px; text-transform: uppercase;
           color: #7a6b8f; font-weight: 700; margin: 2px 2px 8px; }

.pp-nav { display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
           margin-bottom: 11px; }
/* 11.c3 said Settings would be "set apart in the menu: the five above are
   places you go to do something, and this one is not" -- and then left it as
   the sixth cell of the grid. Its own row, with a rule above it, so the code
   comment and the screen agree. */
.pp-navbtn.pp-set { grid-column: 1 / -1;
    margin-top: 5px; padding-top: 11px;
    border-top: 1px solid #3b2f4b; border-radius: 6px; }
/* 19.33 - the print page, a row of its own under Settings, drawn as a link
   because it is one. */
.pp-navbtn.pp-link { grid-column: 1 / -1; display: block; text-decoration: none;
    color: #988fa5; font-weight: 600; background: transparent; }
.pp-navbtn.pp-link:hover { color: #e8e7e9; background: #382c48; }
/* Five activity tabs do not divide by two, so the fifth spans the row rather
   than sitting beside an empty cell. */
/* Written when there were five activity tabs and Progress was left alone on
   its row. With six there is nothing to straighten, and spanning the second-
   to-last would now stretch the Tuner across a row for no reason.   (11.q) */
.pp-navbtn {
    background: #2d233b; border: 1px solid #3b2f4b; color: #b7b2be;
    font-weight: 700; font-size: 13px; padding: 9px 10px; border-radius: 6px;
    text-align: center;
}
.pp-navbtn:hover { background: #382c48; color: #e8e7e9; }
.pp-navbtn.on {
    background: var(--brand-deep); border-color: var(--brand); color: #ffffff;
}

/* 11.c6 - THE LIST SCROLLS, THE REST DOES NOT. The panel grew with the
   number of profiles and had no max-height, so at fifteen profiles it was
   747px tall and ran 175px off the bottom of a 620px laptop screen -- with
   "Clear data" and the storage line unreachable, because the popover does not
   scroll either.

   Capping the LIST rather than the panel keeps the nav above it and the
   footer below it always visible: the two things you can always do are go
   somewhere and see how much space you are using, and neither should be
   behind a scroll. Roughly six profiles before it starts scrolling, which is
   more than anyone has and fewer than breaks the panel. */
.pp-list { display: flex; flex-direction: column; gap: 3px;
           max-height: 210px; overflow-y: auto; }

/* And a backstop for a genuinely short window, where even the nav and the
   footer together do not fit. */
.profile-panel { max-height: calc(100vh - 64px); overflow-y: auto; }
.pp-row  { display: flex; align-items: center; gap: 2px; border-radius: 6px; }
.pp-row.current { background: #30253f; }

/* The whole row is the switch target; the icons sit outside it so a
   mis-click on the cross can never also change who is practising. */
.pp-pick {
    flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
    background: none; border: none; color: #d8d6da; font-weight: 600;
    font-size: 13px; padding: 7px 8px; border-radius: 6px; text-align: left;
}
.pp-pick:hover { background: #382c48; }
.pp-pick .dot { width: 11px; height: 11px; border-radius: 50%; flex: none;
                box-shadow: 0 0 0 1px rgba(0,0,0,.5) inset; }
.pp-pick .nm  { flex: 1; min-width: 0; overflow: hidden;
                text-overflow: ellipsis; white-space: nowrap; }
.pp-pick .sc  { color: #cfccd2; font-weight: 700; font-size: 11.5px; flex: none; }
.pp-pick .ev  { color: #6c5d81; font-weight: 400; font-size: 10.5px;
                flex: none; min-width: 62px; text-align: right; }
.pp-row.current .pp-pick { color: #fff; }

.pp-ico {
    background: none; border: none; color: #7a6b8f; font-size: 14px;
    padding: 6px 7px; border-radius: 5px; font-weight: 700; line-height: 1;
}
.pp-ico:hover { background: #382c48; color: #e0dfe1; }
.pp-ico.danger:hover { background: #4a2626; color: #ff9b9b; }
.pp-ico:disabled { opacity: .25; }
.pp-ico:disabled:hover { background: none; color: #7a6b8f; }

.pp-new { display: flex; gap: 6px; margin-top: 9px; }
.pp-new input {
    flex: 1; min-width: 0; background: #1e1628; border: 1px solid #423654;
    color: #e0dfe1; border-radius: 6px; padding: 6px 9px; font-size: 13px;
    font-family: inherit;
}
.pp-new input:focus { outline: none; border-color: #6c5d81; }
.pp-new button { padding: 6px 12px; }

.pp-err { margin-top: 8px; font-size: 12px; color: #ff9b9b;
          background: #3a2222; border-radius: 5px; padding: 6px 8px; }

.pp-foot { display: flex; align-items: center; gap: 8px; margin-top: 10px;
           padding-top: 9px; border-top: 1px solid #382c48; }
.pp-foot .data-stat { flex: 1; min-width: 0; font-size: 11.5px; color: #7a6b8f; }
.pp-foot .data-stat.warn { color: #ffb86c; }
.pp-foot button { font-size: 12px; padding: 5px 9px; }

/* IT USED TO DROP INTO THE FLOW HERE, AND NO LONGER DOES.            (D41)
   The rule it replaced said "on a narrow window the pill would sit over the
   tab strip". That tab strip is gone -- navigation moved INTO this menu --
   so the reason went with it and only the cost was left: a fixed-height row
   at the top of a phone screen, on the one device where vertical space is
   the thing in shortest supply.
   It floats at every width now. What it must not do is land on the chord
   name, which is why it goes compact below. */
@media (max-width: 720px) {
    .profile-menu { top: 8px; right: 8px; }

    /* ON A PHONE THE PILL IS JUST THE DOT.
       The chord title is centred and can be 263px wide at its worst
       (Db7 - dominant seventh). Measured at 375px: with the name shown the
       pill starts at x=299 and the title ends at x=319, so they overlap by
       20px and the pill lands on the word "seventh". With the percentage
       shown it is still tight.
       So the phone gets the dot and the chevron. The twelve hues exist to
       identify a profile without reading it, which is exactly the job here,
       and the name, the percentage and everything else are one tap away. */
    .profile-pill .nm, .profile-pill .pc { display: none; }
    .profile-pill { padding: 6px 8px; gap: 5px; }
    .profile-pill .dot { width: 13px; height: 13px; }

    /* 310px does not fit beside an 8px margin on the narrowest phones. */
    .profile-panel { width: min(310px, calc(100vw - 16px)); }
}

/* ================= the up-close carousel ========================
   The three prose sections used to share a three-column grid, so each got a
   third of the width and none could hold a picture. One at a time, full
   width, is what made the diagram panels possible. */
.carousel { margin-top: 4px; }
.cx-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.cx-title { flex: 1; margin: 0; font-size: 14px; color: #cfccd2;
            letter-spacing: .6px; text-transform: uppercase; }
.cx-count { font-size: 11px; color: #6c5d81; font-family: "JetBrains Mono", Consolas, monospace; }
.cx-nav {
    background: #2d233b; border: 1px solid #423654; color: #cfccd2;
    font-size: 17px; line-height: 1; padding: 3px 11px 5px; border-radius: 6px;
}
.cx-nav:hover { background: #382c48; color: #fff; }

/* Tall enough for the biggest panel, so paging through does not make the
   page jump under the mouse you are clicking with. Centred, because every
   panel is one column now rather than a third of a three-column grid. */
.cx-stage { min-height: 208px; text-align: center; }
/* Scoped to both places that use it. It was only bound inside the carousel,
   so the songs blurb inherited body defaults and came out 16px near-white -
   louder than the song titles it is meant to introduce. */
.cx-stage .note, .songs .note {
    color: #8b7f9b; font-size: 13.5px; line-height: 1.65;
    margin: 0 auto 14px; max-width: 660px;
    /* D151 - `.note` is also the fretboard's class for a note, and that rule
       starts every note at opacity 0 until the board lights it. This prose
       inherited the zero and the blurb under each songs heading has been an
       invisible paragraph of exactly the right height since it was written. */
    opacity: 1; pointer-events: auto;
}
.cc-prose { color: #d0cdd3; font-size: 16.5px; line-height: 1.8;
            margin: 0 auto; max-width: 640px; }
/* No bullets: a centred bullet list hangs its markers in mid-air. The gap
   between items does the separating instead. */
.cc-list { margin: 0 auto; padding: 0; list-style: none; color: #d0cdd3;
           font-size: 15.5px; line-height: 1.85; max-width: 700px; }
.cc-list li { margin-bottom: 16px; }
.cc-list li:last-child { margin-bottom: 0; }

/* A chord or note named inside a sentence, wearing the colour it wears on
   the board. Larger too, so it is findable without reading the sentence. */
.named { font-weight: 700; font-size: 1.14em; }

.cx-dots { display: flex; gap: 6px; justify-content: center; margin-top: 14px; }
.cx-dot { width: 8px; height: 8px; padding: 0; border-radius: 50%;
          background: #3b2f4b; border: none; }
.cx-dot:hover { background: #57496b; }
.cx-dot.on { background: #cfccd2; }

/* --- where this chord lives --- */
.lives { list-style: none; margin: 0; padding: 0; }
.lives { max-width: 660px; margin: 0 auto; }
.lives li { display: flex; align-items: center; justify-content: center;
            gap: 12px; flex-wrap: wrap;
            padding: 9px 0; border-bottom: 1px solid #2e243c; }
.lives li:last-child { border-bottom: none; }
.lv-num { font-weight: 700; color: #f1fa8c; font-size: 15px; min-width: 30px;
          font-family: "JetBrains Mono", Consolas, monospace; }
.lv-key { color: #c7c4cc; font-size: 15px; }
.lv-prog { display: flex; gap: 5px; flex-wrap: wrap; }

/* --- the same chord elsewhere --- */
.mb-row { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; }
.mb-cell { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.mb-cell span { font-size: 10.5px; color: #6c5d81; letter-spacing: .4px; }
.mini-board { width: 210px; height: auto; border-radius: 4px; }
.mini-board .mb-n { font-size: 6px; font-weight: 700; text-anchor: middle; }
.mini-board .mb-f { font-size: 7px; fill: #8b7f9b; font-weight: 700; }

/* --- stacked thirds --- */
.stack { display: inline-flex; flex-direction: column; gap: 0; }
.stack { text-align: left; }
.st-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; }
.st-deg { font-family: "JetBrains Mono", Consolas, monospace; font-size: 12px;
          color: #8b7f9b; min-width: 24px; }
.st-role { color: #7a6b8f; font-size: 13.5px; }
.st-gap { display: flex; align-items: center; gap: 6px; font-size: 11px;
          color: #6c5d81; padding-left: 34px; }
.st-gap span { color: #f1fa8c; font-weight: 700; }

/* --- keyboard --- */
.keyboard { width: 100%; max-width: 460px; height: auto;
             margin: 0 auto; display: block; }
.keyboard .kb-w { fill: #d8d6da; stroke: #191222; stroke-width: 1; }
.keyboard .kb-b { fill: #20192b; stroke: #191222; stroke-width: 1; }
.keyboard .kb-t { font-size: 10px; font-weight: 700; text-anchor: middle; }

/* ================= songs ======================================= */
.songs { margin-top: 16px; }
.songs h4 { margin: 0 0 4px; font-size: 14px; color: #cfccd2;
            letter-spacing: .6px; text-transform: uppercase; }
.sg-block { margin-top: 14px; }
.sg-block h5 { margin: 0 0 6px; font-size: 12.5px; color: #f1fa8c; font-weight: 700; }
/* One column, centred. Three columns made it a directory to scan; one makes
   it a list to read, which is what it is for. */
.songs { text-align: center; }
.sg-block ul { list-style: none; margin: 0 auto; padding: 0; max-width: 620px; }
.sg-block li { font-size: 14.5px; color: #c7c4cc; padding: 5px 0; }
.sg-block li b { font-weight: 700; }
.sg-block li .who { color: #8b7f9b; }
.sg-block li .why { color: var(--accent-1); font-size: 11.5px; }
.sg-foot { margin: 14px 0 0; font-size: 11.5px; color: #6c5d81; }

@media (max-width: 620px) {
    .cx-stage { min-height: 250px; }
    .mini-board { width: 100%; max-width: 260px; }
}

/* The sync row in the profile menu. Hidden entirely when there is no
   server to sync with, so a local-only install looks untouched.      (D37) */
.pp-sync {
    margin: 10px 0 0; padding: 9px 11px; border-radius: 7px;
    background: #1d1527; border: 1px solid #2e243c;
    font-size: 11.5px; line-height: 1.5; color: #877b99;
}
.pp-sync.warn { background: #241f19; border-color: #4a3a28; color: #c4b49c; }
.pp-sync a {
    color: #e67e22; font-weight: 700; text-decoration: none;
    white-space: nowrap;
}
.pp-sync a:hover { text-decoration: underline; }

/* ================= the chord transition ==========================
   The last beat before the loop changes chord, both chords are on the neck
   at once: what is leaving fades, what is arriving appears, and what is in
   BOTH does neither.                                          (D44, 11.a2)

   The held notes are the whole point. Half of the 1,260 transitions this
   loop can produce hold exactly one note and one in six holds none, so the
   thing worth seeing is not "most of it stays" -- it is WHICH note carries
   you across, or that nothing does.

   Opacity only, on .note. The dot's fill, ring and text are all set as SVG
   attributes by dressDot(), so fading the group is the one change that does
   not fight the painter. */
/* AN ARRIVING NOTE IS NOT SOUNDING YET, AND SAYS SO.                (D44e)

   Two full-colour chords on one neck is too much colour to read -- twelve
   hues doing their job twice over, and no way to tell at a glance which set
   your hands are actually on.

   So the incoming chord is drawn in the language this board ALREADY uses for
   "here, but not the one being played": the greyed unison twin. Same grey
   body, same shrink -- though not the dashed ring, which came off in D44l:
   at .38 the dashes were competing with the label for a very small circle,
   and grey plus small already says "not this one yet".

   What is left in colour is exactly what is sounding: the chord under your
   hands, fading, and the notes that hold. */
/* THE LEAVING NOTES ARE STILL SOUNDING.                            (D44i)
   advanceLoop() does not swap the chord until the bar line AFTER the run-in,
   so for these two beats every "leaving" note is under your fingers. Dimming
   them to .3 said "this is going away" while you were still playing it -- and
   could dim a note at the exact moment it got plucked.

   The .3 was never really a judgement about leaving notes anyway: it was set
   when the arriving ghost was a near-solid .8 and the current chord had to
   yield to it. At .52 and grey the ghost no longer competes, so nothing needs
   to get out of its way. */

/* .38, down from .8 originally and .52 after the first pass. A grey disc at
   .8 is as solid as a real note: the chord you have NOT played yet ends up
   more present than the one under your hands, which is backwards. At .38 it
   stops reading as a chord and starts reading as a suggestion -- what carries
   it now is the GROWTH (see noteArriveInner), because motion survives low
   opacity far better than a static shape does. */
.note.arriving { opacity: .38; }

/* Lighter than the unison twin's #454545 on purpose. That twin is a thing
   you are meant to look past; this one you are meant to read, because it is
   where your hand goes next. The ring is lighter than the disc because at
   this opacity WHERE matters more than WHICH -- the dashes survive the fade
   even while the label is still resolving. */
.note.arriving .body  { fill: #645579 !important; stroke: none !important; }
/* A ghost keeps a lighter outline: the full-strength one made it read as
   more present than the chord actually sounding. */
.note.arriving text   { fill: #e0dfe1 !important; stroke: rgba(0,0,0,.3) !important; }
.note.arriving .inner { transform: scale(.88); }

/* Duration comes from the tempo -- app.js sets --xfade-ms when a transition
   starts, so the fade is the same FRACTION OF A BEAT at every speed instead
   of a fixed number of milliseconds that a fast tempo cuts off. */

/* KEYFRAMES, NOT A TRANSITION, for the arriving note.              (D44h)
   A transition can only run from whatever the note already was, and an
   arriving note sits at the board's base scale(1) -- so the old code SHRANK
   it from full size down to .88 while the comment claimed it grew. Keyframes
   let the note start genuinely small and settle out, which is what
   materialising looks like. The leaving note keeps a plain transition: it
   has a real from-state, because it is already on the neck. */
@keyframes noteArrive      { from { opacity: 0; }            to { opacity: .38; } }
@keyframes noteArriveInner { from { transform: scale(.42); } to { transform: scale(.88); } }

/* Full brightness for most of the run-in, easing back only as the change
   actually lands. The halo on a held note already says "this one stays", so
   the fade is a second voice on the same fact -- it can afford to be late and
   slight. It bottoms out above the arriving ghost, because a note that is
   sounding should never sit behind one that is not. */
@keyframes noteLeave      { 0%, 58% { opacity: 1; }          100% { opacity: .62; } }
/* The shrink mirrors the arriving note's growth and starts at the same instant
   the dimming does, so a note recedes on both axes at once instead of dimming
   at full size. It bottoms out at .86, a hair under the arriving note's .88 --
   at the moment of the swap the two are peers trading places. Which one is
   SOUNDING stays an opacity question (.62 against .52), not a size one. */
@keyframes noteLeaveInner { 0%, 58% { transform: scale(1); } 100% { transform: scale(.86); } }

.note.leaving {
    animation: noteLeave var(--xfade-ms, 700ms) cubic-bezier(.45, .02, .3, 1) both;
}
.note.leaving .inner {
    animation: noteLeaveInner var(--xfade-ms, 700ms) cubic-bezier(.45, .02, .3, 1) both;
}
/* Whatever is being plucked right now is never dimmed and never shrunk,
   leaving or not. Both need !important: an author declaration outranks an
   animation, and without it the keyframes above would suppress the pluck pop
   (.note.playing .inner is a plain rule) -- the note would go quiet on screen
   at the exact moment it sounded. */
.note.leaving.playing        { opacity: 1 !important; }
.note.leaving.playing .inner { transform: scale(1.3) !important; }

/* THE SWAP WAS THE ONE UNOWNED MOMENT.                             (D44k)
   When advanceLoop() repaints, the leaving/arriving classes come off and
   their animations stop applying in the same frame -- so opacity fell from
   .62 straight to 0 with nothing bridging it. Measured: flat .62 for 480ms,
   then 0.00 on the very next frame. Everything before it was smooth, which is
   exactly why the cut read as the animation "breaking".

   A departing note now finishes the gesture it was already making: it keeps
   shrinking as it goes, rather than being switched off at full size. The
   :not(.playing) guard matters -- without it the 200ms ramp would smear the
   pluck highlight, which toggles many times a second. */
@keyframes noteDepart      { from { opacity: .62; }           to { opacity: 0; } }
@keyframes noteDepartInner { from { transform: scale(.86); } to { transform: scale(.36); } }

.note.departing        { animation: noteDepart      200ms ease-out both; }
.note.departing .inner { animation: noteDepartInner 200ms ease-out both; }
.note.arriving {
    animation: noteArrive var(--xfade-ms, 700ms) cubic-bezier(.45, .02, .3, 1) both;
}
.note.arriving .inner {
    animation: noteArriveInner var(--xfade-ms, 700ms) cubic-bezier(.45, .02, .3, 1) both;
}

/* The halo on a held note comes up over the same span, so "this one stays"
   arrives with the fade rather than snapping on ahead of it. */
.note.holding .body { transition: filter var(--xfade-ms, 700ms) ease-in-out; }

/* A held note gets a halo rather than a change in brightness, because
   brightness is already carrying "is this sounding right now". */
.note.holding .body { filter: drop-shadow(0 0 5px rgba(255,255,255,.9)); }

/* THE DEGREE FLIP.                                                 (D44m)
   A held note's function changes even though its position does not, and that
   is the single most useful thing this transition can teach. It used to be
   written "R->5" -- both values at once, but three characters crammed into a
   circle sized for one, which cost every held note a drop to 7.4px.

   Told over time instead: the old degree shrinks to nothing across the run-in,
   the slot sits empty for a beat (which itself reads as "about to change"),
   and the new value explodes in exactly at the swap. The label is therefore
   never wrong -- it shows the current function or it shows nothing, and the
   next function appears only once it is true.

   Only the degree moves. The dot and the note name hold perfectly still,
   because that circle marks a fret a finger is on. */
.note .dg { transform-box: fill-box; transform-origin: center; }

/* OPACITY DOES THE DISAPPEARING, NOT SCALE.                        (D67)
   Scaling a stroked glyph toward zero rasterises unstably: measured, the
   degree's transform fell smoothly -- two direction changes across 296
   frames -- while its RENDERED top oscillated 25 times. One label doing that
   is a shimmer; every held note doing it at once, in the last two beats
   before a chord change, is the board visibly shaking.

   Stopping at .72 keeps the glyph big enough to rasterise consistently, and
   the fade takes it the rest of the way. The movement reads the same. */
@keyframes degShrink  { 0%, 45% { transform: scale(1);   opacity: .92; }
                        100%    { transform: scale(.72); opacity: 0; } }
/* The overshoot is what makes it read as an arrival rather than a fade-in. */
@keyframes degExplode { 0%   { transform: scale(.72); opacity: 0; }
                        45%  { opacity: .92; }
                        60%  { transform: scale(1.25); }
                        100% { transform: scale(1); opacity: .92; } }

.note.flipping .dg {
    animation: degShrink var(--xfade-ms, 700ms) cubic-bezier(.45, .02, .3, 1) both;
}
.note.deg-explode .dg { animation: degExplode 180ms cubic-bezier(.2, .7, .3, 1) both; }

/* 🔴 Motion is optional; the information is not. Under reduced motion the
   swap is instant and the rings still mark what held.              (11.b9) */
@media (prefers-reduced-motion: reduce) {
    .note.leaving, .note.leaving .inner, .note.departing, .note.departing .inner,
    .note.arriving, .note.arriving .inner,
    /* The degree simply changes at the swap. Nothing is lost: the flip only
       ever animated a value the instant swap still delivers correctly. */
    .note.flipping .dg, .note.deg-explode .dg { animation: none; }
    .note.arriving { opacity: 0; }
    .note.leaving  { opacity: 1; }
}

/* ================= the drill guide ==============================
   Nine tabs on the Drills tab: the eight angles, and Mixed. The angle
   panels are teaching -- what the angle asks, a picture of the question,
   and why it is not the same as the one next to it. Mixed is the one you
   train on, so it leads.                                    (D25, D33) */

.ag-tabs {
    display: flex; flex-wrap: wrap; gap: 5px;
    margin: 4px 0 14px; padding-bottom: 12px;
    border-bottom: 1px solid #312640;
}
.ag-tab {
    padding: 6px 12px; border-radius: 6px; font-size: 12.5px;
    background: #281f35; border: 1px solid #362a46; color: #988fa5;
    cursor: pointer; font-weight: 600;
}
.ag-tab:hover { background: #322741; color: #d0cdd3; }
.ag-tab.on {
    background: #37414d; border-color: var(--accent-1); color: #fff;
}
/* Mixed is the recommendation, so it reads as the one to be on. */
.ag-tab[data-angle="MIX"] { color: #e2b07a; }
.ag-tab[data-angle="MIX"].on { background: #4a3a28; border-color: #e67e22; color: #fff; }

.ag-panel { display: none; }
.ag-panel.on { display: block; }

.ag-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.ag-head h4 { margin: 0; font-size: 19px; color: #eeedef; }
.ag-weight {
    font-size: 10.5px; letter-spacing: 1px; text-transform: uppercase;
    color: #7c6d91; font-weight: 700; cursor: help;
}
.ag-needs-mic {
    font-size: 10.5px; letter-spacing: .8px; text-transform: uppercase;
    color: #b08a5a; font-weight: 700;
}

.ag-what {
    margin: 8px 0 14px; font-size: 14.5px; line-height: 1.65; color: #cfccd2;
    max-width: 62ch;
}

.ag-why {
    margin-top: 14px; padding: 12px 14px;
    background: #1d1527; border-left: 2px solid var(--accent-1); border-radius: 0 7px 7px 0;
}
.ag-why-k {
    display: block; font-size: 10px; letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--accent-1); font-weight: 700; margin-bottom: 5px;
}
.ag-why p { margin: 0; font-size: 13.2px; line-height: 1.65; color: #a7a0b0; max-width: 62ch; }
.ag-why em, .ag-what em { color: #d8d6da; font-style: normal; font-weight: 600; }

/* --- the diagrams: a still of the question, not of the answer --- */
.ag-diagram {
    background: #1b1424; border: 1px solid #2e243c; border-radius: 9px;
    padding: 16px 14px; margin: 4px 0 2px;
}
.ag-row { display: flex; align-items: center; justify-content: center;
          gap: 14px; flex-wrap: wrap; }
.ag-arrow { color: #67587c; font-size: 19px; }

.ag-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.ag-chip {
    padding: 5px 10px; border-radius: 6px; font-size: 12px; font-weight: 700;
    background: #281f35; border: 1px solid #382c48; color: #877b99;
    font-family: "JetBrains Mono", Consolas, monospace;
}
.ag-chip.on { background: var(--brand-deep); border-color: var(--brand); color: var(--brand-soft); }

.ag-toks { display: flex; align-items: center; gap: 5px; }
.ag-toks.wrap { flex-wrap: wrap; justify-content: center; }
.ag-tok {
    display: inline-flex; flex-direction: column; align-items: center;
    justify-content: center; width: 34px; height: 34px; border-radius: 50%;
    font-size: 12px; font-weight: 700; line-height: 1;
    box-shadow: 0 2px 6px rgba(0,0,0,.4);
}
.ag-tok i { font-style: normal; font-size: 7.5px; opacity: .8; }
.ag-tok.blank { background: #2a2137; border: 1px dashed #4b3e5e; color: #67587c; }
.ag-tok.moved { box-shadow: 0 0 0 2px rgba(230,126,34,.85); }
.ag-drop { color: #e67e22; font-weight: 700; margin: 0 1px; }

.ag-symbol {
    font-family: "JetBrains Mono", Consolas, monospace;
    font-size: 20px; font-weight: 700; color: #e67e22;
}
.ag-ask { font-size: 14px; color: #cfccd2; }
.ag-ask b { color: #e67e22; }

/* Recall's whole point is what is NOT there, so the emptiness is drawn. */
.ag-empty {
    padding: 14px 26px; border: 1px dashed #473a5a; border-radius: 8px;
    background: #181220;
}

.ag-mic { display: inline-flex; align-items: center; gap: 6px;
          color: #b08a5a; font-size: 12px; font-weight: 700; }

.ag-wave { width: 110px; height: 30px; }
.ag-wave rect { fill: var(--accent-1); }

.ag-absent {
    margin-top: 10px; text-align: center;
    font-size: 11.5px; color: #77688c; font-style: italic;
}

/* Apply happens in time, so its diagram is a timeline rather than a neck. */
.ag-timeline { position: relative; height: 52px; }
.ag-bar {
    position: absolute; top: 0; bottom: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    background: #241c30; border: 1px solid #382c48; border-radius: 6px;
    margin-right: 3px;
}
.ag-bar b { font-family: "JetBrains Mono", Consolas, monospace;
            font-size: 12.5px; color: #e8e7e9; }
.ag-bar i { font-style: normal; font-size: 9.5px; color: #77688c; }
.ag-guide {
    position: absolute; bottom: -5px; width: 9px; height: 9px;
    border-radius: 50%; background: #e67e22;
    box-shadow: 0 0 0 2px #1b1424;
}

/* --- the per-angle mastery bar --------------------------- (D25 = B) */
.ag-track {
    flex: 1; min-width: 60px; height: 7px; border-radius: 4px;
    background: #2c223a; overflow: hidden; display: block;
}
.ag-track i { display: block; height: 100%; background: var(--brand); border-radius: 4px; }
.ag-track.weak i { background: #d0a12a; }
.ag-track.bad  i { background: #b3563f; }
.ag-track.done i { background: var(--accent-1); }

.ag-mastery { margin-top: 16px; }
.ag-m-row { display: flex; align-items: center; gap: 10px; }
.ag-m-k { font-size: 12px; color: #988fa5; min-width: 92px; }
.ag-m-v { font-family: "JetBrains Mono", Consolas, monospace;
          font-size: 13px; font-weight: 700; color: #e8e7e9; min-width: 38px; text-align: right; }
.ag-m-n { font-size: 11px; color: #6c5d81; min-width: 82px; }
.ag-m-empty { font-size: 12.5px; color: #77688c; font-style: italic; }

/* --- what is slipping --- */
.ag-fading {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin: 0 0 16px; padding: 10px 13px;
    background: #241f19; border: 1px solid #4a3a28; border-radius: 8px;
    font-size: 13px; color: #c4b49c;
}
.ag-fading.off { display: none; }
.ag-fade-n {
    font-family: "JetBrains Mono", Consolas, monospace;
    font-size: 19px; font-weight: 700; color: #e67e22;
}
.ag-fade-ok { color: #8fdc94; font-weight: 700; }

/* --- challenges ------------------------------------------ (D25 = D) */
.ag-challenges { margin-top: 20px; }
.ag-ch-k {
    font-size: 10px; letter-spacing: 1.4px; text-transform: uppercase;
    color: var(--accent-1); font-weight: 700; margin-bottom: 10px;
}
.ag-ch {
    padding: 10px 12px; margin-bottom: 8px;
    background: #1d1527; border: 1px solid #2e243c; border-radius: 8px;
}
.ag-ch.done { border-color: #3f5a41; background: #1c231d; }
.ag-ch-top { display: flex; align-items: baseline; gap: 10px; margin-bottom: 7px; }
.ag-ch-n { font-size: 13.5px; font-weight: 700; color: #dddbdf; }
.ag-ch.done .ag-ch-n { color: #8fdc94; }
.ag-ch-p {
    margin-left: auto; font-family: "JetBrains Mono", Consolas, monospace;
    font-size: 11.5px; color: #877b99; white-space: nowrap;
}
.ag-ch-p em { font-style: normal; color: #6c5d81; }
.ag-ch-b { margin-top: 7px; font-size: 12px; color: #7c6d91; line-height: 1.55; }

.ag-go { margin-top: 18px; padding-top: 16px; border-top: 1px solid #312640; }

/* --- the summary, after a session ------------------------------- */
.sum-earned, .sum-moved { margin-top: 18px; }
.sum-earned.off, .sum-moved.off { display: none; }
.sum-earned-k, .sum-moved-k {
    font-size: 10px; letter-spacing: 1.4px; text-transform: uppercase;
    color: var(--accent-1); font-weight: 700; margin-bottom: 9px;
}
.sum-badge {
    padding: 10px 13px; margin-bottom: 7px; border-radius: 8px;
    background: #1c231d; border: 1px solid #3f5a41;
}
.sum-badge b { display: block; font-size: 14px; color: #8fdc94; margin-bottom: 3px; }
.sum-badge span { font-size: 12px; color: #8a9a8b; line-height: 1.55; }

.sum-moved-row {
    display: flex; align-items: center; gap: 10px;
    font-size: 12.5px; padding: 4px 0; color: #988fa5;
}
.sum-moved-row .nm { min-width: 76px; color: #cfccd2; }
.sum-moved-row .d {
    font-family: "JetBrains Mono", Consolas, monospace;
    font-weight: 700; min-width: 40px; text-align: right; color: #77688c;
}
.sum-moved-row .d.up { color: #6fcf74; }
.sum-moved-row .d.down { color: #c9705a; }
.sum-moved-row .to { color: #6c5d81; font-size: 11.5px; }

/* ================= the phone ====================================
   Written against a real 375px viewport rather than a guess, and the
   measurement that drove it: the board -- the only thing on this page you
   look at while your hands are busy -- started 292px down a 831px screen.
   You had to scroll, one-handed, holding a guitar, to see the thing the
   app is for.

   Nothing here is a redesign. It is the same layout with the furniture
   above the board cut down, because on a phone that furniture is what
   stands between you and the neck:

     #chordLine  wrapped to two lines at 34px and cost 91px on its own
     #chordTag   five mood words, worth keeping, not worth 13.5px
     .mini-spell a reminder of the spelling; the card at the bottom is
                 where you actually go to read it

   Result: the board clears the fold, and the chord buttons and transport
   come with it. */
@media (max-width: 620px) {
    body { padding-top: 12px; }

    /* 23px, and a max-width, because the line used to read "Db7 - dominant
       seventh" and the arithmetic was about keeping that off the floating
       pill: 252px of text inside a 255px box, where 24px measured 263 and
       cleared the pill by 4px, which is luck rather than clearance.  (D41)

       THE LINE IS THE SYMBOL ALONE NOW, so that whole constraint is gone.
       The widest thing it can hold is something like "Dbm7b5" -- six
       characters against twenty-two -- and the words it used to carry sit
       below with the moods. 30px, and the max-width stays only as a
       backstop, since nothing can reach it. A comment describing a
       constraint that no longer exists is worse than no comment. */
    #chordLine {
        font-size: 30px; margin-bottom: 2px; letter-spacing: -.3px;
        max-width: calc(100% - 88px);
    }
    #chordTag  { margin-bottom: 8px; }

    /* THE BADGE FLOATS OVER THE FIRST LINE OF CONTENT. It is fixed at the
       top so it does not push the page down, which is right -- but the title
       is centred, and once the title became just "Cm7" it was narrow enough
       to sit entirely INSIDE the badge's span rather than sticking out
       either side of it. It had always been underneath; it had simply been
       wide enough to look present.

       The clearance is paid only when a routine is actually loaded, which is
       what :has is for: free practice keeps every pixel the media query
       above was written to save.

       PADDING, NOT MARGIN. margin-top collapsed straight out through the
       tabpanel and moved nothing at all -- computed style said 38px while
       the heading sat exactly where it had been, which is the most confusing
       way for a rule to fail. Padding cannot collapse. */
    body:has(#activeRoutine:not([hidden])):not(.focused) #chordLine
        { padding-top: 38px; }
    #chordTag .ct-full  { font-size: 12px; }
    #chordTag .ct-moods { font-size: 11px; }

    .mini-spell { margin-bottom: 9px; gap: 1px; }
    .mini-spell .tok .dot { width: 28px; height: 28px; }
    .mini-spell .tok .dot .n { font-size: 11px; }
    .mini-spell .tok .dot .d { font-size: 6.4px; }
    .mini-spell .gap { min-width: 11px; font-size: 7.5px; }

    .chord-pick { margin-bottom: 8px; }

    /* The preview costs vertical space on the one screen with none to spare,
       so the caption and the chord name share a line instead of stacking.
       A row with wrap, not `display: inline` -- these are flex children and
       flex ignores inline outright, which is why the first attempt did
       nothing at all. */
    .next-up {
        flex-direction: row; flex-wrap: wrap;
        align-items: baseline; justify-content: center;
        padding: 7px 9px 8px; gap: 2px 7px;
    }
    .nu-name { font-size: 15px; }
    .next-up .nu-board, .next-up .nu-note { flex: 1 1 100%; }

    /* The card is a reference, not a control: it can afford to be quieter
       here, and every pixel it gives back is a pixel above the fold. */
    .card { padding: 16px 14px 18px; margin-top: 14px; }
}

/* ================= touch targets =================================
   Measured on a 375px viewport: 16 checkboxes 19-21px tall, carousel dots
   at 8x8, the repeat toggles at 34x28. All comfortable with a mouse, all
   a nuisance with a thumb.

   These grow the TOUCH area and move nothing you can see. A transparent
   ::after, centred on the control and larger than it, is part of the
   button for hit-testing but takes no space in layout -- so the design is
   untouched and the target is not.

   THE SIZES ARE CAPPED BY THE GAPS, not by a guideline. The dots sit on a
   14px pitch, so a 44px circle would swallow its neighbours and you would
   reliably tap the wrong one -- worse than the small target it replaced.
   Each hit area below is the largest that cannot steal a neighbour's tap,
   which is why several fall short of the 44px everyone quotes. Reaching
   44px across the board needs more room between things, and that is a
   layout change rather than this. */
@media (max-width: 620px) {
    .cx-dot, .cx-nav, .rpt, .subtle,
    label:has(> input[type="checkbox"]) { position: relative; }

    .cx-dot::after, .cx-nav::after, .rpt::after, .subtle::after,
    .keybtn::after,
    label:has(> input[type="checkbox"])::after {
        content: ''; position: absolute;
        left: 50%; top: 50%; transform: translate(-50%, -50%);
        width: 100%; height: 100%;
        /* No background, no border: this is a hit area, not a decoration. */
    }

    /* 14px is the dot pitch exactly -- 8px wide plus its 6px gap. Vertical
       has nothing adjacent, so it takes what it likes. */
    .cx-dot::after { width: 14px; height: 36px; }

    /* 254px from its partner and alone on its row. Nothing to collide with. */
    .cx-nav::after { width: 44px; height: 44px; }
    .rpt::after    { width: 44px; height: 44px; }

    .subtle::after { min-width: 44px; height: 34px; }

    /* Rows of keys are 14px apart, so 42px extends 6px each side and stops
       1px short of the neighbour above. */
    .keybtn::after { width: 100%; height: 42px; }

    /* :has() and the child combinator on purpose: a label wrapping a SELECT
       must not get one of these. The overlay would sit over the select and
       swallow the tap that opens it, and Pattern, Backing and Tempo would
       all quietly stop working on the one device this block exists for. */
    label:has(> input[type="checkbox"])::after { height: 31px; }
}

/* ====================================================================
   4.7 — THE SHAPE TRAINER
   ==================================================================== */

/* The launcher sits under the drill setup as its own card, because it is a
   different KIND of thing: the drill session asks twenty questions and this
   teaches one grip until you can play it. Hidden while it is running, the
   same way the drill setup is. */
.shape-setup {
    box-sizing: border-box;
    width: 100%;
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid #382c48;
}
.shape-setup h3 { margin: 0 0 4px; }

.shape-run { width: 100%; display: none; flex-direction: column;
             align-items: center; gap: 14px; }
.shape-run.on { display: flex; }

.shape-actions { display: flex; flex-wrap: wrap; gap: 10px;
                 justify-content: center; margin-top: 14px; }

/* ONE PIP A BEAT, so a run reads as a shape in time rather than as a number.
   The rest is drawn hollow rather than left out: it is a beat of the
   exercise -- the one that makes every pass start on a downbeat -- and
   omitting it would teach the wrong length. */
.beat-strip { display: flex; flex-wrap: wrap; gap: 7px;
              justify-content: center; margin-top: 12px; min-height: 12px; }
.beat-strip .bs {
    width: 11px; height: 11px; border-radius: 50%;
    background: #3b2f4b; border: 1px solid #473a5a;
    transition: background .12s, transform .12s;
}
.beat-strip .bs.rest { background: transparent; border-style: dashed; }
.beat-strip .bs.done { background: #68597d; }
.beat-strip .bs.now  { background: var(--brand); border-color: var(--brand);
                       transform: scale(1.35); }

/* The count-in, and the message when a run breaks. Big, because it is the
   only thing on screen you need at that moment, and centred over the board
   rather than beside it so it cannot be missed while you are looking at
   your hands. */
/* 18.3 — the line trainer's one-line readout: what phase, which chord, what
   to do. One row because all three change together and reading them as three
   separate things would be three places to look while you are playing. */
.line-head {
    display: flex; align-items: baseline; justify-content: center;
    gap: 14px; flex-wrap: wrap; margin: 10px 0 4px; min-height: 26px;
}
.line-phase  { font-size: 12px; letter-spacing: 1.4px; text-transform: uppercase;
               color: #7c6d91; font-weight: 700; }
.line-chord  { font-size: 21px; font-weight: 700; color: var(--brand); }
.line-prompt { font-size: 13px; color: #988fa5; }
.line-score  { font-size: 12px; color: #7c6d91; margin-left: 10px; }

.shape-count {
    font-size: 34px; font-weight: 800; line-height: 1;
    text-align: center; color: #f1fa8c; min-height: 38px; margin-top: 10px;
}
.shape-count.over {
    font-size: 17px; font-weight: 700; color: #ff5555;
    letter-spacing: .01em;
}

/* ====================================================================
   THE MARK
   ==================================================================== */

/* Fixed in the corner and inert. Everything else pinned to an edge of this
   app does something -- the routine badge clears a routine, the profile pill
   opens a menu -- so the one thing that does not must not invite a press: no
   hover state, no pointer cursor, no focus ring, aria-hidden because the
   <title> already names the app to a screen reader. */
.brand {
    position: fixed; top: 11px; left: 14px; z-index: 61;
    display: flex; align-items: center; gap: 8px;
    user-select: none;
    /* A button now, so the default chrome has to go before the mark looks
       like a mark again. */
    background: none; border: 0; padding: 0; margin: 0;
    cursor: pointer; font: inherit; color: inherit;
    border-radius: 10px;
    transition: opacity .12s;
}
.brand:hover { opacity: .82; }
.brand:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

/* 19.14 — AND NOTHING MAY SIT UNDER IT. The mark is fixed, so it reserves no
   space and pushes nothing down: any panel whose content starts at the top
   left has its first line printed behind the wordmark. Three of them did --
   Exercises opened with its heading underneath "Strings", and Drills and
   Progress with the top of their first card.

   Only these three, and not .tabpanel generally: Practice, Tuner and
   Settings open with centred content that starts well clear of it, and
   thirty pixels of nothing at the top of the busiest screen in the app
   would be a worse trade than the bug. */
#tab-routines, #tab-drills, #tab-progress { padding-top: 30px; }
.brand-mark {
    width: 30px; height: 30px; display: block; border-radius: 9px;
    box-shadow: 0 3px 12px rgba(139, 92, 246, .28);
}
.brand-word {
    font-size: 14.5px; font-weight: 700; letter-spacing: -.2px;
    color: #cbbde4;
}

/* THE CORNER IS SHARED. The routine badge was already fixed at left:14px and
   the mark now sits there, so the badge starts after it. One variable rather
   than a number repeated in two rules, because the two must agree exactly or
   they overlap -- and they overlap in the one state hardest to notice, which
   is "a routine is loaded". */
/* Measured, not guessed: 30px mark + 8px gap + the wordmark = 84px,
   plus 10px of air before the badge. */
:root { --brand-w: 94px; }
.routine-badge { left: calc(14px + var(--brand-w)); }

/* Under 560 the wordmark goes and the mark alone stays. The badge carries
   real information and the mark carries none, so the mark is what shrinks. */
@media (max-width: 560px) {
    :root { --brand-w: 36px; }
    .brand-word { display: none; }
    .brand-mark { width: 26px; height: 26px; }
}

/* ====================================================================
   11.d — FOCUS MODE
   ==================================================================== */

/* D156 - A CENTRED COLUMN, NOT A GRID PINNED TO THE EDGES.

   The grid gave the board's row everything that was left, which on a tall
   desktop window was most of the screen: the name pinned to the top, the
   changes and the controls pinned to the bottom, and the neck alone in the
   middle of a hundred and sixty pixels of nothing above and below. The
   column sizes the stage to the neck instead -- the SVG is as tall as its
   width makes it -- and centres the whole stack, so the name sits on the
   neck's upper rule and the changes on its lower one, wherever the window's
   middle is.

   On a SHORT screen nothing changes: the stage is the only thing allowed
   to shrink, the SVG is capped at the height it is given and letterboxes
   sideways, and the status band scrolls inside its own cap as before. The
   floor under the stage is the same one the grid row had. */
.focus-overlay {
    position: fixed; inset: 0; z-index: 300;
    display: flex; flex-direction: column; justify-content: center;
    gap: 10px;
    padding: 14px 16px 16px;
    background: #110d17;
    box-sizing: border-box;
    overscroll-behavior: contain;
}

/* Nothing behind it scrolls or is reachable. */
body.focused { overflow: hidden; }

/* 15.16 — THE NAME IS THE HEADLINE HERE, not a label in the corner.
   It sat left at 30px beside a full-width neck, which is the one thing in
   this mode you glance at without moving your eyes off your hands -- and at
   that size, in that corner, it lost. Centred and large; the key pill and the
   exit sit out of the flow so neither pushes it off centre. */
.fo-head {
    position: relative;
    display: flex; align-items: center; justify-content: center;
    min-height: 64px;
}
/* 19.27 - A ROW: the chord name and, beside it, the badge. The column this
   was (19.19) held the form line, which has since moved down to the status
   band (19.23); what sits beside the name now is where you are in the
   form, kept lit, because in this mode you look up between phrases and
   a popup that has faded has nothing to tell you. */
.fo-now { display: flex; flex-direction: row; align-items: center;
          justify-content: center; gap: 18px; min-width: 0; }
/* Static, lit, and no longer a corner: the page's rules put it top right
   of the board and fade it, and neither applies once it is parked here. A
   floor under the width so the name does not shift as "bar 3 of 8"
   becomes "bar 10 of 12". */
.fo-now .form-badge {
    position: static; opacity: 1; transition: none;
    min-width: 132px; flex: none;
    background: rgba(20, 20, 20, .55);
}
.fo-now .form-badge[hidden] { display: none; }
/* The role word is the pill's job in this mode -- it is two inches to the
   left, lit, and in the same colour -- so the badge does not say it twice.
   The border still takes the role's colour when it arrives. */
.fo-now .fb-role { display: none; }
.fo-now #chordLine {
    margin: 0; padding: 0; font-size: 52px; line-height: 1.02;
    text-align: center; max-width: none; letter-spacing: -1px;
}
/* 19.26 - the key and the job, side by side at the left. One absolutely
   placed cluster rather than two, so the second pill sits after the first
   whatever the key's name is wide. */
.fo-left { position: absolute; left: 0; top: 50%; transform: translateY(-50%);
           display: flex; align-items: center; gap: 6px; }
.fo-role {
    font-size: 12px; font-weight: 700; letter-spacing: 1.1px;
    text-transform: uppercase; white-space: nowrap;
    padding: 4px 9px; border-radius: 999px;
    color: var(--role, var(--brand-soft));
    background: color-mix(in srgb, var(--role, var(--brand)) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--role, var(--brand)) 45%, transparent);
}
.fo-role[hidden] { display: none; }
.fo-role.role-head { --role: var(--accent-1); }
.fo-role.role-solo { --role: var(--accent-2); }
.fo-role.role-out  { --role: #ff5c7a; }
.fo-key {
    font-size: 12px; font-weight: 700; letter-spacing: 1.1px;
    text-transform: uppercase; color: var(--brand-soft);
    padding: 4px 9px; border-radius: 999px;
    background: var(--brand-wash); border: 1px solid var(--brand-edge);
}
.fo-exit {
    position: absolute; right: 0; top: 50%; transform: translateY(-50%);
    width: 40px; height: 40px; flex: none;
    border-radius: 12px; border: 1px solid #3b2f4b; background: #21192d;
    color: #cfccd2; font-size: 21px; line-height: 1; cursor: pointer;
}
.fo-exit:hover { background: #2c223a; color: #fff; }

/* The board and the next-chord panel, side by side while there is width for
   it and stacked when there is not. */
/* STRETCH, NOT CENTRE. Centred, the wrapper sizes to its content and the
   board's `height: 100%` has nothing definite to resolve against -- so it
   stayed at its natural size with empty space above and below it, in the one
   mode built to make it large. The row has a definite height; the chain from
   it down to the SVG has to stay definite the whole way. */
.fo-stage {
    display: flex; align-items: stretch; justify-content: center;
    gap: 14px; min-width: 0;
    /* The one row that gives way when the window is short. */
    flex: 0 1 auto; min-height: 180px;
}
.fo-stage .board-wrap {
    flex: 1 1 auto; min-width: 0; min-height: 0;
    display: flex; align-items: center; justify-content: center;
}
.fo-head, .fo-status, .fo-bar { flex: none; }

/* THE BOARD IS WHY YOU ARE HERE, so it gets the room. build() writes an
   inline max-width in pixels to stop a 3-fret window ballooning on the
   practice page; here that cap is exactly wrong, and an author !important is
   the one thing that outranks a non-important inline style. */
.fo-stage #board {
    max-width: none !important;
    /* D156 - as tall as its width makes it, and no taller than the room it
       has. Full width is what makes a neck big; the cap is what keeps the
       changes on screen when the window is short -- and when the cap bites,
       preserveAspectRatio="meet" letterboxes the drawing sideways inside
       the element, which focus.js measures for the rules. */
    width: 100%; height: auto; max-height: 100%;
}
/* A NECK IS A WIDE OBJECT, so width is what makes it bigger -- the SVG keeps
   its aspect ratio, and giving it more height alone just letterboxes it with
   empty space above and below. The next-chord panel was taking 300px of the
   row and the neck was the thing paying for it. Capped at a quarter, and it
   is a small reference diagram rather than a second board. */
.fo-stage .next-up {
    flex: 0 0 min(240px, 26%); margin: 0; align-self: center;
    min-width: 0;
}

/* THE NECK KEEPS THE MAJORITY. The chart is a real answer to "what is
   coming", but a thirty-two bar tune draws thirty-two capsules and they were
   taking more height than the board -- in a mode whose entire premise is that
   the board is the thing you look at. Capped, and it scrolls inside itself if
   a long form needs more; the board takes everything that leaves. */
.fo-status {
    display: flex; justify-content: center;
    max-height: 34vh; min-height: 0;
}
.fo-status #loopReadout {
    width: 100%; max-width: 760px; text-align: center;
    overflow-y: auto; overscroll-behavior: contain;
}

/* Stop, tempo, click. The three things you reach for mid-take; everything
   else was decided before you pressed play. */
.fo-bar {
    display: flex; align-items: center; justify-content: center;
    flex-wrap: wrap; gap: 12px 18px;
}
.fo-bar .tbtn-wide { min-width: 96px; }
.fo-bar .ctl { margin: 0; }

/* Landscape is the stand orientation, and a neck is a wide object: give the
   head and foot less height and let the board have it. */
@media (orientation: landscape) and (max-height: 560px) {
    .focus-overlay { gap: 6px; padding: 8px 12px 10px; }
    .fo-head { min-height: 0; }
    /* Short landscape is the phone-on-a-stand case and the board is
       height-bound there, so the head gives up what it can -- but not so much
       that the name goes back to being a corner label. */
    .fo-now #chordLine { font-size: 30px; }
    .fo-exit { width: 34px; height: 34px; font-size: 18px; }
    .fo-status #loopReadout { font-size: 12px; }

    /* SHORT LANDSCAPE IS THE PHONE-ON-A-STAND CASE, and there the neck stops
       being width-bound and becomes height-bound: at 740x400 the chart was
       taking a third of the screen and the neck was 164px tall. Everything
       else gives up what it can. The chart keeps a quarter of what it had --
       enough for the current section -- and the readout under it still names
       the next chord and the next section in words. */
    .fo-status { max-height: 22vh; }
    /* A HALF-ROW OF CHART IS WORSE THAN NONE. At 400px tall the capsules do
       not fit in what is left, and a scroll box shows a row sliced through
       the middle -- which reads as broken rather than as scrollable. The
       lines under it already say the bar, the section, the next chord and the
       next section in words, and the next-chord diagram is still on screen. */
    .fo-status .lc-boxes { display: none; }
    .fo-status #loopReadout { overflow: visible; }
    .fo-bar { gap: 8px 14px; }
    .fo-bar .tbtn-wide { min-width: 72px; }
    .fo-stage { gap: 10px; }
}

@media (max-width: 620px) {
    .focus-overlay { padding: 10px 10px 12px; gap: 8px; }
    .fo-now #chordLine { font-size: 25px; }
    /* Under the name rather than beside it, and one line rather than
       three: the header is between the pills and the exit button here and
       has no width to spare, and the stage below needs every pixel of
       height. */
    .fo-now { flex-direction: column; gap: 3px; }
    .fo-now .form-badge {
        flex-direction: row; align-items: baseline; gap: 7px;
        padding: 3px 9px 4px; min-width: 0;
    }
    .fo-now .form-badge b { font-size: 14px; }
    .fo-now .form-badge .fb-role { margin: 0; font-size: 11px; }

    /* STACKED, NOT HIDDEN. Side by side the next-chord panel costs width, and
       width is the only thing that makes a neck bigger -- so on a phone it
       used to be dropped. But a neck in portrait is width-bound and leaves a
       couple of hundred pixels of vertical slack the board CANNOT use, and
       "what is coming" is half of what this mode is for. Below the board it
       is free. */
    .fo-stage { flex-direction: column; gap: 8px; }
    .fo-stage .board-wrap { flex: 1 1 auto; width: 100%; }
    .fo-stage .next-up {
        flex: 0 0 auto; width: 100%; max-width: 320px; align-self: center;
    }
    .fo-status { max-height: 30vh; }
}

/* ====================================================================
   11.e — LISTEN
   ==================================================================== */

/* VIOLET, AND ONLY VIOLET. The neck already speaks in colour: every note
   wears its pitch-class fill, a green ring means right, a red one means
   wrong, and grey-and-dashed means a duplicate you did not pick. "The
   microphone heard this" had to be none of those, and it is not a judgement
   either -- nothing here is graded. So it is a glow rather than a ring, in
   the one hue the app uses nowhere else and its own mark is built from. */
/* The ring itself. Invisible until an animation drives it, and it never takes
   a pointer event -- it sits over the notes, and swallowing a click on a
   fretboard cell would be a strange way to pay for a highlight.

   fill-box is what makes `scale()` grow it around its own centre; without it
   SVG scales about the origin of the whole board and the ring flies off to
   the left. Same line `.note .inner` already needed. */
.ring-layer { pointer-events: none; }
.note .ring, .ring {
    fill: none;
    stroke: #a855f7;
    stroke-width: 2.6;
    opacity: 0;
    transform-box: fill-box;
    transform-origin: center;
    pointer-events: none;
}
/* Held. A class rather than an animation, because a note can sustain for
   seconds and there is nothing to interpolate while it does -- only the
   release moves. */
.note .ring.lit, .ring.lit { opacity: 1; }

/* The button pulses on every heard note, INCLUDING the ones the board cannot
   show -- a pitch outside the window lights nothing, and without this there
   is no way to tell "it did not hear me" from "it heard me, but that note is
   not on this part of the neck". */
.listen-btn.active {
    color: #e8e7e9;
    border-color: #6c5d81;
    background: #473a5a;
}
.listen-btn.pulse {
    animation: listen-pulse .34s ease-out;
}
@keyframes listen-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(168, 92, 247, .65); }
    100% { box-shadow: 0 0 0 11px rgba(168, 92, 247, 0); }
}
.listen-btn:disabled { opacity: .4; }

@media (prefers-reduced-motion: reduce) {
    .listen-btn.pulse { animation: none; }
}

/* The metronome, which is a state rather than an action -- same argument as
   the repeat toggles, so the same treatment. */
.tbtn#metroBtn {
    background: #312640; border: 1px solid #433755; color: #8b7f9b;
}
.tbtn#metroBtn.on {
    background: var(--brand-deep); border-color: var(--brand);
    color: var(--brand-soft);
}
.tbtn#metroBtn:disabled { opacity: .38; }
/* 7.1 - the bars that are yours: lit, but standing back. */
.tbtn#metroBtn.on.silent { opacity: .45; transition: opacity .15s; }

/* A BUTTON IS GREEN IN THIS APP UNLESS IT SAYS OTHERWISE, and green is the
   colour of Play. These are the same kind of control the repeat toggles are
   and carry the same comment: a state you set, not a thing you do. Neutral
   until they are on. */
.listen-btn {
    background: #312640;
    border: 1px solid #433755;
    color: #988fa5;
}
.listen-btn:hover:not(:disabled) { background: #3a2e4a; color: #cfccd2; }

/* The interface button. Violet because that is what a heard note is, so the
   button and the glow it causes are visibly the same feature -- the mic
   button stays neutral, since it is the ordinary one. */
.guitar-btn.active {
    color: #e2caff;
    border-color: rgba(168, 92, 247, .7);
    background: rgba(168, 92, 247, .22);
}
/* Present but not connected. Dimmed rather than removed: a control that
   vanishes is harder to reason about than one that says why, and the title
   says which. */
.guitar-btn.offline {
    opacity: .32;
    border-style: dashed;
}
.guitar-btn.offline:disabled { opacity: .32; }

/* 11.l — the sync-locked bar. Along the bottom rather than the top: the top
   already carries the mark, the routine badge and the profile pill, and this
   is the least urgent of the four. Under the focus overlay's z-index, so a
   take is never interrupted by it. */
.sync-bar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 200;
    display: flex; align-items: center; justify-content: center;
    flex-wrap: wrap; gap: 10px 14px;
    padding: 9px 14px;
    box-sizing: border-box;
    background: #2b2233;
    border-top: 1px solid var(--brand-edge);
    color: #d7cbe6; font-size: 13px;
    box-shadow: 0 -6px 20px rgba(0, 0, 0, .35);
}
.sync-bar a { color: var(--brand-soft); font-weight: 700; }
.sync-bar button { padding: 4px 10px; font-size: 12px; }

/* 11.p — THE BAND, on the front page again. Backing decides whether the three
   voices mean anything, so it leads; the mix closes the row. One panel rather
   than a row appended to the transport, because "what is playing" and "how it
   is played" are different questions and the transport is already long. */
.band-panel {
    display: flex; align-items: center; justify-content: center;
    flex-wrap: wrap; gap: 10px 16px;
}
.band-panel .ctl { margin: 0; }
/* Backing names run long ("Comp + drums + walking bass"), so it is allowed to
   take the room it needs while the three voices stay compact. */
.band-panel .ctl:first-child select { max-width: 220px; }

/* The fret position, in Settings now. A set-row puts its control on the right
   in one box, and this control is three things, so they travel together. */
.set-frets {
    display: inline-flex; align-items: center; gap: 8px;
    white-space: nowrap;
}
.set-frets #posLabel { min-width: 74px; text-align: center; }

/* ====================================================================
   11.q — THE TUNER
   ==================================================================== */

.tuner {
    max-width: 760px; margin: 0 auto;
    /* CLEARS THE FIXED ROW. The mark, the routine badge and the profile pill
       are all pinned to the top, and the dial is centred and wide enough to
       slide under the badge -- measured, its top sat 13px above the badge's
       bottom. D100 paid this only when a routine was loaded because the chord
       title was fighting for scarce vertical space; this page has room to
       spare, so it pays unconditionally and there is one fewer conditional
       layout to reason about. */
    padding: 52px 0 28px;
    display: flex; flex-direction: column; align-items: center; gap: 18px;
}

.tn-gauge { position: relative; width: 100%; max-width: 460px; }
.tn-gauge svg { width: 100%; height: auto; display: block; overflow: visible; }

/* The dial. Drawn as an arc rather than a box because a needle needs somewhere
   to sweep, and the eye reads distance along a curve faster than a bar. */
.tn-face {
    fill: none; stroke: #382c48; stroke-width: 2.5; stroke-linecap: round;
}
/* The in-tune zone, at the top of the sweep. Violet until you are inside it,
   green when you are -- the one place green survives as "correct". */
.tn-arc {
    fill: none; stroke: #5b4a72; stroke-width: 7; stroke-linecap: round;
    transition: stroke .12s;
}
.tn-arc.in { stroke: #50fa7b; }

.tn-ticks line { stroke: #534567; stroke-width: 2; stroke-linecap: round; }
.tn-edge { fill: #6c5d81; font-size: 11px; text-anchor: middle; }

.tn-needle line {
    stroke: var(--brand); stroke-width: 3; stroke-linecap: round;
}
.tn-needle circle { fill: var(--brand); }

/* The numbers live under the needle's pivot, inside the dial, so one glance
   takes both. */
.tn-read { text-align: center; margin-top: -6px; }
.tn-note {
    font-size: 46px; font-weight: 700; letter-spacing: -1px; line-height: 1;
    color: #f0eff1;
}
.tn-nums {
    display: flex; gap: 16px; justify-content: center; margin-top: 4px;
    font-family: "JetBrains Mono", Consolas, monospace; font-size: 14px;
    color: #988fa5; min-height: 18px;
}

/* The six strings, low to high left to right -- the way they sit under your
   hand rather than the way STRING_MIDI indexes them. */
.tn-strings { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.tn-str {
    display: flex; flex-direction: column; align-items: center; gap: 1px;
    width: 56px; padding: 7px 0; border-radius: 9px;
    background: #2c223a; border: 1px solid #3d304e; color: #b6b1bd;
    font-weight: 400; cursor: default;
}
.tn-str b { font-size: 15px; font-weight: 700; }
.tn-str i {
    font-style: normal; font-size: 10px; color: #7a6b8f;
    font-family: "JetBrains Mono", Consolas, monospace;
}
.tn-str.on {
    background: var(--brand-deep); border-color: var(--brand);
    color: var(--brand-soft);
}
.tn-str.on i { color: var(--brand-soft); }

/* The waveform. Its whole job is to make "in tune" and "hearing nothing"
   stop looking identical. */
.tn-scope {
    width: 100%; max-width: 720px; height: auto;
    background: #1a1323; border: 1px solid #312640; border-radius: 10px;
}
.tn-status { text-align: center; min-height: 17px; }

@media (max-width: 620px) {
    .tn-note { font-size: 38px; }
    .tn-str { width: 46px; }
}


/* ==================================================== 19.18 THE FORM LINE

   One letter per pass through the tune, above the chord name, with the one
   you are in enlarged. It replaces nothing -- the badge on the board still
   announces a change as it happens -- but it is the answer to "where am I"
   that is there when you look rather than only when it moved. */
/* 19.20 - ON the border, not above the panel. Pulled up by half its own
   height so it straddles a line that was already being drawn, which is why
   it costs no vertical space at all.

   19.22 - AND NO PLATE BEHIND IT. Painting the page colour under the letters
   broke the rule cleanly but left a visible rectangle sitting on the panel,
   which is the boxy thing this app keeps removing. The letters carry their
   own dark halo instead: the rule stays continuous and simply dims where it
   passes close to a character, so it reads as running BEHIND the form rather
   than being cut for it. */
.statusbar { position: relative; }
.form-line {
    position: absolute; top: 0; left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2;
    background: none; padding: 0 6px;
    display: flex; justify-content: center; align-items: baseline;
    gap: 9px; margin: 0; min-height: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    letter-spacing: .06em;
}
.form-line[hidden] { display: none; }
/* 19.22 - lifted out of the panel's own darkness. At #6c5d81 the small
   letters were about as dark as the ground behind them, so a BLACK halo did
   nothing for them -- two dark things do not separate. Bright enough to read
   against the rule now, with size and colour still saying which one you are
   in. */
.fl-slot {
    font-size: 12px; font-weight: 700; color: #a79ec0;
    text-shadow: 0 0 4px #000, 0 0 9px rgba(0, 0, 0, .92);
    transition: color .25s ease, font-size .25s ease;
    line-height: 1;
}
/* Big, bright, and in the brand rather than in the key's colour: the key
   already owns the board's ring and the chord name, and a third thing
   changing hue with it would be noise dressed as information. */
/* Smaller than it was floating above the title: on the border it has a rule
   either side of it doing half the work of standing out. */
/* Black under, violet outside: the dark halo does the legibility work close
   to the letter and the brand bloom sits beyond it, so the active letter
   still reads as lit rather than merely larger. */
.fl-slot.on {
    font-size: 20px; color: var(--brand-soft);
    text-shadow: 0 0 4px #000, 0 0 10px rgba(0, 0, 0, .95),
                 0 0 17px var(--brand-edge);
}
/* Fires on ARRIVAL only. This line repaints on every bar; a letter that
   jumped each time would be a tic rather than a signal. */
.fl-slot.arrive { animation: flArrive .42s cubic-bezier(.2,.8,.3,1); }
@keyframes flArrive {
    0%   { transform: scale(.72); opacity: .35; }
    55%  { transform: scale(1.16); opacity: 1; }
    100% { transform: scale(1); opacity: 1; }
}
.fl-slot { display: inline-block; will-change: transform; }

/* ================================================ 19.18 BARS, NOT CHORDS

   The strip's unit is the bar now. Half a bar is a fact about the data; a
   bar with two chords in it is what a player reads. */
.sb-sec-bars { display: flex; flex-direction: column; gap: 7px; flex: 1 1 auto;
               min-width: 0; }
/* 19.19 - A BAR LINE, NOT A BOX. Wrapping each bar in its own bordered cell
   drew the grouping twice, once as a box and once as the space between
   boxes, and made the strip look like a table of bars rather than like
   music. One vertical rule where a bar ends is what notation uses and is
   enough; the chords inside a bar simply sit together.

   Four a row whatever the meter -- a phrase is four BARS, not four beats. */
.bar-row { display: grid; grid-template-columns: repeat(4, 1fr); position: relative; }
/* D146 - A RULE BETWEEN THE LINES. The vertical rules say where a bar ends;
   nothing said where a LINE of four ended, and the second row of a section
   simply sat under the first. A lead sheet has the system break for this.
   Half the width, centred, fading to nothing at both ends, so it reads as
   the paper's own horizontal rather than as a border - the strip has spent
   several commits getting rid of borders and this is not one coming back.
   D148 - NOT the colour of the bar rules after all. At one pixel the rules'
   purple vanished into the strip, and in the section you are IN -- which
   has a warm wash under it -- it vanished completely, so the line was
   invisible exactly where you were looking. A pale line reads on both
   grounds; the current section gets it in its own warm tint. */
.bar-row + .bar-row::before {
    content: ''; position: absolute; left: 25%; width: 50%;
    top: -4px; height: 1px;
    background: linear-gradient(90deg, transparent,
        rgba(255,255,255,.30) 28%, rgba(255,255,255,.30) 72%, transparent);
    pointer-events: none;
}
.sb-sec.on .bar-row + .bar-row::before {
    background: linear-gradient(90deg, transparent,
        rgba(216,168,130,.55) 28%, rgba(216,168,130,.55) 72%, transparent);
}
.bl-bar {
    position: relative;
    min-height: 32px; padding: 3px 8px 4px;
    display: flex; align-items: center; justify-content: center;
    gap: 7px; flex-wrap: wrap;
    transition: background .18s;
}
/* 19.20 - the rule is SHORTER THAN THE BAR. A border runs the full height of
   whatever it edges, which drew a tall line for every bar and made the strip
   a grid of tall cells; a stroke through the middle says the same thing and
   lets the bars themselves be shorter. A pseudo-element rather than a border
   is what makes the height a choice at all. */
.bl-bar::before, .bar-row .bl-bar:last-child::after {
    content: ''; position: absolute; top: 50%;
    transform: translateY(-50%);
    width: 2px; height: 58%; background: #4a3d63;
    border-radius: 1px;
}
.bl-bar::before { left: 0; }
.bar-row .bl-bar:last-child::after { right: 0; }
.bl-bar .bn { position: absolute; top: 0; left: 6px;
              font-size: 8.5px; color: #6c5d81; line-height: 1.3; }
/* The bar the transport is in -- a wash rather than a border, because the
   bar lines are already drawing every edge and a second set of them would
   be two grids arguing. */
.bl-bar.on { background: rgba(255,255,255,.07); }
.bl-bar .bc {
    background: #292036; border: 1px solid #3b2f4b;
    padding: 3px 9px; margin: 0; border-radius: 6px;
    font-family: inherit; font-size: 14px; font-weight: 700;
    line-height: 1.15; cursor: pointer; white-space: nowrap;
    transition: background .12s, border-color .12s;
}
.bl-bar .bc:hover { background: #342a45; border-color: #4a3d63; }
/* Which chord is SOUNDING, inside a bar that may hold two. */
.bl-bar .bc.on { border-color: rgba(255,255,255,.7); background: #362a48;
                 box-shadow: 0 0 0 1px rgba(255,255,255,.3); }
/* Three chords in one bar have to fit the width one gets. */
.bl-bar .bc:nth-child(4) { font-size: 12px; padding: 2px 6px; }
.bl-bar .bc:nth-child(n+5) { font-size: 10.5px; padding: 2px 5px; }

@media (max-width: 560px) {
    .bl-bar { min-height: 30px; padding: 2px 4px 3px; gap: 4px; }
    .bl-bar .bc { font-size: 12px; padding: 2px 6px; }
    .fl-slot.on { font-size: 22px; }
}


/* ============================================= 19.19 FOCUS MODE, SMALLER

   Focus mode is the one screen where fitting matters: it is what you look at
   while you play, and a page that scrolls under your hands is a page you
   stop reading. On a 1280x800 desktop it was running to 1013px.

   THE SECTION YOU ARE IN IS FULL SIZE AND THE OTHERS ARE CONDENSED. Not
   hidden -- reading ahead into the next section is a real part of not
   getting lost -- but a section you are not playing does not need to be as
   legible as the one you are. That alone gets an AABA under the fold line
   in every tune the app has. */
.focused .fo-status .sb-sec:not(.on) .bl-bar { min-height: 26px; padding: 2px 5px 3px; }
.focused .fo-status .sb-sec:not(.on) .bc {
    font-size: 11px; padding: 1px 5px; border-radius: 4px;
}
.focused .fo-status .sb-sec:not(.on) .bn { display: none; }
.focused .fo-status .sb-sec:not(.on) { opacity: .62; }
.focused .fo-status .sb-sec.on .bl-bar { min-height: 32px; }
/* Both sizes animate, so a section growing as you arrive in it reads as the
   same section rather than as the strip being rebuilt. */
.focused .fo-status .sb-sec,
.focused .fo-status .bl-bar,
.focused .fo-status .bc {
    transition: opacity .25s ease, min-height .25s ease, font-size .25s ease,
                padding .25s ease;
}

/* The form line rides with the chord name now, so it needs the header's
   centring rather than the page's. */
/* 19.23 - the same legend on the same rule, in focus mode. The status band
   holds the very panel the page does -- #loopReadout keeps its .sb-loop
   border wherever it is parked -- so the placement is the placement, not a
   copy of it. */
.fo-status { position: relative; }
.fo-status .form-line { top: 0; }


/* 19.19 - FOCUS MODE DOES NOT SCROLL THE PAGE.

   It never actually overflowed. The overlay fits the viewport with room to
   spare -- on a 1280x800 desktop its last element ends at 784 - and the
   1013px of scroll height was THE PAGE UNDERNEATH, still its full length
   behind an overlay covering it. So the page moved under your hands while
   the thing you were looking at had not moved at all.

   Locking the body is the fix; the strip gets its own scroll for the case
   the overlay really cannot hold, which is a long form on a short window. */
/* The status band already caps itself at 34vh and scrolls inside itself --
   that is not new. What was new is that the PAGE scrolled too, so there were
   two scrollers and the outer one moved the thing you were looking at.
   Locking is done in focus.js, on the documentElement, because body alone
   does not stop it: html is the scrolling element. */


/* ================================== 19.24 THE LEGEND KEEPS ITS OWN AIR

   Measured at 375x812 with the overlay open: the changes panel is 244px tall
   holding 368px of strip, so it scrolls -- and #loopReadout is the scroller
   itself, which means its 17px of top padding, the space put there for the
   form legend to sit in, scrolls away with everything else. Two bars in, the
   bar row was passing straight under "A A2 B A3" and the letters were
   printed across chord names.

   It only shows in focus mode: on the page the panel draws its full height
   and never scrolls, so the padding stays where it was put.

   The band is on .fo-status, which does NOT scroll, so it holds still while
   the strip moves beneath it -- sized the way the panel is sized so it lines
   up at any width. Solid for the twelve pixels the letters actually sit in,
   then dissolved, so a row going under the rule fades rather than being cut
   off by a second edge parallel to the first. */
.fo-status::before {
    content: ""; position: absolute; z-index: 1; pointer-events: none;
    top: 1px; left: 50%; transform: translateX(-50%);
    width: calc(100% - 2px); max-width: 758px; height: 19px;
    border-radius: 5px 5px 0 0;
    /* Solid for eleven pixels -- the half of the legend that hangs below the
       rule -- and clear again by nineteen, which is where the panel's own
       17px of top padding ends. So at rest it covers nothing but padding,
       and the first row is as bright as the rest. */
    background: linear-gradient(#1b1424 0, #1b1424 11px, rgba(27, 20, 36, 0) 19px);
}
/* With the loop off the panel goes transparent and draws no border, so there
   is nothing for the band to continue and it would read as a smudge. */
.fo-status:has(#loopReadout.off)::before { display: none; }

/* ============================== 19.24 AND THE FIRST SECTION IS REACHABLE

   Measured at 375x812: the strip is 365px inside a 242px panel, scrollTop is
   0, and section A's box starts 97px ABOVE the top edge. Ninety-seven pixels
   of a tune that no amount of scrolling can reach, because scrollTop cannot
   go below zero.

   .sb-loop centres its rows with `justify-content: center`, which is right
   for the two or three lines it holds on the page. Once the content is taller
   than the box, centring pushes the overflow out of BOTH ends and the top end
   is not scrollable -- one of the oldest traps in flexbox. It only appears
   where the panel is height-capped, which is focus mode, which is exactly
   where you cannot afford to lose the first eight bars.

   `safe` means "centre until that would put content out of reach, then align
   to the start instead", which is the whole fix in one keyword. */
.fo-status #loopReadout { justify-content: safe center; }


/* ======================================================= D150 RULES, NOT BOXES

   The rule between the two rows of a chart section (D146) turned out to be
   the best-looking line in the app, so it is the app's line now. Every
   container that was a box -- a fill with a one-pixel border, rounded, with
   a drop shadow -- is a fill that dissolves towards its sides, held between
   two rules that do the same. Nothing on the page has a vertical edge any
   more except the things that are actually objects: the neck, the keys, the
   buttons.

   THREE SIZES AND THE NECK. Big sections get a two-pixel rule with a long
   fade; boxes inside them get one pixel with a slightly longer fade; the
   chart sections and the grip cells get the D146 line itself. The neck is
   the exception on purpose: three pixels, nearly full width, in the KEY'S
   colour with a glow, because the key used to be the ring round the board
   and the board is the one thing on the page that should be more
   pronounced than everything else.

   Each tier sets five custom properties and the rules read them, so a
   container that wants a different colour -- a routine you wrote, the
   section you are in, the chord that is coming -- overrides one property
   rather than redrawing the line. The line itself is drawn once, below.

     --fill   the tint that used to be the background
     --fade   how far in from each side the tint takes to arrive
     --rc     the rule's colour
     --rh     the rule's thickness
     --rin    how far in from each side the rule takes to arrive          */

:root {
    --rule-lg: rgba(196,178,232,.44);
    --rule-md: rgba(196,178,232,.34);
    --rule-sm: rgba(255,255,255,.30);
}

/* Two rules meet wherever two boxes do -- the bottom of one and the top of
   the next -- and at the old fourteen pixels apart they read as one double
   line. Room enough between them to be two ends. */
.panel, .sidebar { margin-bottom: 28px; }
.card { margin-top: 30px; }

/* ---- the three tiers ------------------------------------------------ */
.panel, .sidebar, .card, .sb-loop, .drill-setup, .drill-summary, .drill-q,
.placeholder, .routine-editor, .set-group, .ag-diagram {
    --rc: var(--rule-lg); --rh: 2px; --rin: 20%; --fade: 12%;
}
.next-up, .routine, .sum-card, .re-seg, .ag-ch, .sum-badge {
    --rc: var(--rule-md); --rh: 1px; --rin: 22%; --fade: 14%;
}
.sb-sec, .sh-cell, .sp-cell {
    --rc: var(--rule-sm); --rh: 1px; --rin: 26%; --fade: 18%;
}

/* ---- what each one was filled with ---------------------------------- */
.panel                       { --fill: #2a2038; }
.card, .routine              { --fill: #221a31; }
.sidebar, .drill-setup, .drill-summary, .drill-q, .placeholder,
.routine-editor, .set-group  { --fill: #21192d; }
.sb-loop, .ag-diagram, .sum-card { --fill: #1b1424; }
.next-up, .sh-cell, .sp-cell { --fill: #1f1729; }
.re-seg, .ag-ch              { --fill: #1d1527; }
.sum-badge                   { --fill: #1c231d; }
.sb-sec                      { --fill: rgba(255,255,255,.025); }

/* ---- the box becomes a fill between two rules ----------------------- */
.panel, .sidebar, .card, .sb-loop, .drill-setup, .drill-summary, .drill-q,
.placeholder, .routine-editor, .set-group, .ag-diagram,
.next-up, .routine, .sum-card, .re-seg, .ag-ch, .sum-badge,
.sb-sec, .sh-cell, .sp-cell,
.stage .board-wrap, .fo-stage .board-wrap {
    position: relative;
    border: 0; border-radius: 0; box-shadow: none;
    background: linear-gradient(90deg, transparent,
        var(--fill) var(--fade), var(--fill) calc(100% - var(--fade)), transparent);
}
.panel::before, .sidebar::before, .card::before, .sb-loop::before,
.drill-setup::before, .drill-summary::before, .drill-q::before,
.placeholder::before, .routine-editor::before, .set-group::before,
.ag-diagram::before, .next-up::before, .routine::before, .sum-card::before,
.re-seg::before, .ag-ch::before, .sum-badge::before, .sb-sec::before,
.sh-cell::before, .sp-cell::before, .stage .board-wrap::before, .fo-stage .board-wrap::before,
.panel::after, .sidebar::after, .card::after, .sb-loop::after,
.drill-setup::after, .drill-summary::after, .drill-q::after,
.placeholder::after, .routine-editor::after, .set-group::after,
.ag-diagram::after, .next-up::after, .routine::after, .sum-card::after,
.re-seg::after, .ag-ch::after, .sum-badge::after, .sb-sec::after,
.sh-cell::after, .sp-cell::after, .stage .board-wrap::after, .fo-stage .board-wrap::after {
    content: ''; position: absolute; left: 0; right: 0; z-index: 1;
    height: var(--rh); pointer-events: none;
    background: linear-gradient(90deg, transparent,
        var(--rc) var(--rin), var(--rc) calc(100% - var(--rin)), transparent);
}
.panel::before, .sidebar::before, .card::before, .sb-loop::before,
.drill-setup::before, .drill-summary::before, .drill-q::before,
.placeholder::before, .routine-editor::before, .set-group::before,
.ag-diagram::before, .next-up::before, .routine::before, .sum-card::before,
.re-seg::before, .ag-ch::before, .sum-badge::before, .sb-sec::before,
.sh-cell::before, .sp-cell::before { top: 0; }
.panel::after, .sidebar::after, .card::after, .sb-loop::after,
.drill-setup::after, .drill-summary::after, .drill-q::after,
.placeholder::after, .routine-editor::after, .set-group::after,
.ag-diagram::after, .next-up::after, .routine::after, .sum-card::after,
.re-seg::after, .ag-ch::after, .sum-badge::after, .sb-sec::after,
.sh-cell::after, .sp-cell::after { bottom: 0; }

/* ---- the neck -------------------------------------------------------- */
/* Outside the wood rather than on its edge, with the room to be outside;
   the drop-shadow follows the line's own alpha, so the glow fades where
   the rule does instead of drawing a lit rectangle round it. Scoped to the
   practice stage: the drill boards share the class and have no room. */
.stage .board-wrap, .fo-stage .board-wrap {
    --fill: transparent;
    --rc: hsl(var(--key-hue, 270) 62% 62% / .92);
    --rh: 3px; --rin: 7%;
}
.stage .board-wrap { margin: 24px 0 18px; }
.stage .board-wrap::before, .stage .board-wrap::after,
.fo-stage .board-wrap::before, .fo-stage .board-wrap::after {
    filter: drop-shadow(0 0 6px hsl(var(--key-hue, 270) 70% 58% / .8));
    transition: background .35s ease, filter .35s ease;
}
.stage .board-wrap::before { top: -13px; }
.stage .board-wrap::after  { bottom: -13px; }
/* D152 - IN FOCUS MODE TOO. The wrap there fills the row and the neck is
   letterboxed inside it, so focus.js measures where the wood actually is
   and writes the letterbox as --neck-top/bottom/left/right; the rules sit
   just outside that rather than on the wrap's edge. Nine pixels out rather
   than thirteen, because the rows above and below are closer here. The
   heading is not wanted -- the name is the headline in this mode. */
.fo-stage .board-wrap .panel-h { display: none; }
.fo-stage .board-wrap::before, .fo-stage .board-wrap::after {
    left: var(--neck-left, 0px); right: var(--neck-right, 0px);
}
.fo-stage .board-wrap::before { top: calc(var(--neck-top, 0px) - 9px); }
.fo-stage .board-wrap::after  { bottom: calc(var(--neck-bottom, 0px) - 9px); }
/* Room for the rules and their glow between the rows. */
.focus-overlay { gap: 18px; }
/* The SVG element is the whole letterbox, and a shadow round it drew the
   letterbox as a lighter rectangle with the neck floating inside it. */
.fo-stage #board { box-shadow: none; }
/* The band that carries the legend over a scrolling strip in focus mode
   was a solid rectangle; it dissolves sideways like the fill under it. */
.fo-status::before {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
/* The status bar keeps its distance from the neck's lower rule. */
.statusbar { margin-top: 20px; }

/* ---- headings on the rule ------------------------------------------- */
/* Inside the box now (index.html moved them), straddling its top rule the
   way the form legend straddles the status bar's: half above, half below,
   with a halo in the page colour so the rule dims where it passes behind
   a letter and reads as running BEHIND the name rather than being cut. */
.panel-h, .sidebar > .side-head {
    position: absolute; top: 0; left: 50%; transform: translate(-50%, -50%);
    margin: 0; z-index: 2; white-space: nowrap; padding: 0 12px;
    width: auto; max-width: none; text-align: center;
    /* D152 - whiter. On the rule, with the halo doing the separating, the
       old #a99bc0 read as part of the line rather than as a word. */
    color: #d4cce0;
    text-shadow: 0 0 4px var(--page), 0 0 9px var(--page), 0 0 14px var(--page);
}
/* The same style inside the cards -- CM7 UP CLOSE, CHORD SHAPES -- lifted
   with it, one step dimmer because it sits in the fill rather than on the
   rule. */
.card h3 { color: #b7abcc; }
.panel { padding-top: 20px; }
.sidebar { padding-top: 19px; }
.stage .board-wrap .panel-h { top: -13px; }

/* ---- the states that used to be border colours ---------------------- */
.sb-loop.off { background: transparent; }
.sb-loop.off::before, .sb-loop.off::after { display: none; }

.sb-sec.on {
    --rc: rgba(216,168,130,.7); --fill: rgba(176,138,111,.12);
    background: linear-gradient(90deg, transparent,
        var(--fill) var(--fade), var(--fill) calc(100% - var(--fade)), transparent);
}
.sb-sec.next { --rc: rgba(255,255,255,.4); }

.next-up { --rc: hsl(var(--next-hue, 270) 55% 60% / .75); }
.next-up::before, .next-up::after { transition: background .3s ease; }

.routine.mine { --rc: rgba(110,180,130,.6); }
.routine.song { --rc: rgba(200,160,115,.55); }
.ag-ch.done {
    --rc: rgba(110,180,130,.6); --fill: #1c231d;
    background: linear-gradient(90deg, transparent,
        var(--fill) var(--fade), var(--fill) calc(100% - var(--fade)), transparent);
}
.sh-cell:hover, .sp-cell:hover {
    --rc: rgba(196,178,232,.5); --fill: #241c30;
    background: linear-gradient(90deg, transparent,
        var(--fill) var(--fade), var(--fill) calc(100% - var(--fade)), transparent);
}
.card.fold > summary:hover {
    background: linear-gradient(90deg, transparent,
        #251c32 12%, #251c32 88%, transparent);
}

/* ---- and the same line inside the prose ----------------------------- */
/* The D146 line, between the blocks of a card: the spelling and the
   carousel, the two rows of the smooth path, the blocks of the songs list,
   the filter and the grid of grips. */
.carousel, .sp-row + .sp-row, .sg-block + .sg-block, .sh-grid {
    position: relative; padding-top: 14px;
}
.carousel { margin-top: 10px; }
.carousel::before, .sp-row + .sp-row::before, .sg-block + .sg-block::before,
.sh-grid::before {
    content: ''; position: absolute; top: 0; left: 25%; width: 50%; height: 1px;
    pointer-events: none;
    background: linear-gradient(90deg, transparent,
        var(--rule-sm) 28%, var(--rule-sm) 72%, transparent);
}

/* ---- and the lines that were already lines ------------------------- */
/* Eleven places drew a flat one-pixel border-top or -bottom across their
   full width as a separator. Same line, same fade, through border-image
   because they are borders already and a border-image is the whole change. */
.loop-panel, .set-row, .r-params, .re-parts, .re-seg-head, .re-actions,
.lives li, .ag-tabs, .ag-go, .shape-setup, .sync-bar {
    border-image: linear-gradient(90deg, transparent,
        rgba(196,178,232,.30) 22%, rgba(196,178,232,.30) 78%, transparent) 1;
}

/* ---- D151 - room from the edges, for the words --------------------- */
/* The fill dissolves over the outer tenth of a box, and text set at the old
   padding sat inside that dissolve, hanging off an edge that was not there.
   Diagrams are fine where they are -- a grid of grips reads from its
   centre -- so the wider inset is for the boxes and blocks that are mostly
   words. Sideways only: the vertical padding is untouched, because the
   page is read in one long scroll and every pixel of height is a pixel of
   music further away. */
.panel, .sidebar, .card, .sb-loop, .drill-setup, .drill-summary, .drill-q,
.placeholder, .routine-editor, .set-group, .ag-diagram { --fade: 9%; }

.card { padding-left: 34px; padding-right: 34px; }
.card.fold > summary { padding-left: 34px; padding-right: 34px; }
.card.fold > *:not(summary) { margin-left: 34px; margin-right: 34px; }
/* The grips and the smooth path are diagrams; they keep the old inset. */
.card.fold > .sh-grid, .card.fold > #spBody { margin-left: 16px; margin-right: 16px; }

.drill-setup, .drill-summary, .drill-q { padding-left: 40px; padding-right: 40px; }
.placeholder      { padding-left: 40px; padding-right: 40px; }
.set-group        { padding-left: 30px; padding-right: 30px; }
.routine          { padding-left: 26px; padding-right: 26px; }
.routine-editor   { padding-left: 30px; padding-right: 30px; }
.sum-card         { padding-left: 20px; padding-right: 20px; }
.ag-ch, .sum-badge { padding-left: 18px; padding-right: 18px; }
.re-seg-body      { padding-left: 16px; padding-right: 16px; }

@media (max-width: 560px) {
    /* A phone has no width to give away; half the inset is still twice
       what the fade needs. */
    .card, .card.fold > summary { padding-left: 22px; padding-right: 22px; }
    .card.fold > *:not(summary) { margin-left: 22px; margin-right: 22px; }
    .card.fold > .sh-grid, .card.fold > #spBody { margin-left: 12px; margin-right: 12px; }
    .drill-setup, .drill-summary, .drill-q, .placeholder { padding-left: 24px; padding-right: 24px; }
    .set-group, .routine-editor { padding-left: 20px; padding-right: 20px; }
}

/* The add-a-routine card drew a dashed border; it is a dimmer pair of
   rules now, lit on hover the way the border was. */
.routine-new { --rc: rgba(196,178,232,.22); }
.routine-new:hover { --rc: var(--accent-1); }
