/* ==========================================================================
   Charts — inline SVG rendered at build time, styled here.
   Rules and ticks use the kit connector grey; marks use the tier scale.
   ========================================================================== */

.chart { width: 100%; height: auto; overflow: visible; font-family: var(--font-text); }

/* Three weights of line, and the order of them is the whole hierarchy: the
   gridlines are the palest thing on the page so a bar always outweighs the
   scale behind it, the axis the bars stand on is a connector, and zero — the
   line every value is read against — is the same connector drawn heavier. It
   used to be `--ink-3`, which is the colour of the tick labels: a structural
   line as dark as the words next to it competes with the marks, and on the
   diverging charts the reader met a near-black vertical rule down the middle
   of the plot that carried no value of its own. */
.chart .grid-line { stroke: var(--rule); stroke-width: 1; }
.chart .zero-line { stroke: var(--connector); stroke-width: 1.5; }
.chart .axis-line { stroke: var(--connector); stroke-width: 1; }
/* The tick mark is the axis line's own serif and belongs to it, so it is drawn
   in the same colour and weight. It exists because a number floating under a
   plot points at nothing: the mark is what says this label is read at *this*
   position, and it is the piece that lets a chart drop its interior gridlines
   without the scale becoming a guess. */
.chart .tick-mark { stroke: var(--connector); stroke-width: 1; }
/* The hairline that divides a chart into panels. It is the palest line in the
   kit on purpose — it separates two halves of one figure, and a divider drawn
   as heavily as the axis claims to be a second axis. It also had no rule until
   now, which for a `<line>` means `stroke: none`: the divider was in the markup,
   in the accessibility tree, and invisible. */
.chart .panel-rule { stroke: var(--rule); stroke-width: 1; }

.chart .tick {
  font-family: var(--font-data);
  font-size: 11.5px;
  fill: var(--ink-3);
}
.chart .axis-title {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.005em;
  fill: var(--ink-2);
}
.chart .cat-label { font-size: 12.5px; fill: var(--ink); }
/* Two names for one treatment, because they are the same thing in two charts: a
   reading set in a column beside the mark rather than on it. The slope chart's
   tier text had no rule at all, so it inherited the SVG default — 16px of the
   body face, larger and darker than the disease name it was annotating and wide
   enough that "Tier 2 · #3 of 41" ran back into "Idiopathic pulmonary fibrosis".
   A missing declaration is not a neutral one: it is whatever the user agent
   guesses, and here the guess outranked the data. */
.chart .val-label,
.chart .tier-text {
  font-family: var(--font-data);
  font-size: 11px;
  fill: var(--ink-2);
}
.chart .quad-label {
  font-size: 11.5px;
  fill: var(--ink-3);
}
.chart .quad-label--tier { font-weight: 600; fill: var(--ink-2); }

/* Marks ------------------------------------------------------------------ */
.chart .mark {
  transition: opacity var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out);
  transform-box: fill-box;
  transform-origin: center;
  cursor: pointer;
}
/* Hover is a pointer already on the mark, so the browser's default ring adds
   nothing there and is dropped. Focus is the opposite case and the ring is the
   whole point of it: a keyboard reader has no pointer, and the ring is the only
   thing on the screen telling them which of a hundred marks they are standing
   on. `outline: none` was being applied to both, which the dot charts got away
   with because the two rules below redraw the dot bigger and give it a dark
   collar — but a bar has neither, so on the 50 bar marks of this report the ring
   was removed and nothing replaced it: tabbing into the chart moved focus
   through fifty stops with no visible change of any kind, and the reader's only
   way of knowing where they were was the tooltip.

   The declaration is repeated for `.bar` rather than being left to the general
   `:focus-visible` rule in base.css, because that rule is written inside
   `:where()` and carries zero specificity, so `.chart .mark:hover` above it —
   or anything else at all — outranks it. Named here it wins on its own terms.

   `outline-offset` is what keeps the ring off the mark. A bar is a filled
   rectangle and a ring drawn flush against its edge reads as a border on the
   bar, which is to say as part of the data. Two pixels of air says it belongs
   to the browser and not to the chart. */
