/*
 * tsubu styles. Everything meant to be adjusted is a custom property, which can be set on
 * `.tsubu` or on any ancestor (a speech bubble, a theme's :root). Each default is written as
 * the fallback where the property is used. A colour property without a default leaves the
 * inherited text colour alone. Font family and size are inherited from the page.
 *
 * Layout
 *   --tsubu-line-height              1.75
 *   --tsubu-ruby-reserve             0.3em    room above the first line for its furigana
 *                                             (at the right of the first column in vertical text);
 *                                             with origins drawn, --tsubu-reading-gap more
 *   --tsubu-ruby-size                0.5em
 *   --tsubu-ruby-weight              400      (romaji and origins too)
 *   --tsubu-romaji-size              --tsubu-ruby-size
 *   --tsubu-origin-size              --tsubu-ruby-size   loanwords' origins
 *   --tsubu-origin-label-size        0.75em   an origin's language code and 和製 mark, in em of
 *                                             the origin
 *   --tsubu-under-reserve            0.5em    room below the last line for the readings under it
 *                                             (at the left of the last column in vertical text)
 *   --tsubu-reading-gap              0.2em    between the text and the readings under it (twice
 *                                             that past them), and the romaji and origins over it,
 *                                             in em of the reading: the underline runs clear of the
 *                                             one, the highlight of the other's descenders
 *                                             (furigana over the text have none)
 *   --tsubu-underline-style          dotted
 *   --tsubu-underline-width          0.06em
 *   --tsubu-underline-offset         0.02em   below the bottom of the text's em box (left of it in
 *                                             vertical text, where the underline is a side line)
 *   --tsubu-underline-inset          0.12em   in from each end, so neighbouring underlines stay apart
 *   --tsubu-current-spread           0.06em   how far the highlight reaches past the text's em box
 *                                             (a positioned layer, so it takes no room)
 *   --tsubu-current-radius           0.18em
 *   --tsubu-plate-spread             0px      how far the plate reaches past the sentence's box
 *   --tsubu-plate-radius             0
 *   --tsubu-wrap                     balance  `text-wrap` of the sentence: balance, pretty or wrap
 *   --tsubu-max-lines                none     more lines than this are paged by t (see apply());
 *                                             in vertical text, more columns
 *
 * Fluid size, inside an element with class `tsubu-fluid` (a query container): the font size is
 * the container's width divided by a number of characters per line, between a minimum and a
 * maximum. Everything else is in em, so spacing, furigana and underlines scale with it. The
 * container's width is split into three steps; the breakpoints are fixed, the values are not.
 * In vertical text the container's height takes the width's place (see below).
 *   --tsubu-size-min                 14px
 *   --tsubu-size-max                 72px
 *   --tsubu-narrow-chars             11       container narrower than 480px
 *   --tsubu-chars                    15       480px to 960px
 *   --tsubu-wide-chars               18       960px and wider
 *   --tsubu-narrow-max-lines         --tsubu-max-lines in a narrow container
 *   --tsubu-wide-max-lines           --tsubu-max-lines in a wide container
 *
 * Colour tokens. The base group applies before the first apply() and with timing "none"; each
 * state's group falls back to the base group.
 *   base     --tsubu-text-color (inherited)  --tsubu-ruby-color #6b7280  --tsubu-underline-color #9ca3af
 *   future   --tsubu-future-color   --tsubu-future-ruby-color   --tsubu-future-underline-color
 *   current  --tsubu-current-color  --tsubu-current-ruby-color  --tsubu-current-underline-color
 *            --tsubu-current-bg #e5e7eb (the highlight)
 *   past     --tsubu-past-color     --tsubu-past-ruby-color     --tsubu-past-underline-color
 *   plate    --tsubu-plate-bg (transparent): a background behind the whole sentence
 *   speakers --tsubu-speaker-<name>-bg, -color, -ruby-color, -underline-color: used instead of
 *            the current group in a sentence whose root has data-speaker="<name>"
 *   --tsubu-backdrop  the colours a theme is designed to sit on; never painted, read only by
 *                     Tsubu.checkContrast(). A list of colours, or "any" (checked on black and white).
 *
 *   --tsubu-progress                 written by apply(); read it, do not set it
 *
 * Romaji and origins take the furigana's colours in every group.
 *
 * No box changes size with the state, the switch of either side's readings, the theme or the
 * speaker: these change colours and the background of one positioned layer only, and hidden
 * readings keep their place.
 *
 * The em box. A segment's own box is the font's content area, which in many web fonts (Noto
 * Sans JP and the rest of the Noto and Source Han family, Shippori Mincho, Zen Kaku Gothic) is
 * about 1.45em tall: it reaches 0.28em above the em box of the text, into the furigana, whose ink
 * sits just above the base's em box. So the highlight and the underline are not drawn from that
 * box but from the em box, as a 1em band and a line under it. CSS cannot read the font's
 * ascent, so the em box is placed within the content area by a fixed split of the extra height,
 * --tsubu-em-lead before it and --tsubu-em-trail after it: 0.62 / 0.38, which is exact for
 * Hiragino Sans (no extra height) and for the whole Noto family. A font whose metrics split
 * differently can set the two on the sentence. Vertical text needs no split: there each segment
 * is an inline-block whose line is centred on the em box (see after the underline's rules).
 */
