/* ================= PROGRESS ================= */
.progress-top {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 40px;
    align-items: center;
    border: 1px solid var(--border);
    padding: 32px;
    margin-bottom: 32px;
}

.bar-row {
    margin-bottom: 18px;
}

.bar-row-head {
    display: flex;
    justify-content: space-between;
    font-size: .88rem;
    margin-bottom: 7px;
}

.bar-row-head b {
    font-weight: 700;
    font-family: 'Public Sans', sans-serif;
}

.bar-row-head span {
    font-family: 'IBM Plex Mono', monospace;
    color: var(--text-muted);
}

.bar-row-head .bar-meta {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* Where the exam-history bullet list under the heatmap ended up: a section's
   status belongs on the section's own row, not in a separate chronological list
   the reader has to re-join by hand. */
.bar-row-head .sec-tag {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .68rem;
    line-height: 1.5;
    padding: 0 6px;
    border: 1px solid currentColor;
    border-radius: 2px;
    white-space: nowrap;
    color: var(--text-muted);
}

.bar-row-head .sec-tag.pass {
    color: var(--success);
}

.bar-row-head .sec-tag.sched {
    color: var(--accent);
}

@media (max-width: 560px) {
    .bar-row-head {
        flex-wrap: wrap;
        gap: 4px 12px;
    }
}

.bar-track {
    height: 8px;
    background: var(--bg-sunken);
}

.bar-fill {
    height: 100%;
    background: var(--accent);
}

.bar-fill.good {
    background: var(--success);
}

.bar-fill.mid {
    background: var(--gold);
}

.bar-fill.low {
    background: var(--danger);
}

.bar-fill.neutral {
    background: var(--text-muted);
}

/* ---------- study activity heatmap ----------

   The grid is the hero of this card, so the grid now sizes to the card instead
   of the card being built around a fixed 11px square. A 53-column year at 11px
   filled about two thirds of the width and left a dead gutter down the right,
   which is what made the block read as lopsided: squares hanging left, legend
   hanging right, and nothing lining up with anything.

   Two modes, picked in 09-progress.js and switched with data-heat-fit:

   fit="1"  the columns fit, so squares are fluid (1fr columns + aspect-ratio)
            and the grid spans the card edge to edge. The month strip shares the
            grid's column tracks, so the two stay registered at any width, and
            there is no scrollbar.
   fit="0"  all-time runs 150+ columns and cannot fit at a legible size, so the
            squares fall back to a fixed 8px and the grid scrolls sideways.
*/
.heatmap-card {
    border: 1px solid var(--border);
    padding: 26px 28px 20px;
    position: relative;

    /* The ramp is derived from --success against the card background rather than
       reusing --success-soft. --success-soft is a background-tint token; in dark
       mode it sits at #20301f against an empty cell of #0f0d09, which made a
       light study day visually identical to a day with no study at all. */
    --heat-size: 8px;
    /* dense fallback only; fit mode sizes itself */
    --heat-gap: 3px;
    --heat-max: 18px;
    /* a square is a day, not a tile: cap how big it can get */
    --heat-label-w: 30px;
    --heat-label-gap: 10px;
    --heat-month-h: .68rem;
    /* the month strip is one line at its own font-size */
    --heat-row-gap: 7px;
    --heat-0: color-mix(in srgb, var(--bg-sunken) 55%, var(--bg-raised));
    --heat-1: color-mix(in srgb, var(--success) 24%, var(--bg-raised));
    --heat-2: color-mix(in srgb, var(--success) 46%, var(--bg-raised));
    --heat-3: color-mix(in srgb, var(--success) 70%, var(--bg-raised));
    --heat-4: var(--success);
}

.heatmap-card[data-heat-fit="0"] {
    --heat-gap: 2px;
}

.heatmap-card[data-heat-fit="0"] .heat-days {
    font-size: .58rem;
}

/* ---------- head: title left, range right ---------- */
.heat-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px 24px;
    margin-bottom: 16px;
}

.heat-head-text {
    flex: 1 1 340px;
}

.heat-head h3 {
    font-size: 1.15rem;
    margin-bottom: 5px;
}