.chart .mark:hover { outline: none; }
.chart .mark:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 2px;
}
.chart .dot { r: 5.5; stroke: var(--card); stroke-width: 1.5; }
.chart .mark:hover .dot, .chart .mark:focus-visible .dot { r: 7.5; }
.chart .mark:focus-visible .dot { stroke: var(--ink-strong); stroke-width: 2; }

.chart .bar { transition: opacity var(--dur-2) var(--ease-out); }
.chart .mark:hover .bar { opacity: 0.85; }

.chart .mark.is-dim { opacity: 0.12; }
.chart .is-off { display: none; }

/* Tier and series fills -------------------------------------------------- */

/* The default fill, used by any chart whose caller passes no class function.
   It has to exist: without it the kit's most general chart draws unfilled
   bars, and the only way to get a coloured one is to borrow a class named
   after some other report's series.

   The series scale is the Insilico product palette from tokens.css —
   Chemistry42 blue, PandaOmics teal, Medicine42 purple, Biology42 olive —
   drawn at full strength; nothing here mutes them. The brand green is
   deliberately absent: it belongs to the chrome and to "supports", and a
   data series must not borrow a verdict colour. Legend swatches take the
   same classes, so the key and the marks cannot drift apart. */
.s-a { fill: var(--series-a); }
.s-b { fill: var(--series-b); }
/* Two more, so a chart drawing a third or fourth series does not have to reach
   for a tier colour that means something else. */
.s-c { fill: var(--series-c); }
.s-d { fill: var(--series-d); }

.t1 { fill: var(--tier-1); }
.t2 { fill: var(--tier-2); }
.t3 { fill: var(--tier-3); }
.t4 { fill: var(--tier-4); }
/* The category neutral: the bar that is on the chart to be counted rather
   than to be looked at. It was `--ink-4`, which is a text grey for disabled
   controls and axis ticks — dark enough that on the two charts where most
   rows are neutral, the neutrals were the heaviest marks in the figure and
   the one coloured bar the chart existed to point at was the palest. The
   connector grey is the scale the rest of the kit already uses for "present,
   not the subject", and reaching it through `--cat-4` says that this is a
   category slot and not a borrowed text colour. */
.s-neutral { fill: var(--cat-4); }
.s-hold { fill: var(--ink-3); }   /* matches the slope chart's unmoved rows */
.s-caution { fill: var(--caution); }
.s-flag { fill: var(--flag); }

.chart .label-hi { font-weight: 600; fill: var(--ink-strong); }
.chart .no-data { font-family: var(--font-data); font-size: 10px; fill: var(--ink-4); }
/* The marker standing where a measurement is missing, and it is hollow for a
   reason that survives being photocopied: an outline is the one mark on a chart
   that cannot be mistaken for a quantity, because there is nothing inside it.
   It also had no rule, so it took the SVG default fill of solid black — on
   Figure 4 the six diseases the source never measured were drawn as the darkest
   marks in the figure, which is the exact opposite of what the absence means.
   Sized to the connector rather than the ink scale for the same reason the
   neutral bar is: present, not the subject. */
.chart .dot-empty { fill: none; stroke: var(--connector); stroke-width: 1.25; }

/* Small multiples and sparkbars ------------------------------------------ */
/* The bar sits beneath its value, right-aligned under it, rather than beside
   it. A numeric column is about as wide as the number it holds, so a bar on the
   same line has nowhere to go: it is drawn across the next column's figures,
   which reads as a chart overlapping the data it describes. Narrowing to fit
   keeps every bar on the same scale, because the scale is set by the viewBox. */