.tsubu {
  display: block;
  line-height: var(--tsubu-line-height, 1.75);
  /* Logical, so in vertical text the room is at the right of the first column. */
  padding-block-start: var(--tsubu-ruby-reserve, 0.3em);
  color: var(--tsubu-text-color);
  position: relative;
  isolation: isolate;
  /* Lines of a sentence that takes several are kept close in length. */
  text-wrap: var(--tsubu-wrap, balance);
  /* The number of lines per page apply() reads; the fluid steps below change it. */
  --tsubu-page-lines: var(--tsubu-max-lines, none);
}

/* apply() lays the sentence out greedily to find its lines, and hides the segments that are not
 * on the page being read. A page starts at the start of a line, so on its own it breaks into the
 * same lines, which are then balanced among themselves. Where balancing would add a line (WebKit
 * 26 can), apply() marks the page data-greedy and it keeps its greedy lines. */
.tsubu:is([data-measuring], [data-greedy]) { text-wrap: wrap !important; }
.tsubu-seg[data-off-page] { display: none; }

.tsubu-fluid { container: tsubu / inline-size; }
.tsubu-fluid .tsubu {
  --tsubu-chars-now: var(--tsubu-chars, 15);
  font-size: clamp(var(--tsubu-size-min, 14px), calc(100cqi / var(--tsubu-chars-now)), var(--tsubu-size-max, 72px));
}
@container tsubu (width < 480px) {
  .tsubu-fluid .tsubu {
    --tsubu-chars-now: var(--tsubu-narrow-chars, 11);
    --tsubu-page-lines: var(--tsubu-narrow-max-lines, var(--tsubu-max-lines, none));
  }
}
@container tsubu (width >= 960px) {
  .tsubu-fluid .tsubu {
    --tsubu-chars-now: var(--tsubu-wide-chars, 18);
    --tsubu-page-lines: var(--tsubu-wide-max-lines, var(--tsubu-max-lines, none));
  }
}

/*
 * Vertical writing: data-tsubu-writing="vertical" on the root (options.writing) or on any
 * ancestor; "horizontal" on the root keeps a sentence horizontal inside a vertical ancestor.
 * Columns run from right to left. Everything that depends on the direction is written with
 * logical properties, so it follows on its own: the furigana (ruby-position: over) go to the
 * right of the characters, the ruby reserve becomes room at the right of the first column, and
 * each segment's underline becomes a side line at its left. The highlight, its spread and the
 * plate are the same boxes turned with the text. Each segment is an inline-block there, and its
 * highlight and side line are drawn from its left edge (see the rules after the underline's).
 *
 * Full-width punctuation, brackets and the long-vowel mark take their vertical forms from the
 * font (OpenType vertical alternates, applied with text-orientation: mixed). Half-width letters
 * and digits lie on their side, except short runs, which the script wraps in .tsubu-upright to
 * be set upright across the column. text-combine-upright has no effect in horizontal text, so
 * that span changes nothing there.
 *
 * The column length is the root's height, which a vertical block takes from its container when
 * that has a definite height, and otherwise from its content, up to the viewport's height: give
 * the container a height.
 */
.tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) {
  writing-mode: vertical-rl;
  text-orientation: mixed;
}
.tsubu-upright { text-combine-upright: all; }

/* Fluid vertical text: the column runs along the container's height, so the height drives the
 * size, as the width does for lines. The container becomes a size container (it must then have
 * a definite height, since size containment ignores its content), the font size is its height
 * divided by the characters per column, and the steps go by its height. */
.tsubu-fluid:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *, :has(.tsubu[data-tsubu-writing="vertical"])):not(:has(.tsubu[data-tsubu-writing="horizontal"])) {
  container-type: size;
}
.tsubu-fluid .tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) {
  --tsubu-chars-now: var(--tsubu-chars, 15);
  --tsubu-page-lines: var(--tsubu-max-lines, none);
  font-size: clamp(var(--tsubu-size-min, 14px), calc(100cqh / var(--tsubu-chars-now)), var(--tsubu-size-max, 72px));
}
@container tsubu (height < 480px) {
  .tsubu-fluid .tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) {
    --tsubu-chars-now: var(--tsubu-narrow-chars, 11);
    --tsubu-page-lines: var(--tsubu-narrow-max-lines, var(--tsubu-max-lines, none));
  }
}
@container tsubu (height >= 960px) {
  .tsubu-fluid .tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) {
    --tsubu-chars-now: var(--tsubu-wide-chars, 18);
    --tsubu-page-lines: var(--tsubu-wide-max-lines, var(--tsubu-max-lines, none));
  }
}