.heat-sub {
    margin: 0;
    font-size: .8rem;
    color: var(--text-muted);
    max-width: 68ch;
}

/* ---------- toolbar: section filter left, colour key right ---------- */
.heat-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px 20px;
    margin-bottom: 16px;
    padding-bottom: 14px;
    border-bottom: 1px dashed var(--border);
}

/* One recessed track with the active chip filled, rather than five loose
   outlined chips: the group reads as a single control, and the range and
   section rows share an edge instead of half-aligning at two different sizes. */
.heat-seg {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 3px;
    padding: 3px;
    background: color-mix(in srgb, var(--bg-sunken) 45%, transparent);
    border: 1px solid var(--border);
    border-radius: 5px;
}

.heat-seg button {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .72rem;
    letter-spacing: .04em;
    line-height: 1.6;
    padding: 3px 11px;
    background: transparent;
    color: var(--text-muted);
    border: 0;
    border-radius: 3px;
    cursor: pointer;
    white-space: nowrap;
}

.heat-seg.heat-seg-sm button {
    font-size: .7rem;
    padding: 3px 9px;
}

/* Years become a dropdown past the point where they would wrap the control onto
   a third row; a candidate who started in college can accumulate six or seven. */
.heat-seg select {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .72rem;
    letter-spacing: .04em;
    padding: 2px 6px;
    background: transparent;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: pointer;
}

.heat-seg select:hover {
    border-color: var(--border-strong);
}

.heat-seg button:hover {
    color: var(--text);
    background: color-mix(in srgb, var(--border) 45%, transparent);
}

.heat-seg button[aria-pressed="true"],
.heat-seg button[aria-pressed="true"]:hover {
    background: var(--accent);
    color: var(--accent-ink);
}

/* ---------- grid ---------- */

/* The weekday rail is a sibling of the scroller, not a cell inside it, so the
   all-time view can scroll its 150 columns without taking the axis with it.
   The two stay registered because the rail is pushed down by exactly the height
   of the month strip, and both use the same row gap. */
.heat-plot {
    display: grid;
    grid-template-columns: var(--heat-label-w) 1fr;
    column-gap: var(--heat-label-gap);
    align-items: stretch;
}

.heat-scroll {
    overflow-x: auto;
}

/* Only the scrolling view needs room for a scrollbar, and only there can the
   extra height throw the rail out of alignment. */
.heatmap-card[data-heat-fit="0"] .heat-scroll {
    padding-bottom: 6px;
}

/* All-time runs 150+ columns. Fading whichever edge still has content off-screen
   is the cheapest honest signal that the grid continues past the viewport. */
.heat-scroll[data-edge="left"] {
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 32px);
    mask-image: linear-gradient(90deg, transparent 0, #000 32px);
}

.heat-scroll[data-edge="right"] {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 32px), transparent 100%);
    mask-image: linear-gradient(90deg, #000 calc(100% - 32px), transparent 100%);
}

.heat-scroll[data-edge="both"] {
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 32px, #000 calc(100% - 32px), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 32px, #000 calc(100% - 32px), transparent 100%);
}

.heat-canvas {
    display: grid;
    grid-template-rows: auto auto;
    row-gap: var(--heat-row-gap);
    width: max-content;
}

/* Fluid squares, capped: on a wide desktop the cap stops a year of study from
   turning into a wall calendar, and what is left over is a few pixels rather
   than the 300px gutter this card used to end on. */
.heatmap-card[data-heat-fit="1"] .heat-canvas {
    width: 100%;
    max-width: calc(var(--heat-cols, 53) * var(--heat-max) + (var(--heat-cols, 53) - 1) * var(--heat-gap));
}

.heat-months {
    grid-row: 1;
    display: grid;
    grid-template-columns: repeat(var(--heat-cols, 53), var(--heat-size));
    gap: var(--heat-gap);
    /* holds its line even when no label fits, so the rail below stays aligned */
    min-height: var(--heat-month-h);
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--heat-month-h);
    line-height: 1;
    color: var(--text-muted);
}

.heatmap-card[data-heat-fit="1"] .heat-months {
    grid-template-columns: repeat(var(--heat-cols, 53), minmax(0, 1fr));
}