.spark {
  width: 76px; height: 14px; max-width: 100%;
  display: block; margin: 2px 0 0 auto;
  overflow: visible;
}
/* One datum, drawn twice in one cell: the number and the bar under it. They
   used to be coloured by two different systems that did not know about each
   other — the number took the row's tone class from the profile, the bar took
   its sign from here — so the same figure could print as violet text over a
   blue bar, while the cell next to it printed green on green. Two colours on
   one value is two claims about it, and the reader has to guess which of them
   the page meant.

   `currentColor` makes the coupling structural rather than a pair of
   declarations that happen to agree today: the bar is the cell's text colour,
   whatever that turns out to be, and no future tone class can separate them.
   The sign is still on the page twice over — the minus on the number and the
   bar's own direction from the centre axis — so nothing was carried by the
   old hue split that is not still there without it. The opacity is a weight,
   not a second colour: at full strength a bar as wide as the number above it
   reads as heavier than the figure it is annotating. `pos` and `neg` stay in
   the markup as hooks.

   0.4, not the 0.75 this started at, and the number is arrived at by area
   rather than by eye. A bar at the middle of the column's range covers about
   30 by 8 pixels, or 240 square pixels of solid fill; the six glyphs of
   "+0.414" above it lay down something near 150. At 0.75 the annotation was
   carrying more ink than the figure it annotates, which is the wrong way
   round, and in a table forty rows deep the effect was a column of dark
   dashes with the numbers reading as their captions. At 0.4 the two are about
   level, and the bar does what it is for — letting the eye run down the column
   and find the long ones without reading a single digit — without being the
   first thing the eye finds. */
.spark rect { fill: currentColor; fill-opacity: 0.4; }
/* The stroke width has to be told not to scale, and until it was, the axis this
   whole figure is built around did not draw at all.
   A stroke width inside an SVG is in user units, and these user units are not
   pixels: the box is 76 wide by declaration but 58 wide on the page, because
   `max-width: 100%` narrows it to the column and `preserveAspectRatio` then
   scales the drawing uniformly to fit. Everything in the picture is therefore
   drawn at 0.76 of its stated size, which is harmless for a rectangle and fatal
   for a hairline: one unit became three-quarters of a pixel, and a
   three-quarter-pixel line of #E8E8E8 antialiases to #F8F8F8, three per cent
   grey. Photographed, the entire spark box held exactly one colour besides
   white, and that colour was the bar. Every column of pixels either side of it
   was #FFFFFF.
   That mattered more than a missing hairline usually does, because the comment
   above claims the sign is on the page twice, "the minus on the number and the
   bar's own direction from the centre axis". With no centre axis there is no
   direction to read: thirty-nine of the sixty-three bars run left from a line
   the reader cannot see, and they look like bars at arbitrary offsets rather
   than one scale with two sides. `non-scaling-stroke` takes the width out of
   the user-unit space and puts it in device pixels, where a 1 means 1.

   `crispEdges` is the second half of the same fix. The axis sits at 38 of 76,
   which lands on 660.05 on the page, so a one-pixel stroke centred there covers
   half of column 659 and half of column 660 and paints both at about #E2: the
   right quantity of ink spread over twice the width, which is a smudge rather
   than a line. Snapped, it is one column of #C3C3C3.

   The colour is the connector grey rather than the rule grey it started with,
   and it stays lighter than the bar it references. A reference mark darker than
   the data has changed places with it. */
.spark .axis {
  stroke: var(--connector);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  shape-rendering: crispEdges;
}

/* No quadrant tint rule here any more; the reason is with the code that used to
   draw it, in charts.py. Short version: a near-black green at 5.5% is grey, and
   grey is the key's colour for a different tier. */