/* The plate: one layer behind the sentence, reaching `spread` past its box. Positioned, so it
 * takes no room; one layer rather than a background and a box-shadow, so a translucent plate
 * has no seam where the two would meet. */
.tsubu::before {
  content: "";
  position: absolute;
  inset: calc(-1 * var(--tsubu-plate-spread, 0px));
  z-index: -1;
  background-color: var(--tsubu-plate-bg, transparent);
  border-radius: var(--tsubu-plate-radius, 0);
  pointer-events: none;
}

/* A speaker's colours take the place of the current group. When the theme has no colours for
 * that speaker, these are left unset and the current group applies. */
.tsubu[data-speaker="a"] {
  --tsubu-speaker-bg: var(--tsubu-speaker-a-bg);
  --tsubu-speaker-color: var(--tsubu-speaker-a-color);
  --tsubu-speaker-ruby-color: var(--tsubu-speaker-a-ruby-color);
  --tsubu-speaker-underline-color: var(--tsubu-speaker-a-underline-color);
}
.tsubu[data-speaker="b"] {
  --tsubu-speaker-bg: var(--tsubu-speaker-b-bg);
  --tsubu-speaker-color: var(--tsubu-speaker-b-color);
  --tsubu-speaker-ruby-color: var(--tsubu-speaker-b-ruby-color);
  --tsubu-speaker-underline-color: var(--tsubu-speaker-b-underline-color);
}

.tsubu,
.tsubu * {
  transition: none !important;
  animation: none !important;
}

/* Segments that may not be parted at a line break (Japanese line-breaking rules) share a unit;
 * a line breaks only between units. */
.tsubu-unit { white-space: nowrap; }

.tsubu-seg {
  position: relative;
  white-space: nowrap;
  text-decoration: none;
}

/* The highlight: one layer behind the segment, over the em box of its text and `spread` past
 * it on every side. Empty until the segment is current; it is always there, so the state
 * creates nothing. z-index -1 puts it behind every character of the sentence (the root is a
 * stacking context) and, in tree order, over the plate. Its colour is painted as a one-colour
 * gradient, not as a background colour: Chromium rasterises the rounded corners of a positioned
 * box's background colour differently after a partial repaint, by a pixel, and a frame must be
 * the same whatever was drawn before it; the gradient is clipped to the corners and is not.
 * checkContrast() measures where the highlight and the underline lie on probes that take the
 * same geometry (.tsubu-band-probe, .tsubu-line-probe): WebKit reports the computed insets of a
 * box positioned inside an inline against the block around it, so they cannot be read back. */
.tsubu-seg::before,
.tsubu-seg > .tsubu-band-probe {
  content: "";
  position: absolute;
  z-index: -1;
  inset-inline: calc(-1 * var(--tsubu-current-spread, 0.06em));
  inset-block-start: calc(var(--tsubu-em-lead, 0.62) * (100% - 1em) - var(--tsubu-current-spread, 0.06em));
  block-size: calc(1em + 2 * var(--tsubu-current-spread, 0.06em));
  border-radius: var(--tsubu-current-radius, 0.18em);
  background: none;
  pointer-events: none;
}