.heat-months span {
    white-space: nowrap;
}

.heat-days {
    /* clears the month strip, so row 1 of the rail is row 1 of the grid */
    margin-top: calc(var(--heat-month-h) + var(--heat-row-gap));
    display: grid;
    grid-template-rows: repeat(7, var(--heat-size));
    gap: var(--heat-gap);
    font-family: 'IBM Plex Mono', monospace;
    font-size: .66rem;
    line-height: 1;
    color: var(--text-muted);
    align-items: center;
    justify-items: end;
}

.heatmap-card[data-heat-fit="1"] .heat-days {
    grid-template-rows: repeat(7, 1fr);
}

.heat-days span:nth-child(1) {
    grid-row: 2;
}

.heat-days span:nth-child(2) {
    grid-row: 4;
}

.heat-days span:nth-child(3) {
    grid-row: 6;
}

.heatmap-grid {
    grid-row: 2;
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: repeat(7, var(--heat-size));
    grid-auto-columns: var(--heat-size);
    gap: var(--heat-gap);
}

/* aspect-ratio rather than a JS-computed pixel size: the rows take their height
   from the column width, so the squares stay square at any card width without
   the grid having to be re-measured and re-painted on every resize frame. */
.heatmap-card[data-heat-fit="1"] .heatmap-grid {
    grid-template-rows: repeat(7, auto);
    grid-auto-columns: minmax(0, 1fr);
}

.heatmap-card[data-heat-fit="1"] .heatmap-grid .heat-cell {
    width: auto;
    height: auto;
    aspect-ratio: 1;
}

.heat-cell {
    width: var(--heat-size, 11px);
    height: var(--heat-size, 11px);
    border-radius: 2px;
    background: var(--heat-0, var(--bg-sunken));
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border) 40%, transparent);
}

.heat-cell.l1 {
    background: var(--heat-1);
}

.heat-cell.l2 {
    background: var(--heat-2);
}

.heat-cell.l3 {
    background: var(--heat-3);
}

.heat-cell.l4 {
    background: var(--heat-4);
}

/* Days outside the selected range, and days that simply haven't happened yet,
   render as nothing at all — a future month should not read as "you did no work". */
.heat-cell.pad {
    background: transparent;
    box-shadow: none;
}

/* outline-offset so the marker rings the square instead of eating it — at the
   8px dense size a 1.5px inset ring is most of the cell */
.heat-cell.ms-exam,
.heat-cell.ms-pass,
.heat-cell.ms-result {
    outline-offset: 1px;
}

.heat-cell.ms-exam {
    outline: 1.5px solid var(--accent);
}

.heat-cell.ms-pass {
    outline: 1.5px solid var(--gold);
}

.heat-cell.ms-result {
    outline: 1.5px solid var(--border-strong);
}

/* Must come after the milestone rules: they're the same specificity, and a cell
   carrying a marker was otherwise the one cell with no visible focus ring —
   which, with a roving tabindex, means keyboard users lose their place. */
.heat-cell:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    position: relative;
    z-index: 2;
}

.heat-empty {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .78rem;
    color: var(--text-muted);
    margin: 14px 0 0;
}

/* ---------- foot: stat strip left, milestone key right ---------- */
.heat-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 14px 24px;
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px dashed var(--border);
}

/* Four numbers the reader can land on at a glance, rather than one monospace
   run-on sentence separated by middots. Equal columns with hairline dividers:
   the strip gets a rhythm of its own instead of four blocks of unrelated width
   drifting apart along the bottom of the card. */
.heat-stats {
    display: flex;
    flex-wrap: wrap;
    flex: 1 1 380px;
    gap: 12px 0;
}

.heat-stat {
    flex: 1 1 0;
    min-width: 108px;
    padding: 0 16px;
    border-left: 1px solid var(--border);
}

.heat-stat:first-child {
    padding-left: 0;
    border-left: 0;
}

.heat-stat b {
    display: block;
    font-family: 'IBM Plex Mono', monospace;
    font-variant-numeric: tabular-nums;
    font-size: 1.4rem;
    font-weight: 600;
    line-height: 1.15;
}