/* Three rules stood here — `.row-stripe`, `.tier-chip` and `.chip-text` — and
   nothing in the kit drew any of them. Checked before removing them: zero
   emitters anywhere in charts.py or the profiles, zero occurrences in the
   built page, against a control class from the same block that returns eleven
   and four. They were speculative styling for charts that were never written,
   and the argument for keeping them was that one of the unbuilt profiles might
   want them one day. That is the argument for every piece of dead code, and
   the policy in this project is render or delete: a rule nobody can see is a
   rule nobody can check, and the next person to read this block would have
   spent time working out which of these classes are real. */

/* Rank slope: two prioritisations of the same disease, joined ------------- */
.chart .slope-line {
  stroke: var(--connector);
  stroke-width: 1.25;
  stroke-dasharray: 2 3;
}
.chart .slope-dot { stroke: var(--card); stroke-width: 1.5; fill: var(--ink-4); }
.chart .mark:hover .slope-dot, .chart .mark:focus-visible .slope-dot { r: 7.5; }
.chart .mark:focus-visible .slope-dot { stroke: var(--ink-strong); stroke-width: 2; }
/* The move is carried by the dot and only by the dot. The leader used to take
   the state colour too, which meant every row drew a dashed coloured rule
   across two thirds of the chart's width, and the colour a reader met first —
   the long line — was the piece of the row that carries no value. An earlier
   draft of this comment counted those rules as eleven, which was this report's
   row count at the time and is four today; the row count belongs to the
   dossier, not to the stylesheet, and the argument never depended on it.
   The leader is a connector: it says which dot belongs to which name, nothing
   more, and it stays the connector grey the rest of the kit uses for exactly
   that. Left with the state colour it also broke the house rule twice over,
   once for green as a fill on something that is not a verdict and once for a
   tone applied to a line rather than to a mark. */
.chart .slope-row.falls .slope-dot { fill: var(--caution); }
.chart .slope-row.rises .slope-dot { fill: var(--supports); }
.chart .slope-row.holds .slope-dot { fill: var(--ink-3); }
.chart .hit { cursor: pointer; }

/* --- Marks arriving -----------------------------------------------------
   The figures on this page did not move at all. Every other block faded up and
   the charts inside them faded up with their frames, whole, which is the one
   thing a chart entrance should never do: a bar arriving at full length says
   nothing about what it measures, and eight figures fading in unison says only
   that a script ran.

   So the frame and the data are separated. The axis, the gridlines and the tick
   labels are the page's furniture and arrive with the figure; the marks are the
   reading, and a mark whose length is its value arrives out of the axis in the
   direction the reader takes it off — a bar grows from zero, a slope leader
   draws out until its dot can land on the end of it. The information that
   motion carries is where the zero is and which way the value went, which are
   the two things a reader looks for first and the two the old fade withheld.

   A dot is given neither, because it has neither. Its value is its position,
   and a position is not something that can be laid down by degrees the way a
   length can, so anything a dot does on the way in is decoration: it fades, at
   full size, exactly where it belongs. What remains is the order the dots fade
   in, and that is a real reading — `reading_order()` in charts.py sequences the
   scatter along its x axis — which is why the order is the one part of a dot's
   entrance this block still spends time on.

   `cat-label` is furniture and is absent from every rule below, which is worth
   stating because it is the one exclusion that reads as an oversight. On a
   horizontal bar chart the category names are the y axis: they are that
   chart's tick labels, set in a gutter instead of under the plot, and a name
   flying in beside its own bar is an axis animating itself. The scatter's
   corner readings, `quad-label`, are out on the same argument — they name
   regions of the plot rather than values in it.

   Written as keyframes rather than as transitions on purpose. Every mark
   already carries a `transition` for hover and for the dimming a filter
   applies, and an entrance written as a second transition on the same
   properties would be one declaration fighting another: a bar caught mid-entry
   by a legend press would finish neither. Animations run on their own timeline,
   leave nothing behind them, and cannot be interrupted by a class the reader
   causes.

   Every rule below hangs off `.js-anim .figure.reveal.is-in`, so a page with no
   script, a page in print and a page whose reveal has already been stood down
   draw their marks at full size, immediately, as they always did. Reduced
   motion is not switched off here either: tokens.css zeroes `--dur-mark` and
   `--stagger-mark` under the media query, so these keyframes still run, still
   run in order, and take no time at all doing it.

   The figure is named rather than left as any `.reveal`, and the difference is
   not pedantry. `.reveal` sits on eight kinds of block in this kit and the
   script adds `is-in` to every one of them, so an unqualified
   `.reveal.is-in .chart` starts a figure's marks the moment any revealing
   ancestor arrives. Today the only revealing ancestor a chart has is its own
   figure, which is why the loose form appeared to work — but the first chart
   dropped into a section wrapper or a grid card would begin its entrance a
   screen and a half below the fold and finish it before anyone reached it. That
   failure is silent: the marks are drawn, correctly, and the only symptom is an
   animation nobody meets. Bound to the figure, the trigger is the block that
   holds the chart, and a chart placed somewhere without one does not animate at
   all — which is the behaviour this page had last week, and is at least visible
   for what it is.

   Nothing below animates `.mark`, though it is the group holding these marks
   and the obvious handle for all of them at once. Two of these charts put a
   transparent `.hit` rectangle inside that group, to give a thin row a pointer
   target the full height of its band, and a group that scales or fades takes
   the hit area with it: for the half second of the entrance the pointer would
   report values for rows that are not where the row appears to be. The leaves
   move; the group stays where the pointer expects it. */