.tsubu-seg:not([data-kind="punct"])::after,
.tsubu-seg:not([data-kind="punct"]) > .tsubu-line-probe {
  content: "";
  position: absolute;
  /* Logical sides: below the segment in horizontal text, and in vertical text a side line at its
   * left, the side opposite the furigana; `offset` past the em box of the text. */
  inset-inline-start: var(--tsubu-underline-inset, 0.12em);
  inset-inline-end: var(--tsubu-underline-inset, 0.12em);
  inset-block-end: calc(var(--tsubu-em-trail, 0.38) * (100% - 1em) - var(--tsubu-underline-offset, 0.02em));
  border-block-end: var(--tsubu-underline-width, 0.06em) var(--tsubu-underline-style, dotted) var(--tsubu-underline-color, #9ca3af);
  pointer-events: none;
}
/* In vertical text each segment is an inline-block, not an inline: WebKit places a box positioned
 * inside an inline in vertical-rl text mirrored across the block's right edge, as far right of it
 * as the segment lies left of it (measured). Its line is 2em across, centred on the em box of the
 * text like every line, and so reaches 0.5em past it on the left: the highlight and the side line
 * are drawn from there. The line is that wide so it holds the font's content area too, which
 * WebKit takes into the line of a base that has a reading whatever the line height (measured
 * with Shippori Mincho B1, whose content area is 1.45em); the furigana reach past it at the
 * right. The negative margins give that room back: at the left all of it, and at the right all
 * but the column's half-leading, which the furigana may reach into as they do into an inline's,
 * so a column grows only for furigana that would reach the em box of the next. The columns are
 * as wide as with inline segments and no character moves (measured in Chromium and Firefox);
 * WebKit, which sets the furigana past the content area, widens a column only as far as they need.
 * A segment that is off the page keeps display: none. */
.tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg:not([data-off-page]) {
  display: inline-block;
  line-height: 2;
  margin-block: calc((1 - var(--tsubu-line-height, 1.75)) * 0.5em) -0.5em;
}
/* With romaji over the text and nothing under it, no room is given back at the right: romaji reach
 * further than furigana (their gap, see below), as far as the side line of the column to the
 * right. */
.tsubu[data-over-layer="romaji"]:not([data-under-layer]):is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg:not([data-off-page]) {
  margin-block-start: 0;
}
/* With readings under the text, at the left, a column's readings at its left would share the room
 * between two columns with the next column's readings at its right, and meet them. So each column
 * holds its own readings, on both sides, and none is given back to the column's half-leading.
 * The line of a segment is then wide enough on each side for every reading there can be, so that
 * no reading widens it: the highlight and the side line are drawn that far from its left edge,
 * and so stay on the em box of the text whichever readings a segment has, and every segment of a
 * column has the same box across it. Room for both layers on each side, because WebKit sets the
 * romaji past the line of the furigana's ruby rather than past its text, and widened the segment
 * by both (measured); a line height of 1 on the furigana's ruby keeps that line to the em box,
 * and the other engines do not read it there. The margins give back all but the room of each
 * side's own reading: its size, half again for fonts whose content area is taller than their em
 * box (1.45em in the Noto family), and its gaps, which here are in em of the text and so larger
 * than the reading's own. */
.tsubu[data-under-layer]:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg {
  --tsubu-seg-ruby: calc(1.5 * var(--tsubu-ruby-size, 0.5em));
  --tsubu-seg-romaji: calc(1.5 * var(--tsubu-romaji-size, var(--tsubu-ruby-size, 0.5em)));
  --tsubu-seg-gap: var(--tsubu-reading-gap, 0.2em);
  --tsubu-seg-half: calc(0.5em + var(--tsubu-seg-ruby) + var(--tsubu-seg-romaji) + 2 * var(--tsubu-seg-gap));
  --tsubu-seg-over: var(--tsubu-seg-ruby);
  --tsubu-seg-under: calc(var(--tsubu-seg-ruby) + 2 * var(--tsubu-seg-gap));
}
.tsubu[data-under-layer][data-over-layer="romaji"]:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg { --tsubu-seg-over: calc(var(--tsubu-seg-romaji) + var(--tsubu-seg-gap)); }
.tsubu[data-under-layer][data-over-layer="none"]:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg { --tsubu-seg-over: 0px; }
.tsubu[data-under-layer="romaji"]:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg { --tsubu-seg-under: calc(var(--tsubu-seg-romaji) + 2 * var(--tsubu-seg-gap)); }
/* Origins are over the text, in the furigana's row: that side's room is the larger of the two. */
.tsubu[data-under-layer][data-origin]:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg {
  --tsubu-seg-origin: calc(1.5 * var(--tsubu-origin-size, var(--tsubu-ruby-size, 0.5em)) + var(--tsubu-seg-gap));
  --tsubu-seg-half: calc(0.5em + max(var(--tsubu-seg-ruby), var(--tsubu-seg-origin)) + var(--tsubu-seg-romaji) + 2 * var(--tsubu-seg-gap));
  --tsubu-seg-over: max(var(--tsubu-seg-ruby), var(--tsubu-seg-origin));
}
.tsubu[data-under-layer][data-over-layer="none"][data-origin]:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg { --tsubu-seg-over: var(--tsubu-seg-origin); }
.tsubu[data-under-layer]:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-romaji ruby {
  line-height: 1;
}
.tsubu[data-under-layer]:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg:not([data-off-page]) {
  line-height: calc(1em + 2 * var(--tsubu-seg-half));
  margin-block: calc(var(--tsubu-seg-over) - var(--tsubu-seg-half)) calc(var(--tsubu-seg-under) - var(--tsubu-seg-half));
}
.tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg::before,
.tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg > .tsubu-band-probe {
  inset-block-start: auto;
  inset-block-end: calc(var(--tsubu-seg-half, 0.5em) - var(--tsubu-current-spread, 0.06em));
}
.tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg:not([data-kind="punct"])::after,
.tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg:not([data-kind="punct"]) > .tsubu-line-probe {
  inset-block-end: calc(var(--tsubu-seg-half, 0.5em) - var(--tsubu-underline-offset, 0.02em));
}

/* No overhang: WebKit lets a wide reading overhang the kana next to it, but leaves the segment's
 * box as long as before, so it reaches into the next segment and the two underlines meet
 * (measured). The other engines do not read the property. */
.tsubu ruby {
  ruby-position: over;
  ruby-align: center;
  ruby-overhang: none;
}

.tsubu rt {
  font-size: var(--tsubu-ruby-size, 0.5em);
  line-height: 1.2;
  color: var(--tsubu-ruby-color, #6b7280);
  font-weight: var(--tsubu-ruby-weight, 400);
}

/* The parentheses around each reading are for what cannot draw ruby; browsers hide them on their
 * own, and this keeps them hidden where a page's styles would show them. */
.tsubu rp {
  display: none;
}

/*
 * Two sides. Each part's furigana and each segment's romaji are both ruby: a segment with romaji
 * is a ruby whose base is the segment's parts, their own ruby included, and whose reading is the
 * romaji (.tsubu-romaji). options.over and options.under choose which layer goes on which side,
 * and the root says so where it differs from furigana over and nothing under: data-over-layer
 * ("romaji" or "none") and data-under-layer ("ruby" or "romaji"). Over is above the text in
 * horizontal text and at its right in vertical text; under is below it, at its left in vertical
 * text, the side of the segment's underline (ruby-position turns with the writing mode).
 *
 * Across a line, from over to under: the over readings, the text with its highlight, the
 * underline, then the under readings, --tsubu-reading-gap away from the text so that the underline
 * runs between the two, and twice as far from the next line. Romaji over the text are as far from
 * it, so that their descenders (g, j, p, y) stay clear of the highlight; furigana over the text
 * sit as they always have. The lines make room for the readings on both sides, as they do for
 * furigana, so a line's under readings never meet the next line's over readings.
 */
.tsubu .tsubu-romaji > rt {
  font-size: var(--tsubu-romaji-size, var(--tsubu-ruby-size, 0.5em));
}
/* The romaji's ruby in a box of its own. WebKit lays out a line of nested ruby with readings on
 * both sides again, with the segments wider than they are and so on more lines, whenever a reading
 * gets a filter, an opacity or a text shadow (the blur preset) or a positioned box is added to the
 * segment; the first layout is right (all measured). In an inline-block its layout stays the
 * same, in every engine. The box's baseline is the text's, and the segment itself stays an inline
 * whose box is the text's, so its highlight and underline are drawn as for any other segment. */
.tsubu-stack {
  display: inline-block;
}
.tsubu[data-under-layer="ruby"] ruby:not(.tsubu-romaji, .tsubu-origin),
.tsubu[data-under-layer="romaji"] .tsubu-romaji {
  ruby-position: under;
}
/* Padding, not a margin: a margin moves the box of the reading in Firefox's vertical text but not
 * its glyphs; padding moves both, in every engine (measured), and the line makes room for it.
 * Twice as much past the readings under the text keeps them apart from the next line's readings,
 * which the lines otherwise only just make room for, so that it is clear which line each belongs
 * to. */
.tsubu[data-under-layer="ruby"] ruby:not(.tsubu-romaji, .tsubu-origin) > rt,
.tsubu[data-under-layer="romaji"] .tsubu-romaji > rt {
  padding-block: var(--tsubu-reading-gap, 0.2em) calc(2 * var(--tsubu-reading-gap, 0.2em));
}
.tsubu[data-over-layer="romaji"] .tsubu-romaji > rt {
  padding-block-end: var(--tsubu-reading-gap, 0.2em);
}
/* Browsers differ in whether the last line box grows to hold the readings under it, as they do for
 * the first line and the readings over it: WebKit's does not, and furigana under the last line
 * reached 0.35em past the sentence's box without this room (measured). Room below the last line
 * (at the left of the last column in vertical text) keeps them inside it. */
.tsubu[data-under-layer] {
  padding-block-end: var(--tsubu-under-reserve, 0.5em);
}

/*
 * Loanwords' origins (options.origin "over"): a part with an origin and no furigana is a ruby of its
 * own, .tsubu-origin, whose reading is the source word, its language code and, for a pseudo-loanword
 * coined in Japanese, 和製 before them. It is drawn over the text, in the furigana's row, which a
 * loanword in katakana leaves free, and the lines make room for it as they do for furigana. Origins
 * take the furigana's colours in every group, so they meet the same threshold; the code and the
 * mark are set apart by size alone, since a lighter colour would fall below it. The comma between
 * word and code is for what cannot draw ruby. Romaji over the text are refused with origins, so
 * the origins never share their row with romaji; with readings under the text, the romaji's ruby
 * holds the origin's as it holds the furigana's.
 */
.tsubu .tsubu-origin > rt {
  font-size: var(--tsubu-origin-size, var(--tsubu-ruby-size, 0.5em));
}
/* As far from the text as romaji over it are, and for the same reason: the descenders of the
 * source word (the p of pão, the g of glas) stay clear of the highlight, into which they reached.
 * Moved by relative positioning of the reading's body, which moves it the same in every engine,
 * horizontally and vertically, where padding on the <rt> (as for romaji) does not in Chromium,
 * vertical-align does not in Chromium's vertical text, and relative positioning of the <rt> itself
 * does not in WebKit (all measured). It takes no room, so the room is given where the origins could
 * meet something: above the first line, and between lines with readings under the text (both below). */
.tsubu-origin-body {
  position: relative;
  inset-block-start: calc(-1 * var(--tsubu-reading-gap, 0.2em));
}
.tsubu[data-origin] {
  padding-block-start: calc(var(--tsubu-ruby-reserve, 0.3em) + var(--tsubu-reading-gap, 0.2em));
}
.tsubu-origin-lang,
.tsubu-origin-wasei {
  font-size: var(--tsubu-origin-label-size, 0.75em);
}
.tsubu-origin-lang {
  margin-inline-start: 0.3em;
}
.tsubu-origin-sep {
  display: none;
}
/* With readings under the text, a line's readings under it and the next line's origins over it
 * can meet: with the furigana under the text they are on different parts, and Firefox and WebKit
 * do not move the lines apart for them; with the romaji, whose ruby holds the origins', the lines
 * make room for both, but not for the origins' move off the text, by which they reached into the
 * room past the romaji in Chromium and Firefox (all measured). So the lines are given the room
 * themselves: each holds its text, the readings over it, its origins moved off it, and the readings
 * under it, with the sizes counted half again for fonts whose content area is taller than their em
 * box and the gaps in em of the text, as columns are in vertical text, and never less than
 * --tsubu-line-height. WebKit, which gives the side of a nested reading more room of its own, sets
 * lines with romaji under the text further apart than that. Vertical text gives each column the
 * room already (see the vertical rules above). */
.tsubu[data-origin][data-under-layer] {
  --tsubu-room-over: max(var(--tsubu-ruby-size, 0.5em), var(--tsubu-origin-size, var(--tsubu-ruby-size, 0.5em)));
  --tsubu-room-under: var(--tsubu-ruby-size, 0.5em);
  line-height: max(calc(var(--tsubu-line-height, 1.75) * 1em),
    calc(1em + 1.5 * var(--tsubu-room-over) + 1.5 * var(--tsubu-room-under) + 3 * var(--tsubu-reading-gap, 0.2em)));
}
.tsubu[data-origin][data-under-layer="romaji"] {
  --tsubu-room-under: var(--tsubu-romaji-size, var(--tsubu-ruby-size, 0.5em));
}
.tsubu[data-origin][data-under-layer]:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) {
  line-height: var(--tsubu-line-height, 1.75);
}

/* Hidden, not removed: display: none would let a word whose reading is wider than its base
 * narrow, and every character after it would move. data-ruby shows or hides the over side,
 * data-under the under side, and data-origin the origins, which are over the text but are not
 * switched with it. */
.tsubu[data-ruby="off"]:not([data-over-layer]) ruby:not(.tsubu-romaji, .tsubu-origin) > rt,
.tsubu[data-ruby="off"][data-over-layer="romaji"] .tsubu-romaji > rt,
.tsubu[data-under="off"][data-under-layer="ruby"] ruby:not(.tsubu-romaji, .tsubu-origin) > rt,
.tsubu[data-under="off"][data-under-layer="romaji"] .tsubu-romaji > rt,
.tsubu[data-origin="off"] .tsubu-origin > rt {
  visibility: hidden;
}

.tsubu-seg[data-state="future"] { color: var(--tsubu-future-color, var(--tsubu-text-color)); }
.tsubu-seg[data-state="future"] rt { color: var(--tsubu-future-ruby-color, var(--tsubu-ruby-color, #6b7280)); }
.tsubu-seg[data-state="future"]::after {
  border-block-end-color: var(--tsubu-future-underline-color, var(--tsubu-underline-color, #9ca3af));
}

.tsubu-seg[data-state="past"] { color: var(--tsubu-past-color, var(--tsubu-text-color)); }
.tsubu-seg[data-state="past"] rt { color: var(--tsubu-past-ruby-color, var(--tsubu-ruby-color, #6b7280)); }
.tsubu-seg[data-state="past"]::after {
  border-block-end-color: var(--tsubu-past-underline-color, var(--tsubu-underline-color, #9ca3af));
}

.tsubu-seg[data-state="current"] {
  color: var(--tsubu-speaker-color, var(--tsubu-current-color, var(--tsubu-text-color)));
}
.tsubu-seg[data-state="current"]::before {
  background-image: linear-gradient(var(--tsubu-speaker-bg, var(--tsubu-current-bg, #e5e7eb)), var(--tsubu-speaker-bg, var(--tsubu-current-bg, #e5e7eb)));
}
.tsubu-seg[data-state="current"] rt {
  color: var(--tsubu-speaker-ruby-color, var(--tsubu-current-ruby-color, var(--tsubu-ruby-color, #6b7280)));
}
.tsubu-seg[data-state="current"]::after {
  border-block-end-color: var(--tsubu-speaker-underline-color, var(--tsubu-current-underline-color, var(--tsubu-underline-color, #9ca3af)));
}
/* With only the sentence's time the whole sentence is lit: one band per line, no seams. */
.tsubu[data-timing="sentence"] .tsubu-seg::before { border-radius: 0; }

/* Forced colours (Windows contrast themes and the like) replace backgrounds, which would leave
 * the segment being spoken looking like every other. That segment alone keeps a highlight, drawn
 * in the user's own colours for selected text; its furigana sit on the page, so they take the
 * page's text colour. */
@media (forced-colors: active) {
  .tsubu-seg[data-state="current"] {
    forced-color-adjust: none;
    color: HighlightText;
  }
  /* A background colour here: Chromium resolves a system colour inside a gradient from the
   * page's own scheme, not from the forced palette. */
  .tsubu-seg[data-state="current"]::before {
    forced-color-adjust: none;
    background: Highlight;
  }
  .tsubu-seg[data-state="current"] rt { color: CanvasText; }
  .tsubu-seg[data-state="current"]::after { border-block-end-color: HighlightText; }
}

/*
 * Motion presets (Tsubu.MOTIONS), selected with data-tsubu-motion on the root (options.motion) or
 * any ancestor; several can be combined. Each is drawn from the state apply() writes, so every
 * frame is a function of t, and from data-revealed, which setRevealed() writes, so a caller can
 * reveal a segment on any event. "Unheard" is a segment that is neither current nor past and has
 * not been revealed. They change visibility, colour, a filter and a transform only: nothing moves.
 */
[data-tsubu-motion~="appear"] .tsubu-seg:not([data-state="current"], [data-state="past"], [data-revealed]) {
  visibility: hidden;
}

[data-tsubu-motion~="mask"] .tsubu-seg:not([data-state="current"], [data-state="past"], [data-revealed]),
[data-tsubu-motion~="mask"] .tsubu-seg:not([data-state="current"], [data-state="past"], [data-revealed]) rt {
  color: transparent;
}

/* The furigana are blurred on their own: Chromium's filter on the inline segment does not reach a
 * reading drawn above the line. In em, the blur scales with the reading's own size. */
[data-tsubu-motion~="blur"] .tsubu-seg:not([data-state="current"], [data-state="past"], [data-revealed]) {
  filter: blur(var(--tsubu-blur, 0.15em));
}
[data-tsubu-motion~="blur"] .tsubu-seg:not([data-state="current"], [data-state="past"], [data-revealed]) rt {
  filter: blur(var(--tsubu-blur, 0.15em));
}
/* A vertical segment is an inline-block, whose filter does reach its furigana: they are not
 * blurred twice. */
.tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg rt {
  filter: none;
}

[data-tsubu-motion~="underline-draw"] .tsubu-seg:not([data-state="current"], [data-state="past"], [data-revealed])::after {
  visibility: hidden;
}
[data-tsubu-motion~="underline-draw"] .tsubu-seg[data-state="current"]:not([data-revealed])::after {
  transform: scale(var(--tsubu-draw-x, var(--tsubu-progress, 1)), var(--tsubu-draw-y, 1));
  transform-origin: var(--tsubu-draw-origin, left center);
}
/* In vertical text the line is a side line, drawn in from the top. Declared on the ::after itself,
 * so that --tsubu-progress is the segment's own. */
.tsubu:is([data-tsubu-writing="vertical"], [data-tsubu-writing="vertical"] *):not([data-tsubu-writing="horizontal"]) .tsubu-seg::after {
  --tsubu-draw-x: 1;
  --tsubu-draw-y: var(--tsubu-progress, 1);
  --tsubu-draw-origin: center top;
}
@media (prefers-reduced-motion: reduce) {
  [data-tsubu-motion~="underline-draw"] .tsubu-seg[data-state="current"]:not([data-revealed])::after { transform: none; }
}

/*
 * Theme presets. Set data-tsubu-theme on the sentence's root (options.theme) or on any ancestor.
 * Each one is checked in the tests with Tsubu.checkContrast() against its own --tsubu-backdrop,
 * in every state and for each speaker, at the thresholds in Tsubu.CONTRAST.
 *
 * The highlight covers the em box of the text, not the furigana above it, whatever the font's
 * content area (see "The em box" at the top): the current segment's furigana sit on the page (or
 * the plate), so they take the highlight's hue in a shade that reads there, not the colour of the
 * text inside the highlight.
 */

/* Dark text on a white or near-white page. */
[data-tsubu-theme="light"] {
  --tsubu-backdrop: #ffffff, #f1f5f9;
  --tsubu-text-color: #111827;
  --tsubu-ruby-color: #4b5563;
  --tsubu-underline-color: #6b7280;
  --tsubu-future-color: #111827;
  --tsubu-future-ruby-color: #4b5563;
  --tsubu-future-underline-color: #6b7280;
  --tsubu-current-color: #ffffff;
  --tsubu-current-ruby-color: #1d4ed8;
  --tsubu-current-underline-color: #93c5fd;
  --tsubu-current-bg: #1d4ed8;
  --tsubu-past-color: #4b5563;
  --tsubu-past-ruby-color: #4b5563;
  --tsubu-past-underline-color: #6b7280;
  --tsubu-plate-bg: transparent;
  --tsubu-speaker-a-bg: #1d4ed8;
  --tsubu-speaker-a-color: #ffffff;
  --tsubu-speaker-a-ruby-color: #1d4ed8;
  --tsubu-speaker-a-underline-color: #93c5fd;
  --tsubu-speaker-b-bg: #b45309;
  --tsubu-speaker-b-color: #ffffff;
  --tsubu-speaker-b-ruby-color: #92400e;
  --tsubu-speaker-b-underline-color: #fcd34d;
}

/* Light text on a dark page. */
[data-tsubu-theme="dark"] {
  --tsubu-backdrop: #0f172a, #1e293b;
  --tsubu-text-color: #f8fafc;
  --tsubu-ruby-color: #cbd5e1;
  --tsubu-underline-color: #7c8aa0;
  --tsubu-future-color: #f8fafc;
  --tsubu-future-ruby-color: #cbd5e1;
  --tsubu-future-underline-color: #7c8aa0;
  --tsubu-current-color: #0f172a;
  --tsubu-current-ruby-color: #93c5fd;
  --tsubu-current-underline-color: #1d4ed8;
  --tsubu-current-bg: #93c5fd;
  --tsubu-past-color: #94a3b8;
  --tsubu-past-ruby-color: #94a3b8;
  --tsubu-past-underline-color: #7c8aa0;
  --tsubu-plate-bg: transparent;
  --tsubu-speaker-a-bg: #93c5fd;
  --tsubu-speaker-a-color: #0f172a;
  --tsubu-speaker-a-ruby-color: #93c5fd;
  --tsubu-speaker-a-underline-color: #1d4ed8;
  --tsubu-speaker-b-bg: #fcd34d;
  --tsubu-speaker-b-color: #0f172a;
  --tsubu-speaker-b-ruby-color: #fcd34d;
  --tsubu-speaker-b-underline-color: #b45309;
}

/* Over an image or a video: a translucent plate behind the sentence keeps every colour
 * readable whatever is underneath, from pure black to pure white. */
[data-tsubu-theme="media"] {
  --tsubu-backdrop: any;
  --tsubu-plate-bg: rgba(0, 0, 0, 0.75);
  --tsubu-plate-spread: 0.25em;
  --tsubu-plate-radius: 0.3em;
  --tsubu-text-color: #ffffff;
  --tsubu-ruby-color: #e5e7eb;
  --tsubu-underline-color: #9ca3af;
  --tsubu-future-color: #ffffff;
  --tsubu-future-ruby-color: #e5e7eb;
  --tsubu-future-underline-color: #9ca3af;
  --tsubu-current-color: #111827;
  --tsubu-current-ruby-color: #fde047;
  --tsubu-current-underline-color: #854d0e;
  --tsubu-current-bg: #fde047;
  --tsubu-past-color: #d1d5db;
  --tsubu-past-ruby-color: #d1d5db;
  --tsubu-past-underline-color: #9ca3af;
  --tsubu-speaker-a-bg: #fde047;
  --tsubu-speaker-a-color: #111827;
  --tsubu-speaker-a-ruby-color: #fde047;
  --tsubu-speaker-a-underline-color: #854d0e;
  --tsubu-speaker-b-bg: #67e8f9;
  --tsubu-speaker-b-color: #111827;
  --tsubu-speaker-b-ruby-color: #67e8f9;
  --tsubu-speaker-b-underline-color: #0e7490;
}