.heat-stat span {
    display: block;
    font-size: .72rem;
    color: var(--text-muted);
}

.heat-stat i {
    display: block;
    font-style: normal;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .66rem;
    color: var(--text-muted);
    opacity: .75;
}

.heat-key {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px 18px;
}

.heat-key:empty {
    display: none;
}

/* An empty range empties both halves of the footer; drawing its rule anyway
   leaves a line ruled under nothing, right below the "no activity" message. */
.heat-foot:has(.heat-stats:empty):has(.heat-key:empty) {
    display: none;
}

.heat-key li {
    display: flex;
    align-items: center;
    gap: 7px;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .72rem;
    color: var(--text-muted);
}

/* the swatch mirrors a grid marker: an empty square with a coloured ring */
.heat-key .dot {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    flex: none;
    background: var(--heat-0);
    box-shadow: inset 0 0 0 1.5px var(--border-strong);
}

.heat-key .dot.exam {
    box-shadow: inset 0 0 0 1.5px var(--accent);
}

.heat-key .dot.pass {
    box-shadow: inset 0 0 0 1.5px var(--gold);
}

/* same ring as the base — spelled out so it's clear the kind isn't missing */
.heat-key .dot.result {
    box-shadow: inset 0 0 0 1.5px var(--border-strong);
}

.heat-legend {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: .72rem;
    color: var(--text-muted);
    font-family: 'IBM Plex Mono', monospace;
}

/* the legend is a fixed key — it shouldn't grow or shrink with the grid */
.heat-legend .heat-cell {
    width: 11px;
    height: 11px;
    flex: none;
}

.heat-tip {
    position: absolute;
    z-index: 20;
    pointer-events: none;
    background: var(--text);
    color: var(--bg);
    font-family: 'IBM Plex Mono', monospace;
    font-size: .72rem;
    line-height: 1.45;
    padding: 6px 9px;
    border-radius: var(--radius);
    white-space: nowrap;
    transform: translate(-50%, -100%);
    box-shadow: var(--shadow);
}

@media (max-width: 720px) {
    .heatmap-card {
        padding: 22px 18px 18px;
    }

    .heat-toolbar,
    .heat-foot {
        justify-content: flex-start;
    }

    .heat-stats {
        flex: 1 1 100%;
    }

    .heat-stat {
        min-width: 96px;
        padding: 0 12px;
    }

    .heat-key {
        justify-content: flex-start;
    }
}

/* ================= EXAM READINESS =================

   Sits above the ring, the heatmap and the analytics because it is the only
   block on this page that points forward. It gets --border-strong and the
   raised surface for the same reason: on a page of same-weight bordered cards,
   the one that carries the headline has to be able to say so.

   Each of the two bar idioms on this page owns one variable, and colour never
   means two things at once:

     readiness meters (here)   length = readiness, colour = readiness severity
     coverage bars (below)     length = coverage,  colour = recent accuracy

   So a red meter is "you are behind" and a red bar is "you do not know this" --
   related, but not the same claim, and a reader can tell which is being made
   from where the bar is sitting.
*/
.readiness-card {
    position: relative;
    border: 1px solid var(--border-strong);
    background: var(--bg-raised);
    padding: 26px 28px 22px;
    margin-bottom: 24px;
}

.rd-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
}

.rd-head-text {
    flex: 1 1 340px;
}

.rd-head-text h3 {
    font-size: 1.5rem;
    margin-bottom: 6px;
}

.rd-sub {
    color: var(--text-muted);
    font-size: .82rem;
    max-width: 68ch;
    margin: 0;
}

/* The countdown is the second-loudest thing in the card. A readiness score with
   no clock beside it is a fact; with the clock it is a decision. */
.rd-countdown {
    flex: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: 96px;
    padding: 8px 14px;
    border: 1px solid var(--border);
    background: var(--bg);
}

.rd-countdown b {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 1.9rem;
    line-height: 1.1;
    color: var(--accent);
}

.rd-countdown span {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .64rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--text-muted);
}

/* Dashed rules rather than solid: the tear-line motif the rest of the product
   uses to separate a stub from its ticket, which is the relationship between
   the headline and the per-section breakdown under it. */