@keyframes mark-bar-in { from { transform: scaleX(0.001); } }
/* Two names holding one declaration, and they stay two names. A mark's fade and
   the fade of the number beside it are the same instruction today and are not
   the same thing: they run for different lengths on different curves, and the
   first change to either — a mark that wants a settle at the end, a label that
   must never have one — would have to prise them apart again. Folded into one
   keyframe the file would also be stating that a reading and its annotation
   arrive by the same rule, which is the single relationship this whole block
   exists to keep separate. */
@keyframes mark-dot-in { from { opacity: 0; } }
@keyframes mark-line-in { from { transform: scaleX(0.001); } }
@keyframes mark-label-in { from { opacity: 0; } }

/* Only the marks whose entrance is a transform need a box to measure it in,
   which since the dot lost its scale is the bar and the slope leader and
   nothing else. It is stated on the elements themselves and cannot be taken
   from the `.mark` group they sit inside: `transform-box` does not inherit, so
   the declaration on `.mark` reaches `.mark` and stops there.

   The default it would otherwise fall back to is `view-box`, and that is not a
   near miss. Under `view-box` the `transform-origin` each of these marks is
   given below means an edge of the entire SVG rather than an edge of the mark
   itself, so every mark in the figure would collapse to the outer edge of the
   plot and sweep back across the picture to its place — a wipe of the whole
   chart, and one that crosses the axis the bar is supposed to be leaving.
   `fill-box` is the element's own bounding box, which makes each mark's origin
   its own edge, which is the only edge that means anything. */
.js-anim .figure.reveal.is-in .chart .bar,
.js-anim .figure.reveal.is-in .chart .slope-line {
  transform-box: fill-box;
}

/* `--mark-i` is a mark's place in its figure's arrival order, written into the
   markup at build time by `arrival()` in charts.py. It goes on the group that
   holds a row's marks rather than on each mark: a custom property inherits, and
   a row's two bars, the number beside them and the hollow marker standing in
   for a missing reading are all placed at the same step of the sequence.

   The step is not the same thing as the moment. What each rule below does with
   the index it inherits is that rule's own argument — the number beside a mark
   and the slope chart's dot are both held back from their step by a fraction of
   a mark's duration — and the offsets are written where they are reasoned about
   rather than folded into the index, so that an index remains readable as a
   place in a queue and nothing else.

   An index and not a delay, which is the whole reason it is spelled this way. A
   built page carrying `animation-delay: 312ms` in an attribute would have this
   stylesheet's timing set into eight figures and every one of the two hundred
   odd marks in them, and retuning the entrance — or deciding against it — would
   mean rebuilding every report rather than editing one token. It also could not
   be zeroed by the reduced-motion query, because an inline declaration outranks
   a stylesheet and there is nothing the media query could reach.

   The index is capped where it is written, so a twenty-row chart does not spend
   longer arriving than a five-row one; the arithmetic and the argument for it
   are with the code that does it. The `0` fallback is what a page built before
   any of this existed gets: every mark at once, which is where this started. */