.rd-hero {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 26px;
    align-items: center;
    margin-top: 22px;
    padding: 20px 0;
    border-top: 1px dashed var(--border-strong);
    border-bottom: 1px dashed var(--border-strong);
}

.rd-hero-fig {
    flex: none;
    text-align: center;
    min-width: 108px;
}

.rd-hero-fig b {
    display: block;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 3.2rem;
    line-height: 1;
    letter-spacing: -.02em;
}

.rd-hero-fig span {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .64rem;
    text-transform: uppercase;
    letter-spacing: .09em;
    color: var(--text-muted);
}

.rd-hero-fig.good b {
    color: var(--success);
}

.rd-hero-fig.mid b {
    color: var(--gold);
}

.rd-hero-fig.low b {
    color: var(--danger);
}

.rd-hero-fig.neutral b {
    color: var(--text-muted);
}

.rd-hero-name {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 700;
    font-size: .95rem;
    margin-bottom: 9px;
}

.rd-hero-eq {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .74rem;
    color: var(--text-muted);
    margin-top: 8px;
}

.rd-hero-eq em {
    font-style: normal;
    color: var(--text);
    padding: 0 3px;
}

.rd-hero-eq-note {
    opacity: .78;
}

.rd-advice {
    font-size: .86rem;
    margin: 10px 0 0;
    max-width: 78ch;
}

/* ---------- meter ----------
   The track is the same sunken surface the coverage bars use, so a reader does
   not have to learn two bar idioms on one page. The two ticks are the whole
   point of the component: a bar with no target is just a shape. */
.rd-meter {
    position: relative;
    height: 10px;
    background: var(--bg-sunken);
}

.rd-meter-fill {
    height: 100%;
    background: var(--accent);
}

.rd-meter-fill.good {
    background: var(--success);
}

.rd-meter-fill.mid {
    background: var(--gold);
}

.rd-meter-fill.low {
    background: var(--danger);
}

.rd-meter-fill.neutral {
    background: var(--text-muted);
    opacity: .45;
}

.rd-meter-mark {
    position: absolute;
    top: -3px;
    bottom: -3px;
    width: 2px;
    margin-left: -1px;
    background: var(--text);
}

/* The 65 floor is the softer of the two rules, biting only when the average
   already clears, so it is drawn as the quieter tick. */
.rd-meter-mark.floor {
    background: var(--text-muted);
    opacity: .5;
    top: 0;
    bottom: 0;
}

.rd-meter.settled {
    display: flex;
    align-items: center;
    justify-content: center;
    background: repeating-linear-gradient(135deg, var(--bg-sunken) 0 4px, transparent 4px 8px);
}

.rd-meter.settled span {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .6rem;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--text-muted);
    background: var(--bg-raised);
    padding: 0 6px;
}

/* ---------- per-section rows ---------- */
.rd-rows {
    margin-top: 4px;
}

/* Each row is its own grid, so the two content-sized columns have to be pinned
   to a fixed width or they resolve per row: "~1000q weighted" made TAX's
   numbers sit 12px right of AUD's, and four meters started at four different x
   positions. Everything past the name column is a column of numbers to be read
   downwards, and a column that does not line up is not a column. */
.rd-row {
    display: grid;
    grid-template-columns: minmax(180px, 1.3fr) minmax(120px, 1fr) 316px 116px;
    gap: 20px;
    align-items: center;
    padding: 15px 0;
    border-bottom: 1px solid var(--border);
}

.rd-row:last-child {
    border-bottom: none;
}

/* A passed section is context, not a to-do. It stays on the list because its
   absence would read as "not started", but it recedes. */
.rd-row[data-state="passed"] {
    opacity: .62;
}

.rd-row-name {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 8px;
    font-size: .88rem;
    min-width: 0;
}

.rd-row-name b {
    font-weight: 700;
}

.rd-row-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}

.rd-row-when {
    flex-basis: 100%;
    font-size: .68rem;
    color: var(--text-muted);
}

.rd-row-nums {
    display: flex;
    justify-content: flex-end;
    gap: 14px;
}

.rd-num {
    text-align: right;
    width: 96px;
    flex: none;
}

.rd-num i {
    display: block;
    font-family: 'IBM Plex Mono', monospace;
    font-style: normal;
    font-size: .6rem;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--text-muted);
}

.rd-num b {
    display: block;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 1.05rem;
    line-height: 1.25;
}

.rd-num em {
    display: block;
    font-style: normal;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .62rem;
    color: var(--text-muted);
}

/* ---------- sparkline ----------
   One series, so no legend: the row's own name is the label. Ink is muted for
   the run and accent for the latest week, because the question the strip
   answers is "is the recent number going anywhere", not "what was week 4". */
.rd-row-spark {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    min-height: 30px;
}

/* A settled section draws no trend line, and an empty flex box still claims
   its min-height -- which on a narrow layout, where the sparkline gets its own
   grid row, left a 40px hole under every passed section. */
.rd-row-spark:empty {
    display: none;
}

.rd-spark {
    overflow: visible;
}

.rd-spark-line {
    fill: none;
    stroke: var(--text-muted);
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.rd-spark-dot {
    fill: var(--text-muted);
}

.rd-spark-now {
    fill: var(--accent);
}

.rd-spark-pass {
    stroke: var(--border-strong);
    stroke-width: 1;
}

.rd-spark-hit {
    fill: transparent;
}

.rd-spark-hit:hover {
    fill: var(--text);
    fill-opacity: .06;
}

.rd-spark-empty {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .62rem;
    color: var(--text-muted);
    width: 116px;
}

.rd-empty {
    color: var(--text-muted);
    font-size: .86rem;
    padding: 12px 0 0;
}

.rd-tip {
    position: absolute;
    z-index: 20;
    pointer-events: none;
    background: var(--text);
    color: var(--bg);
    font-family: 'IBM Plex Mono', monospace;
    font-size: .72rem;
    line-height: 1.45;
    padding: 6px 9px;
    border-radius: var(--radius);
    white-space: nowrap;
    transform: translate(-50%, -100%);
    box-shadow: var(--shadow);
}

/* The rows reflow on the card's own width, not the viewport's: beside the
   248px sidebar a 1024px window leaves the card too narrow for four columns
   long before any viewport breakpoint fires. */
.readiness-card {
    container: readiness / inline-size;
}

@container readiness (max-width: 820px) {
    .rd-row {
        grid-template-columns: 1fr 316px;
        gap: 10px 16px;
    }

    .rd-row-nums {
        order: 2;
    }

    .rd-row-meter {
        grid-column: 1 / -1;
        order: 3;
    }

    .rd-row-spark {
        grid-column: 1 / -1;
        order: 4;
    }
}

@media (max-width: 720px) {
    .readiness-card {
        padding: 22px 18px 18px;
    }

    .rd-hero {
        grid-template-columns: 1fr;
        gap: 14px;
        justify-items: start;
    }

    .rd-hero-fig {
        text-align: left;
        min-width: 0;
    }

    .rd-hero-body {
        width: 100%;
    }
}

@container readiness (max-width: 520px) {
    .rd-row {
        grid-template-columns: 1fr;
    }

    .rd-row-nums {
        justify-content: flex-start;
        gap: 12px;
    }

    .rd-num {
        text-align: left;
        width: auto;
        min-width: 72px;
    }

    .rd-row-spark {
        justify-content: flex-start;
    }
}

/* ---------- the coverage bars, now carrying accuracy in their colour ---------- */
.prog-bars {
    min-width: 0;
}

.prog-bars-key {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
    margin: 16px 0 0;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .68rem;
    color: var(--text-muted);
}

.prog-bars-key .key-item {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.key-swatch {
    width: 12px;
    height: 8px;
    flex: none;
    background: var(--text-muted);
}

.key-swatch.good {
    background: var(--success);
}

.key-swatch.mid {
    background: var(--gold);
}

.key-swatch.low {
    background: var(--danger);
}

.key-swatch.neutral {
    background: var(--text-muted);
    opacity: .45;
}

@media (max-width: 900px) {
    .progress-top {
        grid-template-columns: 1fr;
        text-align: center;
        justify-items: center;
    }
}