/* Out of the axis, in the direction the value goes. Which edge is pinned is
   decided at build time — an SVG rectangle is a left edge and a width, so the
   two directions are written identically and only the sign the bar came from
   tells them apart. */
.js-anim .figure.reveal.is-in .chart .bar--grows-right { transform-origin: left center; }
.js-anim .figure.reveal.is-in .chart .bar--grows-left  { transform-origin: right center; }
.js-anim .figure.reveal.is-in .chart .bar {
  animation: mark-bar-in var(--dur-mark) var(--ease-mark) both;
  animation-delay: calc(var(--mark-i, 0) * var(--stagger-mark));
}

/* A dot fades, at full size, in its place. It used to come up from `scale(0.3)`
   as well, and the scale was the one thing in this block that taught the reader
   nothing. A bar's growth is a claim about the data — it says length is the
   quantity being laid down, and `grows-left`/`grows-right` keeps the sign
   readable for the whole half second it takes. A dot has neither a length nor a
   direction; its value is where it is, and a position cannot be laid down by
   degrees. So a dot swelling into place says only that a script is running,
   which is the generic-template motion this report exists not to look like.

   On the scatter the scale was worse than empty. Every point on that plot is
   drawn at r=5.5; the radius is a constant of the figure and not one of its
   encodings, and the only thing that ever moves it is the pointer resting on a
   point. An entrance that walks all twenty-seven of them through a spread of
   radii therefore contradicts the chart's own key for as long as it lasts, and
   does it at the one moment the reader is learning how to read the plot.

   The arrival order survives untouched, and it is the part that was carrying
   information all along: `reading_order()` in charts.py sequences the scatter's
   points along x, so they still come in left to right, which is the direction
   the eye runs that plot in before it climbs the other axis. `dot-empty` keeps
   the same treatment for a stronger version of the same argument — it stands
   where the source took no measurement, and a marker that grows implies a
   quantity accumulating at the one place on the chart where there is none.

   No `transform-box` or `transform-origin` here any more either. Both existed
   solely to point that scale at the dot's own centre, and kept on they would be
   two declarations describing a transform that no longer happens: the next
   reader would go looking for the motion they configure.

   `.slope-dot` is in this list for the fade and for nothing else. Its timing is
   taken back off it below, where the leader it lands on is argued for. */
.js-anim .figure.reveal.is-in .chart .dot,
.js-anim .figure.reveal.is-in .chart .slope-dot,
.js-anim .figure.reveal.is-in .chart .dot-empty {
  animation: mark-dot-in var(--dur-mark) var(--ease-mark) both;
  animation-delay: calc(var(--mark-i, 0) * var(--stagger-mark));
}

/* The leader draws out from the axis the row is measured against, and its
   length is the reading: `rank_slope()` in charts.py runs the line from the
   first tick to the dot, so how far it reaches is how far down the field the
   row sits. That is the whole reason the dot is not allowed to be there first.

   Both used to run the full `--dur-mark` off the same delay, which means they
   started on one frame and ended on one frame, and the sentence that stood here
   — that the line "finishes a beat before the dot lands on the end of it" —
   described a beat the arithmetic never produced. What a reader actually met
   was a dot already sitting at its final rank, attached to nothing, while a
   dashed line crawled out towards it for half a second: the motion said the dot
   was the answer and the leader an ornament arriving late, when on this chart
   the leader is the answer and the dot is only its end.

   So the dot is held for 55% of the leader's run and given the remaining 45%.
   They still land together, which is deliberate — a dot appearing after the
   line had already stopped would read as a second event and put a stall in the
   middle of a single row. The 55% is measured on `--ease-mark`, where it is 97%
   of the leader's final length, so for everything but the last hair of its
   reach the line is on the page alone and there is nothing to read but how far
   it has got.

   The value labels below wait by the same 0.55, and the repetition is the
   point rather than a duplication to be tidied away: it is one rule written in
   the two places it applies — nothing may be legible before the mark it reads —
   and both measure that moment from the same point on the same curve. A second
   number here would be a second answer to a question the file has already
   settled. */
.js-anim .figure.reveal.is-in .chart .slope-line {
  transform-origin: left center;
  animation: mark-line-in var(--dur-mark) var(--ease-mark) both;
  animation-delay: calc(var(--mark-i, 0) * var(--stagger-mark));
}
/* This has to stay below the shared dot rule and it is not a matter of taste.
   The `animation` shorthand up there resets delay and duration to their initial
   values, and the two selectors are character-for-character the same weight, so
   source order is the only thing keeping these two numbers. Moved above it, the
   slope dot silently goes back to arriving with its own leader. */
.js-anim .figure.reveal.is-in .chart .slope-dot {
  animation-delay: calc(var(--mark-i, 0) * var(--stagger-mark) + var(--dur-mark) * 0.55);
  animation-duration: calc(var(--dur-mark) * 0.45);
}

/* The number beside a mark is the mark's reading and must not be legible
   before the mark it reads. It only fades — a number that slides is a number
   being animated for its own sake — and it starts at 0.55 of a mark's duration,
   which on `--ease-mark` is 97% of a bar's final length: the figure appears
   against a bar that is already, to the eye, as long as it is going to be.

   `no-data` is here on a slightly different footing, and the difference is
   worth stating because the delay reads as an oversight otherwise. The marker
   it annotates does not grow — it is a dot and it fades like every other dot —
   so there is no growth for these words to wait out. What the 0.55 does there
   is hold the sentence behind the marker it belongs to, which is the same rule
   asked to do less work: the reader meets the hollow mark, then reads why it is
   hollow, and never the other way round. `tier-text` is the slope chart's
   left-hand column, which is a reading of the same row from the other
   prioritisation and arrives with it; it is the one label on the page that
   carries `--mark-i` on itself rather than inheriting it, because the markup
   sets it beside the row's group instead of inside it. */
.js-anim .figure.reveal.is-in .chart .val-label,
.js-anim .figure.reveal.is-in .chart .no-data,
.js-anim .figure.reveal.is-in .chart .tier-text {
  animation: mark-label-in var(--dur-2) linear both;
  animation-delay: calc(var(--dur-mark) * 0.55 + var(--mark-i, 0) * var(--stagger-mark));
}

/* Nothing here for the sparklines in table cells. They are eight pixels tall,
   and all sixty-three on this page sit in one table — sixty-three things
   growing at once inside a single grid of numbers is not an entrance, it is a
   flicker, and it is a flicker underneath the figures whose entrance the reader
   is meant to be watching.

   Two things keep them out, and both were read off the built page rather than
   assumed, because this is the exclusion most likely to fail quietly. Every
   selector above steps through `.chart`, and a sparkline's root is `.spark`;
   and a sparkline's rectangle is classed `pos` or `neg`, never `bar`, its rule
   `axis`, never `slope-line`. The `.chart` step is the guard that matters, and
   it is doing real work: a sparkline lives in a `.table-wrap`, which is a
   `.reveal` and does get `is-in`, so a rule reaching for `.reveal.is-in .bar`
   would have caught all sixty-three of them inside a table of numbers. */

@media print {
  .chart * { animation: none !important; }
}
