:root {
  /* The page (CR-63 round 3): a light blue-grey of the accent's hue family, OKLCH L 0.75,
     C 0.023, H 266. A new light page stays in the family: hue 255 to 270, chroma 0.02 to 0.03,
     lightness 0.72 to 0.78. */
  --bg: #a8afbe;
  /* Text and icons that lie on the page itself, not on a card: --page-text for what must be read,
     --page-muted for the secondary. --text, --muted and --accent are for cards (the surfaces).
     --page-muted is darker than the cards' --muted, which gives only 2.2:1 on this page. */
  --page-text: #1c1f24;
  --page-muted: #3c4049;
  /* Verdict colours for text on the page (Type's finished and revealed letters, CR-63 round 4):
     the cards' green and red darkened to read on it. On cards --ok and --bad stay. */
  --page-ok: color-mix(in oklch, var(--ok), black 35%);
  --page-bad: color-mix(in oklch, var(--bad), black 35%);
  /* The sheet (CR-64): the words screen lies on a dark base of the page's family (OKLCH L 0.41,
     C 0.05, H 266, CR-64 round 1), over a glass that blurs the page (CR-66 round 13), the one sharp
     thing on the screen. Everything on it derives from --sheet-bg; the page's and the cards'
     tokens never appear on it. */
  --sheet-bg: #3e4c70;
  /* The sheet's 1 px light edge, with its shadow. */
  --sheet-edge: rgba(255, 255, 255, 0.12);
  /* The separators between the rows of the list's group. */
  --sheet-line: rgba(255, 255, 255, 0.08);
  /* The base of the rows, the search field and the tabs' track. */
  --sheet-surface: color-mix(in srgb, white 14%, var(--sheet-bg));
  --sheet-text: rgba(255, 255, 255, 0.92);
  --sheet-muted: rgba(255, 255, 255, 0.62);
  /* Far fainter than muted, which lights the passed flowers: the unlit flowers of a learned row's
     ladder (CR-89 round 1). */
  --sheet-dim: rgba(255, 255, 255, 0.12);
  /* GLASS_BLUR (CR-66 round 13): the radius of the blur under an open sheet, the knob if it feels
     too strong or too weak. */
  --glass: 14px;
  /* The hint sticker's glass (CR-109, SPEC 5.34): lighter than the sheets', so the screen the
     sticker explains stays readable under it. */
  --glass-hint: 3px;
  /* The hint sticker's paper (CR-109): a post-it yellow, the one colour outside the palette, on
     the stickers and nothing else (DESIGN.md "Colour"). Round 5: the note's yellow with the cards'
     light, from the top left as --surface-plain; the button stays flat on --hint-button. Round 6:
     the light's span halved, so the button stays darker than the paper on a tall note. */
  --hint-bg-from: #fffad0;
  --hint-bg-to: #fdefa6;
  --hint-bg: linear-gradient(135deg, var(--hint-bg-from), var(--hint-bg-to));
  /* The note's yellow deepened (CR-109 round 1): the stickers' button alone, in the note's own
     palette, since the blue pill fought the yellow. */
  --hint-button: #f1cf5c;
  /* A state's tint over the sheet surface, the chosen tab's alone (CR-64 round 2): 22%, because 10%
     vanishes on a dark base (the knob). */
  --sheet-tint: 22%;
  /* The state colours lightened for text and borders on the dark base. */
  --sheet-learned: color-mix(in oklch, var(--flower-10), white 25%);
  --sheet-known: color-mix(in oklch, var(--known), white 30%);
  --sheet-learning: color-mix(in oklch, var(--flower-2), white 20%);
  /* The sheet as a window (CR-66 round 1): the title bar a shade darker than the sheet with a
     darker line under it; a section's head plate, lighter than the sheet, darker than the surface;
     a text field sunk into the sheet with a light border all round. Since CR-66 round 3 the bar is
     the sheet's own colour with a light hairline, as platform sheets have it: a darker fill with a
     dark line read as a bevel. */
  --sheet-bar: var(--sheet-bg);
  --sheet-bar-line: var(--sheet-line);
  --sheet-plate: color-mix(in srgb, white 7%, var(--sheet-bg));
  --sheet-field: color-mix(in srgb, black 18%, var(--sheet-bg));
  --sheet-field-edge: rgba(255, 255, 255, 0.22);
  --sheet-field-focus: rgba(255, 255, 255, 0.45);
  /* The light sheets' body (the acknowledgment, the install sheet; CR-100 round 2): white with the
     page colour at 20%, a card's lower-corner tone, since plain white was harsh. The knob. */
  --sheet-light-bg: color-mix(in oklch, var(--surface), var(--bg) 20%);
  /* The accent lightened for the dark base, as --sheet-known is: on a sheet it means a link,
     tappable; and a link row's fill. The only way the accent reaches a sheet. */
  --sheet-accent: color-mix(in oklch, var(--accent), white 35%);
  --sheet-link: color-mix(in srgb, var(--accent) 14%, var(--sheet-surface));
  /* The list tier (CR-66 round 2): the lists are quieter than the buttons. Buttons and cards on
     --sheet-surface (white 14%), lists on --sheet-surface-low (10%), plates on --sheet-plate (7%);
     the rows' primary text and kind icons at white 80%. */
  --sheet-surface-low: color-mix(in srgb, white 10%, var(--sheet-bg));
  --sheet-text-low: rgba(255, 255, 255, 0.8);
  /* The statuses, by ticket systems and messengers: Sent grey (quieter than the excerpt), Viewed
     the accent's blue (the two ticks), Replied lilac (answered, mentioned), In progress the
     learning orange, Done the learned green. The Delete strip in the cards' red, as the words
     screen's strips keep the cards' colours. */
  --sheet-faint: rgba(255, 255, 255, 0.5);
  --sheet-replied: color-mix(in oklch, color-mix(in oklch, var(--accent), var(--flower-1) 35%), white 30%);
  --sheet-delete: var(--bad);
  /* Red text and borders on the dark base: destructive actions (the report view's Delete). */
  --sheet-bad: color-mix(in oklch, var(--bad), white 25%);
  /* White at 90% over the page, opaque; the level tints mix over it. */
  --surface: #f6f7f9;
  /* The light on every card (CR-84, CR-85; DESIGN.md): from the top left, as the shadow falls
     down. A card that carries no state is on --surface-plain itself; a card that shows a state
     has its level tint laid over it. Buttons stay on the flat --surface. */
  --surface-plain-from: #f5f6f8;
  --surface-plain-to: #e2ebff;
  --surface-plain: linear-gradient(135deg, var(--surface-plain-from), var(--surface-plain-to));
  --text: #1c1f24;
  --muted: #6b7280;
  --line: rgba(20, 30, 50, 0.12);
  --accent: #2f6fde;
  --accent-text: #ffffff;
  --shadow: 0 8px 24px rgba(20, 30, 50, 0.12), 0 2px 6px rgba(20, 30, 50, 0.08);
  --shadow-soft: 0 4px 12px rgba(20, 30, 50, 0.06), 0 1px 3px rgba(20, 30, 50, 0.04);
  /* Half the soft shadow's spread and blur: Type's hint button, present but not calling. */
  --shadow-hint: 0 2px 6px rgba(20, 30, 50, 0.04), 0 1px 2px rgba(20, 30, 50, 0.03);
  --ok: #1f9d55;
  --bad: #d7263d;
  --arrow: 48px;
  /* The widest deck, in the modes that take the full width; the bottom row matches it. */
  --deck-wide: 520px;
  /* The board's widest (CR-112): the home screen's column (SPEC 5.1), so a tablet shows the
     phone's layout centred; on a phone the stage is narrower and the board takes all of it. */
  --board-max: 520px;
  --bottom: 56px;
  /* The learned pocket's size while a card flies into it (SPEC 5.22); "/ <count>" steps aside
     by half the extra width it gives. */
  --pocket-grow: 1.5;
  /* The pocket's height at rest (64 shows the stitches and the lens best; 56 was the first size),
     its width from the drawing's 480 x 494. Its number, "/ <count>" and the Undo chip derive
     from the height. */
  --pocket-height: 64px;
  --pocket-width: calc(var(--pocket-height) * 480 / 494);
  /* The drawing's greys overridden by id (CR-63 round 4): a denim of the page's hue, darker than
     the page, a touch bluer; the number on the lid white; the creases' stop colour darker than the
     lid (their opacities stay as drawn). Option B of the sheet; the other letters as knobs:
     A #9aa5bc / #8c98b1 / #4a5670 (the number dark), C #6a7a9a / #5a6b8e / #2e3a52,
     D #7384a6 / #5f739b / #33425f (back / lid / line). */
  --pocket-back: #7d8ba8;
  --pocket-lid: #6e7e9d;
  --pocket-line: #3a4660;
  --pocket-count: #ffffff;
  --pocket-crease: #1f2838;
  --gap: 6px;
  --pad: 12px;
  /* The size of a card's word; Type's translation and Pick's captions derive from it. */
  --word-size: clamp(1.6rem, 7vw, 2.4rem);
  /* The stored side of a word's image; mirrors IMAGE_SIZE in config.php. */
  --image-side: 512px;
  /* The health flower (SPEC 5.21): one base colour per level, red through yellow to green;
     every other colour of the flower and the card's tint is mixed from these. Ten rows, as
     HEALTH_MAX in config-gameplay.php. */
  --flower-1: #F05617;
  --flower-2: #F3831F;
  --flower-3: #F5A325;
  --flower-4: #F7BF2A;
  --flower-5: #F8D72F;
  --flower-6: #F7E130;
  --flower-7: #E4DD2C;
  --flower-8: #C0D524;
  --flower-9: #8CCA19;
  --flower-10: #47BA09;
  --flower-tint: 10%;
  /* Known words: the learned green shifted toward the accent's blue; the words screen and later
     the sets screen. Not the accent, which means tappable or selected (DESIGN.md, Colour). */
  --known: color-mix(in oklch, var(--flower-10) 45%, var(--accent));
  /* The flower at rest is mixed this much of itself with the surface; its overlay is full. */
  --flower-dim: 60%;
  --flower: 44px;
  --flower-grow: 2;
  color-scheme: light;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

/* The body is 100% of html, the initial containing block: the whole screen. The insets
   are the body's padding, counted once; nothing below the body takes a viewport-based
   height. In the app installed on iOS the height is the window's, measured by app.js into
   --app-height (CR-66 round 5): WebKit's viewport units have been wrong there in two opposite
   ways, one status bar short with the translucent bar (301108, which the large viewport
   unit once worked around) and one status bar too tall without it (316008, the footer below the edge). app.js
   sets the standalone class only when navigator.standalone is true, so a browser tab, Android
   and desktop keep 100%. */
html,
body {
  height: 100%;
  margin: 0;
  overflow: hidden;
  overscroll-behavior: none;
}

html.standalone,
html.standalone body {
  height: var(--app-height, 100%);
}

/* No scrollbar anywhere (SPEC 5.5, CR-102): every scrolling area is touch-scrolled and scrolls
   silently; iOS draws no bar at rest, Android Chrome draws an overlay one that sits inside a
   card like a crack. Chrome honours scrollbar-width before the WebKit pseudo-element, so the
   debug panel (5.17), which keeps its bars for a desktop, is left out of both rules. */
* {
  scrollbar-width: none;
}

:not(.debug, .debug *)::-webkit-scrollbar {
  display: none;
}

.debug,
.debug * {
  scrollbar-width: auto;
}

/* The status-bar plate (CR-66 round 14, as round 3 made it): on the installed app iOS blurs the top
   of the page (the icons' top halves) unless an opaque fixed element lies in the page's top pixels
   (at least 6 px tall, most of the width, at the very edge); the plate is that element. It has
   nothing to do with the status bar's colour, which is the page's anyway. Always shown, no dimmed
   state; under the sheets (9, later in the document), the hint stickers (10) and the modals (11). */
.statusbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: calc(env(safe-area-inset-top) + 6px);
  z-index: 9;
  background: var(--bg);
  pointer-events: none;
}

body {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

button {
  font: inherit;
  color: inherit;
  cursor: pointer;
}

button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Screens: each fills the viewport as a column so only inner parts can scroll. */

.cards,
.sets {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* The containing block of the undo chip, which stands over the footer. */
.cards {
  position: relative;
}

/* Sign-in screen (SPEC 12.4): one centred card in the Cards look. The landing has more text
   than a phone shows at once, so the column scrolls (the page still does not, 5.5). A card
   that fits stays centred; a taller one starts at the top: safe centring where supported,
   the card's auto margins elsewhere. */

.signin {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: safe center;
  padding: 16px;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

.signin > .signin-card {
  flex: none;
  margin: auto 0;
}

.signin-card {
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 24px 20px;
  border-radius: 20px;
  background: var(--surface);
  box-shadow: var(--shadow);
  text-align: center;
}

.signin-title {
  margin: 0;
  font-size: 1.4rem;
  font-weight: 700;
}

.signin-line {
  margin: 0 0 8px;
  color: var(--muted);
}

/* A link drawn as the primary button of the modals. */
.signin-button {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
  text-decoration: none;
}

/* The removed "Already invited?" heading's 8 px stays above the Google button (12.4); the
   maintenance card's retry button follows a .signin-line and keeps its place. */
.signin-text + .signin-button {
  margin-top: 8px;
}

.signin-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.signin-error {
  margin: 0;
  font-size: 14px;
  color: var(--bad);
}

/* The landing's text sits left inside the centred card; the title and the button stay centred. */
.signin-list {
  margin: 0;
  padding: 0 0 0 20px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.45;
  text-align: left;
}

/* The short dash marker of the landing's lists, shared with the acknowledgment's (5.20). */
.signin-dashes,
.ack-list {
  list-style: none;
}

.signin-dashes > li::before,
.ack-list > li::before {
  content: "-";
  display: inline-block;
  width: 20px;
  margin-left: -20px;
}

/* Dark and bold since CR-101: the muted grey read as whispers next to the text. */
.signin-heading {
  margin: 8px 0 0;
  color: var(--text);
  font-size: 15px;
  font-weight: 700;
  text-align: left;
}

/* A platform's steps under their own heading, when the request's platform is unknown (12.4). */
.signin-subheading {
  margin-top: 8px;
}

.signin-text {
  margin: 0;
  font-size: 15px;
  line-height: 1.45;
  text-align: left;
}

.signin-muted {
  color: var(--muted);
}

.signin-link {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  overflow-wrap: anywhere;
}

.signin-install {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Already installed: the steps have nothing to say. The class only matters after a sign-out
   inside the installed app, where the media query applies as well. */
@media (display-mode: standalone) {
  .signin-install {
    display: none;
  }
}

html.standalone .signin-install {
  display: none;
}

/* The home screen (SPEC 5.1, 5.5, DESIGN.md "The home screen"): the sheets' look edge to edge,
   no panel and no bar (CR-97 round 1: a home is not a window). The dark base is the body's own
   colour while it is shown, under the safe areas too, and the status-bar plate's, so the installed
   app's bar is dark with it (app.js puts a fresh plate in at every switch, the layer iOS
   re-samples on, CR-97 round 3); the switch is a jump between screens, never an animation. */
html[data-screen="home"] body,
html[data-screen="home"] .statusbar {
  background: var(--sheet-bg);
}

.home {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* The circled "?" (CR-109, SPEC 5.1): at the screen's top right corner, 10 px in from the top and
   the right inside the body's padding, over the scrolling body; 28 px in a 44 px hit area (round 1:
   the circled glyph fills its circle, so it is drawn larger than the bar's open ones to read the
   same size), in the link card's colour (tappable on the dark base). It opens the home sticker. */
.home-help {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--sheet-accent);
}

.home-help svg {
  display: block;
  width: 28px;
  height: 28px;
}

.home-help:focus-visible {
  outline: 2px solid var(--sheet-accent);
  outline-offset: -2px;
}

/* The one scrollable area, the logo scrolling with the lists; the footer stays at the bottom. */
.home-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 24px 16px 16px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  touch-action: pan-y;
}

/* At most 520 px of content, centred on a wider page; the footer's box counts its padding. */
.home-content {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 520px;
  margin: 0 auto;
}

.home-footer {
  width: 100%;
  max-width: calc(520px + 32px);
  margin: 0 auto;
}

/* The logo was drawn for the light page; it keeps its colours here. */
.home-logo {
  display: block;
  height: 96px;
  width: auto;
  margin: 0 auto;
}

/* The app's state said once, close under the logo, not baked into the drawing; the second line
   in the fainter tone of every explanation on the screen (round 4). */
.home-preview {
  margin: 4px 0 0;
  color: var(--sheet-text-low);
  font-size: 14px;
  text-align: center;
}

.home-preview-line {
  margin: 2px 0 20px;
  color: var(--sheet-faint);
  font-size: 14px;
  text-align: center;
}

.home-status {
  flex: 1;
  justify-content: center;
  color: var(--sheet-muted);
}

.home-status .status-text {
  font-size: 15px;
}

.home-retry {
  border-color: var(--sheet-accent);
  color: var(--sheet-accent);
}

.home-empty {
  margin: 0;
  padding: 24px;
  color: var(--sheet-muted);
  font-size: 15px;
  text-align: center;
}

/* The languages group: the settings sheet's rows; the flag where the timer rows have their icon.
   A flag keeps its own colours (DESIGN.md). */
.home-flag {
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 4px;
  object-fit: cover;
}

/* One head row in two halves, "I learn" and "I speak", a line between them; the caption small
   above the chosen language. */
.home-lang-heads {
  display: flex;
}

.home-lang {
  flex: 1 1 0;
  min-width: 0;
  min-height: 56px;
}

.home-lang + .home-lang {
  border-left: 1px solid var(--sheet-line);
}

.home-lang-caption {
  color: var(--sheet-muted);
  font-size: 13px;
}

.home-lang-name {
  color: var(--sheet-text-low);
  font-size: 16px;
}

/* The unfolded languages under the whole row, full width, read as its children: indented 14 px
   more than the halves, the group's separators. */
.home-lang-list {
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--sheet-line);
  list-style: none;
}

.home-lang-list .settings-row {
  padding-left: 28px;
}

.home-lang-list li + li .settings-row {
  border-top: 1px solid var(--sheet-line);
}

/* The sets not yet added: the feedback sheet's choice cards. */
.home-sets {
  margin: 0;
  padding: 0;
  list-style: none;
}

.home-set {
  margin-bottom: 2px;
  border-radius: 14px;
  background: var(--sheet-surface);
  overflow: hidden;
}

.home-set:last-child {
  margin-bottom: 0;
}

.home-set > a {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-height: 56px;
  padding: 10px 14px;
  line-height: 1.2;
  text-decoration: none;
}

.home-set > a:active {
  background: color-mix(in srgb, black 12%, var(--sheet-surface));
}

.home-set > a:focus-visible,
.home-mine-row > a:focus-visible {
  outline: 2px solid var(--sheet-accent);
  outline-offset: -2px;
}

/* While the list is being added the card keeps its look but takes no taps. */
.home-set.is-busy > a {
  pointer-events: none;
}

.home-set.is-busy > a:active {
  background: transparent;
}

.home-set-title {
  color: var(--sheet-text);
  font-size: 17px;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.home-set-line {
  color: var(--sheet-muted);
  font-size: 14px;
}

/* My lists: the plate and a list group under it; without cards above, no divider and the
   cards' 16 px above the plate. */
/* A section's head over its list, both lists headed alike: the caption in the bright text, a
   muted line under it; the line's 8 px is the gap to the list. */
.home-caption {
  margin: 20px 0 0;
  padding: 0 16px;
  color: var(--sheet-text);
  font-size: 16px;
  font-weight: 600;
}

.home-caption-line {
  margin: 4px 0 8px;
  padding: 0 16px;
  color: var(--sheet-faint);
  font-size: 14px;
}

.home-mine {
  margin: 0;
  padding: 0;
  border-radius: 14px;
  list-style: none;
  overflow: hidden;
}

.home-mine-row {
  display: flex;
  align-items: center;
}

.home-mine-row + .home-mine-row {
  border-top: 1px solid var(--sheet-line);
}

.home-mine-row > a {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 48px;
  padding: 8px 14px;
  text-decoration: none;
}

.home-mine-row > a:active {
  background: color-mix(in srgb, white 4%, var(--sheet-surface-low));
}

.home-mine-title {
  color: var(--sheet-text-low);
  font-size: 16px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.home-mine-line {
  flex: none;
  color: var(--sheet-muted);
  font-size: 14px;
}

/* The bin of a list's Remove button, with a 44 px hit area. */
.home-remove {
  flex: none;
  width: 44px;
  height: 44px;
  margin-right: 2px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: var(--sheet-muted);
  -webkit-mask: url(icons/bin.svg) center / 20px no-repeat;
  mask: url(icons/bin.svg) center / 20px no-repeat;
}

.home-remove:focus-visible {
  outline: 2px solid var(--sheet-accent);
  outline-offset: -2px;
}

.home-ack .sheet-link-icon {
  -webkit-mask-image: url(icons/info.svg);
  mask-image: url(icons/info.svg);
}

/* The account row under the Acknowledgment card; the name lines up with the card's icon. */
.home-account {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  margin: 8px 0 0;
  padding: 0 0 0 14px;
}

.home-account-name {
  flex: 0 1 auto;
  min-width: 0;
  color: var(--sheet-muted);
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The version right after the name; the note takes the room up to the button. Five taps on it
   open the reset page (SPEC 5.19), a hidden way in, so it does not look tappable. */
.home-account-version {
  flex: none;
  cursor: default;
  color: var(--sheet-faint);
  font-size: 13px;
}

.home-account-note {
  flex: 1;
  color: var(--sheet-bad);
  font-size: 14px;
  text-align: right;
}

.home-account-button {
  flex: none;
  min-height: 44px;
  padding: 0 12px;
  border: 0;
  background: transparent;
  color: var(--sheet-accent);
  font-size: 16px;
  font-weight: 600;
}

.home-account-button:focus-visible {
  outline: 2px solid var(--sheet-accent);
  outline-offset: -2px;
}

/* Header */

/* The top bar (SPEC 5.5, CR-66 round 3): 44 px, 24 px side padding, no background, no line; the
   body's safe-area padding stays above it. */
.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 44px;
  padding: 0 24px;
}

/* The side blocks share the row's width equally, whatever they hold (one button at the left, the
   voice and the bug at the right since CR-65), so the centre of the row is the centre of the screen. */
.header-left,
.header-right {
  flex: 1 1 0;
  display: flex;
  align-items: center;
}

.header-right {
  justify-content: flex-end;
}

/* The "?" (CR-109 round 1) with the actions at the right. Round 5: the glyphs 24 px apart between
   the "?" and the gear and 16 px between the gear and the bug, the "?" at one and a half of the
   gear's distance from the bug; the last two hit areas overlap 4 px, a tap going to the nearer. */
.topbar-help {
  margin-right: 4px;
}

.topbar-settings {
  margin-right: -4px;
}

/* The session timer's chip (SPEC 5.29, timer.css). */
.header-centre {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The top bar's four buttons: Andrey's solid drawings (icons/topbar/, inline SVG, filled in
   currentColor, the hollows real holes, the bug's legs a 1.6 px stroke of their own; CR-66 round 15)
   at 24 px in a 44 x 44 hit area, in the pocket's outline colour, as topbar.md asks. */
.topbar-button {
  position: relative;
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--pocket-line);
}

.topbar-button svg {
  display: block;
  width: 24px;
  height: 24px;
  overflow: visible;
}

.topbar-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The badge, a circle in bug.svg (11 px): shown while a report has something unread (4.10), in
   the attention orange ringed in the page colour, which cuts the bug's back under it. It pops
   in once when it appears, then breathes while the news is unread, slow enough to be seen from
   the corner of the eye without pulling at it during a card (CR-112); it goes when the report
   with the news is opened. */
.topbar-feedback .badge {
  display: none;
  fill: var(--flower-2);
  stroke: var(--bg);
}

.topbar-feedback.has-news .badge {
  display: block;
  transform-box: fill-box;
  transform-origin: center;
  animation: badge-pop 150ms ease-out both, badge-pulse 1.6s ease-in-out 150ms infinite;
}

@keyframes badge-pop {
  from { transform: scale(0.6); }
  to { transform: scale(1); }
}

@keyframes badge-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.25); opacity: 0.55; }
}

@media (prefers-reduced-motion: reduce) {
  .topbar-feedback.has-news .badge {
    animation: none;
  }
}

.retry {
  min-height: 40px;
  padding: 0 16px;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
}

/* Stage */

.stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--pad);
  touch-action: none;
  perspective: 1200px;
  /* The containing block of the copy of the previous card (.board-ghost). */
  position: relative;
}

.status {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  color: var(--page-text);
  text-align: center;
}

.status-text {
  margin: 0;
  font-size: 1.1rem;
}

/* The board (SPEC 5.5): the mode's section, the attempts row and the hint row, everything
   between the header and the pager row. It is on the page colour, opaque, so it covers the
   previous card while it enters and is invisible once it lands; no size in pixels. It is the
   block that enters and the block that is copied under the entering one (SPEC 5.4). At most
   --board-max wide, centred by the stage (CR-112); its height stays the stage's. */
.board {
  /* The containing block of the health flower's overlays (RTW.flower). */
  position: relative;
  flex: 1 1 auto;
  max-width: var(--board-max);
  background: var(--bg);
  align-self: stretch;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* The entry of the next card (SPEC 5.4, DESIGN.md): it comes from beyond the right edge
     turned and enlarged and settles flat, as a card laid on the pile from the viewer's side.
     The turned and scaled board's leftmost point lies 1.5 * (w + h) / (2 * sqrt 2), under
     0.54 * (100vw + 100vh), left of its centre, which sits at 50vw; 104vw + 54vh therefore
     starts it wholly beyond the right edge at every viewport. The duration, 350ms, is
     ENTRY_MS in app.js. */
  --entry-turn: 45deg;
  --entry-scale: 1.5;
  --entry-x: calc(104vw + 54vh);
}

/* The space the bottom row and the hint row leave, with the mode's section centred in it,
   as the stage centred it before the board. */
.deck {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The transform and will-change exist only while the class is on, so the 3D flips inside
   the board get the stage's perspective again once the card has landed. */
/* Rounded only while it enters, so the corner that shows first is a card's corner. */
.board.is-entering {
  border-radius: 20px;
  position: relative;
  z-index: 1;
  will-change: transform;
  pointer-events: none;
  animation: board-enter 350ms ease-out;
}

@keyframes board-enter {
  from { transform: translateX(var(--entry-x)) rotate(var(--entry-turn)) scale(var(--entry-scale)); }
  to { transform: none; }
}

/* A card out of the pocket (SPEC 5.22, CR-79): after the entry the board takes no input until the
   card has flown out and its flower's arrival has ended. */
.board.is-arriving {
  pointer-events: none;
}

/* The still copy of the previous card under the entering one: the whole board, its deck
   keeps data-mode, so the mode layouts apply to it. */
.stage > .board.board-ghost {
  position: absolute;
  z-index: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .board.is-entering {
    animation: none;
  }
}

/* The undo chip of a Skip (SPEC 5.16): at the bottom of the cards screen, 5 px above the
   footer's pager row (the body's padding already holds the safe-area inset). Above the learned
   block (7) and below the modals (10), so a tap reaches the chip and not the pocket under it.
   Outside the board, so the entry's pointer-events do not reach it. The fade, 500ms, is
   UNDO_FADE_MS in app.js. */
.undo {
  position: absolute;
  left: 50%;
  /* The footer's bottom padding, the pocket's height (the pager row's), the gap. */
  bottom: calc(4px + var(--pocket-height) + 5px);
  transform: translateX(-50%);
  /* Over the sheets (9, earlier in the document) and the glass (8), under the modals (CR-66 round 13). */
  z-index: 9;
  height: 54px;
  padding: 0 20px;
  border: 1px solid var(--accent);
  border-radius: 16px;
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  color: var(--accent);
  font-size: 22px;
  font-weight: 700;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.undo[hidden] {
  display: none;
}

.undo:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.undo.is-fading {
  opacity: 0;
  transition: opacity 500ms ease-out;
}

.undo-icon {
  flex: none;
  width: 27px;
  height: 27px;
  background-color: currentColor;
  -webkit-mask: url(icons/undo.svg) center / 27px no-repeat;
  mask: url(icons/undo.svg) center / 27px no-repeat;
}

@media (prefers-reduced-motion: reduce) {
  .undo.is-fading {
    transition: none;
  }
}

.arrow {
  flex: none;
  width: var(--arrow);
  height: var(--arrow);
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--page-text);
  font-size: 2.4rem;
  line-height: 1;
  padding: 0 0 4px;
}

.arrow.is-hidden {
  visibility: hidden;
}

/* The footer arrow is the swipe of a mouse (SPEC 5.4): on a touch device it never shows. */
@media not ((hover: hover) and (pointer: fine)) {
  .pager > .arrow {
    visibility: hidden;
  }
}

/* Flip card faces, used by every mode's card */

.face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 20px;
  border-radius: 20px;
  background: var(--surface);
  box-shadow: var(--shadow);
  text-align: center;
  overflow: hidden;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.face-front {
  flex-direction: column;
  font-weight: 700;
  font-size: var(--word-size);
  line-height: 1.2;
  overflow-wrap: anywhere;
}

.face-back {
  flex-direction: column;
  /* Room under the last line; inside the scrolling area where a back scrolls. */
  padding-bottom: 24px;
  font-size: clamp(1.05rem, 4.5vw, 1.3rem);
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.translation {
  font-size: clamp(1.3rem, 6vw, 1.9rem);
  font-weight: 600;
  line-height: 1.25;
}

.divider {
  width: 60%;
  margin: 14px auto;
  border: 0;
  border-top: 1px solid var(--line);
}

.divider[hidden] {
  display: none;
}

.meaning.is-empty {
  color: var(--muted);
}

/* An element RTW.fit sizes (modes/common.js, SPEC 5.5): it breaks between words only, so a word
   too wide overflows and the fit shrinks it; below the floor is-tight lets it break inside, at a
   hyphenation point of its language (its lang, RTW.setWord) where it has one, anywhere else. The
   class counts three times, so it wins over a mode's own overflow-wrap on the element. Never
   wider than its box: a centred flex item would otherwise take its longest word's width and
   never overflow. */
.is-fitted.is-fitted {
  max-width: 100%;
}

.is-fitted.is-fitted:not(.is-tight) {
  overflow-wrap: normal;
  word-break: normal;
  hyphens: none;
}

.is-fitted.is-fitted.is-tight {
  hyphens: auto;
  -webkit-hyphens: auto;
  overflow-wrap: anywhere;
}

/* Word card buttons, used by every mode's card */

.speak {
  position: absolute;
  right: 8px;
  bottom: 8px;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: var(--muted);
  -webkit-mask: url(icons/speaker.svg) center / 24px no-repeat;
  mask: url(icons/speaker.svg) center / 24px no-repeat;
}

.speak:active {
  background-color: var(--accent);
}

/* The big speaker of the cards that ask by ear (Listen and pick, Listen and type). */
.speak-large {
  flex: none;
  width: 64px;
  height: 64px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: var(--text);
  -webkit-mask: url(icons/speaker.svg) center / 48px no-repeat;
  mask: url(icons/speaker.svg) center / 48px no-repeat;
}

.speak-large:active {
  background-color: var(--accent);
}

/* The health flower (SPEC 5.21): petal k is passed (its full colour) when the card's
   data-health is k or more, pale otherwise; the centre and the pale petals' strokes take the
   current level's colour. The petal is one symbol in index.php; a display-none SVG is not a
   safe home for a referenced symbol in every browser, so its holder is only sized away. */
.flower-defs {
  position: absolute;
  width: 0;
  height: 0;
}

.flower {
  position: absolute;
  left: 8px;
  top: 8px;
  width: var(--flower);
  height: var(--flower);
  /* The replay arrow (below) lies around the petals, beyond the grid of the flower. */
  overflow: visible;
  pointer-events: none;
  stroke-width: 10;
  stroke-linejoin: round;
}

/* The replay arrow of the card in review (SPEC 5.21, CR-79): Andrey's drawing around the flower
   (+tools/flower-arrow-svg.php), its ring at about 1.4 times the petals' reach, so at 44 px it
   stays inside the face's rounded corner. Its gradients are its own: never dimmed, no look of a
   level. Shown only while the card element (or the overlay) carries data-review="1"; never on
   Match's pair cards, whose row is too short. */
.flower-arrow {
  display: none;
}

[data-review="1"] .flower-arrow {
  display: inline;
}

/* A card on its way out of the pocket and arriving (CR-79 round 1): the arrival brings the arrow,
   so the small flower shows none until it has ended. */
[data-review="1"].is-arrow-pending .flower-arrow {
  display: none;
}

.match-pair[data-review] .flower-arrow {
  display: none;
}

/* Petal k counted among the petals only: the replay arrow comes first in the flower (CR-79 round 2). */
.flower-petal:nth-child(1 of .flower-petal) {
  --petal: var(--flower-1);
}

.flower-petal:nth-child(2 of .flower-petal) {
  --petal: var(--flower-2);
}

.flower-petal:nth-child(3 of .flower-petal) {
  --petal: var(--flower-3);
}

.flower-petal:nth-child(4 of .flower-petal) {
  --petal: var(--flower-4);
}

.flower-petal:nth-child(5 of .flower-petal) {
  --petal: var(--flower-5);
}

.flower-petal:nth-child(6 of .flower-petal) {
  --petal: var(--flower-6);
}

.flower-petal:nth-child(7 of .flower-petal) {
  --petal: var(--flower-7);
}

.flower-petal:nth-child(8 of .flower-petal) {
  --petal: var(--flower-8);
}

.flower-petal:nth-child(9 of .flower-petal) {
  --petal: var(--flower-9);
}

.flower-petal:nth-child(10 of .flower-petal) {
  --petal: var(--flower-10);
}

[data-health="0"] {
  --level: var(--flower-1);
}

[data-health="1"] {
  --level: var(--flower-1);
}

[data-health="2"] {
  --level: var(--flower-2);
}

[data-health="3"] {
  --level: var(--flower-3);
}

[data-health="4"] {
  --level: var(--flower-4);
}

[data-health="5"] {
  --level: var(--flower-5);
}

[data-health="6"] {
  --level: var(--flower-6);
}

[data-health="7"] {
  --level: var(--flower-7);
}

[data-health="8"] {
  --level: var(--flower-8);
}

[data-health="9"] {
  --level: var(--flower-9);
}

[data-health="10"] {
  --level: var(--flower-10);
}

/* Every look below sets --flower-fill and --flower-stroke; the dim is applied once, here. By
   colour, never by opacity: the flower shares a face with a blended picture (CR-52 round 4). */
.flower-petal,
.flower-centre,
.flower-wipe {
  fill: color-mix(in srgb, var(--flower-fill) var(--flower-dim), var(--surface));
  stroke: color-mix(in srgb, var(--flower-stroke) var(--flower-dim), var(--surface));
}

.flower-petal {
  --flower-fill: color-mix(in srgb, var(--petal) 20%, var(--surface));
  --flower-stroke: color-mix(in srgb, var(--level) 50%, var(--surface));
}

[data-health="1"] .flower-petal:nth-child(-n+1 of .flower-petal),
[data-health="2"] .flower-petal:nth-child(-n+2 of .flower-petal),
[data-health="3"] .flower-petal:nth-child(-n+3 of .flower-petal),
[data-health="4"] .flower-petal:nth-child(-n+4 of .flower-petal),
[data-health="5"] .flower-petal:nth-child(-n+5 of .flower-petal),
[data-health="6"] .flower-petal:nth-child(-n+6 of .flower-petal),
[data-health="7"] .flower-petal:nth-child(-n+7 of .flower-petal),
[data-health="8"] .flower-petal:nth-child(-n+8 of .flower-petal),
[data-health="9"] .flower-petal:nth-child(-n+9 of .flower-petal),
[data-health="10"] .flower-petal:nth-child(-n+10 of .flower-petal) {
  --flower-fill: var(--petal);
  --flower-stroke: color-mix(in srgb, var(--petal) 88%, black);
}

.flower-centre {
  --flower-fill: var(--level);
  --flower-stroke: color-mix(in srgb, var(--level) 88%, black);
}

[data-health="0"] .flower-centre {
  --flower-fill: color-mix(in srgb, var(--level) 20%, var(--surface));
  --flower-stroke: color-mix(in srgb, var(--level) 50%, var(--surface));
}

/* The review ladder (SPEC 5.21, CR-96 and round 1): in the top right corner of the card in
   review, its centre line on the flower's, out of reach of the grown flower and its arrow; one
   small flower per REVIEW_DAYS entry, the reviews passed lit (is-lit, RTW.flower.set) as the
   flower at HEALTH_MAX, the rest in its unpassed look, petal by petal; never the card's level (a
   lapse puts the row out while the card goes to 9). Its looks go through --flower-fill and
   --flower-stroke, so the flower's dim applies; these rules come after the petals' level rules,
   which they meet at the same specificity. The big flower's stroke of 10 vanishes at 16 px.
   Never on the overlay: it stays small on the card. */
.flower-ladder {
  display: none;
  position: absolute;
  right: 8px;
  top: calc(8px + var(--flower) / 2 - 8px);
  gap: 4px;
  pointer-events: none;
}

[data-review="1"] .flower-ladder {
  display: flex;
}

.flower-ladder .flower {
  position: static;
  width: 16px;
  height: 16px;
  stroke-width: 20;
}

.flower-ladder .flower .flower-arrow {
  display: none;
}

[data-review="1"] .flower-ladder .flower .flower-petal {
  --flower-fill: color-mix(in srgb, var(--petal) 20%, var(--surface));
  --flower-stroke: color-mix(in srgb, var(--petal) 50%, var(--surface));
}

[data-review="1"] .flower-ladder .flower .flower-centre {
  --flower-fill: color-mix(in srgb, var(--flower-10) 20%, var(--surface));
  --flower-stroke: color-mix(in srgb, var(--flower-10) 50%, var(--surface));
}

[data-review="1"] .flower-ladder .flower.is-lit .flower-petal {
  --flower-fill: var(--petal);
  --flower-stroke: color-mix(in srgb, var(--petal) 88%, black);
}

[data-review="1"] .flower-ladder .flower.is-lit .flower-centre {
  --flower-fill: var(--flower-10);
  --flower-stroke: color-mix(in srgb, var(--flower-10) 88%, black);
}

/* Full from the answer's play start until the card is left (round 2): the change is seen in the
   colours the big flower plays in. */
.is-ladder-live .flower-ladder .flower {
  --flower-dim: 100%;
}

/* The level colour is registered so a change of level transitions it, and the gradient over
   it follows (CR-85); a gradient cannot transition by itself. */
@property --level {
  syntax: "<color>";
  inherits: true;
  initial-value: #f6f7f9;
}

/* The card's surface in the current level's colour, faintly. A card without data-health
   (New) stays on the plain surface. --rest-bg is the flat resting fill; --rest-surface the
   level at --flower-tint laid over the plain gradient (the same as mixing it into both stops,
   and it fades with --surface-plain in a swipe), for the selected frame below to tint over. */
[data-health] .face,
.type-card[data-health],
.match-picture[data-health],
.match-pair[data-health] {
  --rest-bg: color-mix(in srgb, var(--level) var(--flower-tint), var(--surface));
  --level-tint: color-mix(in srgb, var(--level) var(--flower-tint), transparent);
  --rest-surface: linear-gradient(var(--level-tint), var(--level-tint)), var(--surface-plain);
  background: var(--rest-surface);
}

/* Frames (SPEC 5.5, CR-71): one verdict frame in every mode, the 2 px border and a 14 px glow
   in --ok or --bad, on the card, tile or face that carries the state; the modes' stylesheets
   give these states no border colour or shadow of their own. The selection (Match, Match the
   translations, and a drop target in Match, Pick the word and the Pick boards) is in the known colour, not the
   accent, so it stands out on a light card and is neither a verdict's colour nor the Undo chip's:
   the --known border and a 12% --known tint over the resting fill (a picture card's level tint),
   the drop shadow kept, never a glow. The verdicts come after it and win. */
.match-word.is-selected,
.match-picture.is-selected,
.match-word.is-drop,
.match-picture.is-drop,
.meaning-card.is-drop .face-front,
.pick-tile.is-drop .pick-face {
  border-color: var(--known);
}

/* The tint is a layer over the card's gradient, plain or level-tinted, so the gradient shows
   through (CR-84, CR-85). */
.match-word.is-selected,
.match-picture.is-selected,
.match-word.is-drop,
.match-picture.is-drop,
.meaning-card.is-drop .face-front,
.pick-tile.is-drop .pick-face,
.pick-card.is-drop .face-front {
  --known-tint: color-mix(in srgb, var(--known) 12%, transparent);
  background: linear-gradient(var(--known-tint), var(--known-tint)), var(--rest-surface, var(--surface-plain));
}

/* Pick's card under a dragged tile (Pick the picture, Listen and pick, Pick the word by
   translation): its face has no border of its own, so the selected frame's border is a ring
   beside the drop shadow. */
.pick-card.is-drop .face-front {
  box-shadow: var(--shadow), inset 0 0 0 2px var(--known);
}

.pick-tile.is-correct .pick-face,
.match-word.is-right,
.match-picture.is-right,
.meaning-card.is-right .meaning-face,
.meaning-option.is-right {
  border-color: var(--ok);
  box-shadow: 0 0 14px var(--ok);
}

.pick-tile.is-wrong .pick-face,
.match-word.is-wrong,
.match-picture.is-wrong,
.meaning-card.is-wrong .meaning-face,
.meaning-option.is-wrong {
  border-color: var(--bad);
  box-shadow: 0 0 14px var(--bad);
}

/* The tint moves with the changing petal: the 300 ms are FLOWER_FILL_MS in modes/common.js.
   The level colour transitions (@property --level) and the gradient follows it. Match's cards
   keep their own transitions (modes/match.css). A card shown at a new level takes it at once
   (is-flower-still, RTW.flower.set). */
[data-health] .face,
.type-card[data-health] {
  transition: --level 300ms ease;
}

.is-flower-still .face,
.type-card.is-flower-still {
  transition: none;
}

/* A card that has shown its result keeps its flower full until the next show (SPEC 5.21);
   the property inherits to the flower. */
.is-flower-full {
  --flower-dim: 100%;
}

/* The small flower while its overlay plays over it; the ladder under it stays. */
.is-flower-covered .flower {
  visibility: hidden;
}

.is-flower-covered .flower-ladder .flower {
  visibility: visible;
}

/* A flip card turning (RTW.flipCard.setFlipped): the flower of the face coming into view is
   hidden until the flip has ended and shows at once then; the one going away turns with its
   face. No fade: the face may hold a blended picture (the Cards front), and an opacity change
   beside it makes Safari show the picture's white square (DESIGN.md, CR-52 round 4). */
.is-turning.is-flipped .face-back .flower,
.is-turning:not(.is-flipped) .face-front .flower {
  visibility: hidden;
}

/* The overlay: a copy of the small flower on the board, placed over it by RTW.flower (its
   left, top, width and height are the one geometry the script sets inline). It grows and
   shrinks about its centre over FLOWER_GROW_MS and FLOWER_SHRINK_MS (200 ms each). */
.flower.flower-overlay {
  --flower-dim: 100%;
  z-index: 5;
  transform-origin: center;
  transition: transform 200ms ease-out;
}

.flower-overlay.is-grown {
  transform: scale(var(--flower-grow));
}

.flower-overlay.is-shrinking {
  transition-timing-function: ease-in;
}

/* Held grown for the flower sticker (CR-109 round 2, SPEC 5.21): over the sticker's glass (10), so
   it stays sharp in its place. RTW.flower moves it to the body for the pause (#stage's perspective
   is a stacking context), fixed at the viewport spot it had. */
.flower.flower-overlay.is-lifted {
  position: fixed;
  z-index: 11;
}

/* The centre blinks twice, FLOWER_BLINK_MS = 2 x 250 ms, ending on its own fill. */
.flower-overlay.is-blinking .flower-centre {
  animation: flower-blink 250ms ease-in-out 2;
}

@keyframes flower-blink {
  50% {
    fill: color-mix(in srgb, var(--level) 20%, var(--surface));
  }
}

/* The colours move to the new level over FLOWER_SWITCH_MS. */
.flower-overlay.is-switching .flower-petal,
.flower-overlay.is-switching .flower-centre {
  transition: fill 150ms ease, stroke 150ms ease;
}

/* The ladder's changing flowers blink with the overlay's centre and switch with its colours on
   the card (CR-96); is-ladder-switching lasts from the blink to the play's end. */
.flower-ladder .flower.is-blinking .flower-petal,
.flower-ladder .flower.is-blinking .flower-centre {
  animation: flower-blink 250ms ease-in-out 2;
}

.is-ladder-switching .flower-ladder .flower .flower-petal,
.is-ladder-switching .flower-ladder .flower .flower-centre {
  transition: fill 150ms ease, stroke 150ms ease;
}

/* The changing petal keeps the pale look under the wipe, a copy of it in the passed look of
   its own row that fills from the base or drains from the tip over FLOWER_FILL_MS. The inset
   is in the petal's own coordinates, where the tip is up, whatever its turn. */
.flower-row-1 {
  --petal: var(--flower-1);
}

.flower-row-2 {
  --petal: var(--flower-2);
}

.flower-row-3 {
  --petal: var(--flower-3);
}

.flower-row-4 {
  --petal: var(--flower-4);
}

.flower-row-5 {
  --petal: var(--flower-5);
}

.flower-row-6 {
  --petal: var(--flower-6);
}

.flower-row-7 {
  --petal: var(--flower-7);
}

.flower-row-8 {
  --petal: var(--flower-8);
}

.flower-row-9 {
  --petal: var(--flower-9);
}

.flower-row-10 {
  --petal: var(--flower-10);
}

.flower-overlay .flower-petal.is-wiping {
  --flower-fill: color-mix(in srgb, var(--petal) 20%, var(--surface));
  --flower-stroke: color-mix(in srgb, var(--level) 50%, var(--surface));
}

.flower-wipe {
  --flower-fill: var(--petal);
  --flower-stroke: color-mix(in srgb, var(--petal) 88%, black);
}

.flower-wipe.is-gain {
  clip-path: inset(100% 0 0 0);
}

.flower-overlay.is-filling .flower-wipe {
  animation: flower-fill 300ms ease-out forwards;
}

.flower-overlay.is-draining .flower-wipe {
  animation: flower-drain 300ms ease-in forwards;
}

@keyframes flower-fill {
  from {
    clip-path: inset(100% 0 0 0);
  }

  to {
    clip-path: inset(0);
  }
}

@keyframes flower-drain {
  from {
    clip-path: inset(0);
  }

  to {
    clip-path: inset(100% 0 0 0);
  }
}

/* The arrival of a card out of the pocket (RTW.flower.arrive, CR-79, rounds 1, 3 and 4), the
   failed review's departure backwards: when the overlay has grown the arrow comes out from under
   the petals already spinning. It turns FLOWER_SPIN_TURNS times (4, --flower-spin-turns) over
   --flower-arrow-ms (FLOWER_ARROW_SPIN_MS, 950 ms), fast at first and slowing into its drawn
   position (the departure's curve reversed), while over the first --flower-arrow-grow-ms
   (FLOWER_ARROW_GROW_MS, 300 ms) it grows from opacity 0 and FLOWER_ARROW_FROM (0.2, a ring about
   the centre's size) to opaque and full, ease-out. All three are set by the script. The growth
   and the spin are two animations on the individual scale and rotate, so each has its own curve. Until the growth starts the arrow
   waits at the start values. The <use> turns about 0 0 of the flower's grid, which is the centre.
   The overlay stands on the board, outside the card and its blended picture, so it may fade. */
.flower-overlay .flower-arrow {
  transform-box: view-box;
  transform-origin: 0 0;
}

.flower-overlay.is-arriving .flower-arrow {
  opacity: 0;
  scale: 0.2;
}

.flower-overlay.is-arriving.is-spinning .flower-arrow {
  animation:
    flower-arrow-in var(--flower-arrow-grow-ms) ease-out forwards,
    flower-arrow-spin var(--flower-arrow-ms) cubic-bezier(0.2, 0.7, 0.2, 1);
}

@keyframes flower-arrow-in {
  from {
    opacity: 0;
    scale: 0.2;
  }

  to {
    opacity: 1;
    scale: 1;
  }
}

@keyframes flower-arrow-spin {
  from {
    rotate: calc(var(--flower-spin-turns) * 360deg);
  }

  to {
    rotate: 0deg;
  }
}

/* The answer on a card in review (CR-79 rounds 1, 3 and 4): from the blink to the end of the petal
   step (--flower-arrow-ms, FLOWER_BLINK_MS + FLOWER_SWITCH_MS + FLOWER_FILL_MS, 950 ms, set by the
   script) the arrow spins FLOWER_SPIN_TURNS turns on the arrival's curve reversed, slow to fast.
   Failed: clockwise, and over the last --flower-arrow-grow-ms (300 ms, the petal step) it fades and
   shrinks to FLOWER_ARROW_FROM under the petals, ease-in, gone after it. Passed:
   counter-clockwise as at the arrival, opaque and full, at rest at the end. */
.flower-overlay.is-arrow-out .flower-arrow {
  animation:
    flower-arrow-out var(--flower-arrow-ms) cubic-bezier(0.8, 0, 0.8, 0.3),
    flower-arrow-dive var(--flower-arrow-grow-ms) ease-in calc(var(--flower-arrow-ms) - var(--flower-arrow-grow-ms)) forwards;
}

.flower-overlay.is-arrow-again .flower-arrow {
  animation: flower-arrow-again var(--flower-arrow-ms) cubic-bezier(0.8, 0, 0.8, 0.3);
}

@keyframes flower-arrow-out {
  from {
    rotate: 0deg;
  }

  to {
    rotate: calc(var(--flower-spin-turns) * 360deg);
  }
}

@keyframes flower-arrow-dive {
  from {
    opacity: 1;
    scale: 1;
  }

  to {
    opacity: 0;
    scale: 0.2;
  }
}

@keyframes flower-arrow-again {
  from {
    rotate: 0deg;
  }

  to {
    rotate: calc(var(--flower-spin-turns) * -360deg);
  }
}

/* Cards (SPEC 5.3, RTW.flower.follow): the overlay follows the swipe's pull, 0 at rest to 1 at
   the threshold, with no transition while the finger drives it; the petal's wipe fills with it.
   It drains back over the swipe's spring (BACK_MS, 200 ms) and shrinks away with the flight
   (FLY_MS, 300 ms), its colours moving to the new level in the first 150 ms of it. */
.flower-overlay.is-pulled {
  transform: scale(calc(1 + (var(--flower-grow) - 1) * var(--pull)));
  transition: none;
}

.flower-overlay.is-pulled .flower-wipe {
  clip-path: inset(calc((1 - var(--pull)) * 100%) 0 0 0);
}

.flower-overlay.is-pulled.is-returning {
  transition: transform 200ms ease-out;
}

.flower-overlay.is-pulled.is-returning .flower-wipe {
  transition: clip-path 200ms ease-out;
}

.flower-overlay.is-pulled.is-flying {
  transform: none;
  transition: transform 300ms ease-in;
}

.flower-overlay.is-pulled.is-flying .flower-petal,
.flower-overlay.is-pulled.is-flying .flower-centre {
  transition: fill 150ms ease, stroke 150ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .flower,
  .flower *,
  [data-health] .face,
  .type-card[data-health],
  .match-picture[data-health],
  .match-pair[data-health] {
    transition: none;
    animation: none;
  }
}

/* The wrong-answer shake moves the whole screen. */
.cards.is-shaking {
  animation: shake 400ms ease;
}

@keyframes shake {
  0% { transform: translateX(0); }
  14% { transform: translateX(-8px); }
  28% { transform: translateX(8px); }
  42% { transform: translateX(-6px); }
  57% { transform: translateX(6px); }
  71% { transform: translateX(-3px); }
  85% { transform: translateX(3px); }
  100% { transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
  .cards.is-shaking {
    animation: none;
  }
}

/* The Cards back, shared by Cards and the New card (SPEC 5.3, 5.16): a head row at the top
   (the picture as a memory anchor, the word beside it), then the body centred both ways in
   the height that is left. */

.card-back-head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
}

/* About the size of a Pick tile's picture: 40% of the face's inner width. */
.card-thumb {
  flex: none;
  width: min(40%, 160px);
  aspect-ratio: 1 / 1;
  border-radius: 12px;
  object-fit: contain;
  mix-blend-mode: multiply;
}

.card-back-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 100%;
  text-align: center;
}

/* The translation is the answer, in the word's size; the meaning is the note. The translation
   never gives up height for the meaning. */
.card-back-body > .translation {
  flex: none;
  font-size: var(--word-size);
}

/* One rule, between the thing (picture, word) and what it means. It fades out at both
   ends, so it marks the border without cutting the card into bands. */
.card-back-rule {
  flex: none;
  width: 100%;
  height: 2px;
  margin: 16px 0 0;
  border: 0;
  background: linear-gradient(90deg, transparent, var(--line) 50%, transparent);
}

/* Bounded by the room the body leaves under the translation, so the height fit sees a meaning
   that does not fit (modes/common.js, SPEC 5.5); below the floor it is clipped at the body's
   bottom. */
.card-back-body > .meaning {
  color: var(--muted);
  font-size: 17px;
  line-height: 1.45;
  flex: 0 1 auto;
  min-height: 0;
  overflow: hidden;
  width: 100%;
}

/* The size is the stylesheet's at most; the mode fits it down to 18 px before a word may
   break. */
.card-back-word {
  flex: 1;
  min-width: 0;
  text-align: left;
  font-size: var(--word-size);
  font-weight: 700;
  line-height: 1.2;
  overflow-wrap: normal;
  word-break: normal;
  hyphens: none;
}

.card-back-word.is-tight {
  overflow-wrap: anywhere;
}

/* Swipes (SPEC 5.4), in Cards and New: modes/common.js moves .swipe-target with the finger
   and sets --swipe-fade from 0% at rest to 100% at a travel of the card's width. The card's
   surface and text fade toward the page colour through the colour variables of its child,
   never through opacity (a blended picture would show its white square in Safari). */
@property --swipe-fade {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

.swipe-target {
  --swipe-surface: var(--surface);
  --swipe-plain: var(--surface-plain);
  --swipe-text: var(--text);
  --swipe-muted: var(--muted);
}

.swipe-target > * {
  --surface: color-mix(in srgb, var(--swipe-surface), var(--bg) var(--swipe-fade));
  /* The gradient fades under a layer of the page colour at the same strength. */
  --surface-plain: linear-gradient(var(--swipe-veil), var(--swipe-veil)), var(--swipe-plain);
  --swipe-veil: color-mix(in srgb, var(--bg) var(--swipe-fade), transparent);
  --text: color-mix(in srgb, var(--swipe-text), var(--bg) var(--swipe-fade));
  --muted: color-mix(in srgb, var(--swipe-muted), var(--bg) var(--swipe-fade));
  color: var(--text);
}

.swipe-target.is-dragging {
  will-change: transform;
}

.swipe-target.is-flying {
  transition: transform 300ms ease-in, --swipe-fade 300ms ease-in;
}

.swipe-target.is-returning {
  transition: transform 200ms ease-out, --swipe-fade 200ms ease-out;
}

/* The shadow goes with the surface during the flight only, so a card that ends under its
   button leaves no shadow around it; a card in the hand keeps casting one. The same for a card
   flying into the learned pocket (pocket.js), whose flip cards cast theirs from .face; scoped
   to the deck so the result glow of a right answer (.meaning-card.is-right .meaning-face) goes
   too. */
.swipe-target.is-flying .face,
.board .deck .is-pocketing .face {
  box-shadow: none;
  transition: box-shadow 300ms ease-in;
}

@media (prefers-reduced-motion: reduce) {
  .swipe-target.is-flying,
  .swipe-target.is-flying .face,
  .swipe-target.is-returning {
    transition: none;
  }
}

/* A card flying into the learned pocket (SPEC 5.22). The individual transform properties, not
   transform: the flip cards are turned through transform and the swipe sets it inline; these
   compose with it, applied before it, so a flipped card flies flipped. 300ms is POCKET_FLY_MS
   in pocket.js, the swipe's FLY_MS. No fade: the card goes into the pocket. Scoped to the
   board's deck so it outweighs the modes' own transitions on their cards (the flip's
   .meaning-card, .pick-card, the tint's .match-pair[data-health]), which the flight replaces.
   z-index 6: over everything on the board (the attempts row's masked faces, the flower overlay's
   5), under the learned block's 7. */
.board .deck .is-pocketing {
  z-index: 6;
  pointer-events: none;
  transition: translate 300ms ease-in, rotate 300ms ease-in, scale 300ms ease-in;
}

/* The cards that cast their shadow themselves: the Type card and Match's pair card. */
.board .deck .match-pair.is-pocketing {
  position: relative;
}

.board .deck .type-card.is-pocketing,
.board .deck .match-pair.is-pocketing {
  box-shadow: none;
  transition: translate 300ms ease-in, rotate 300ms ease-in, scale 300ms ease-in, box-shadow 300ms ease-in;
}

/* The card is left during its flight (RTW.pocket.cancel): it stands at its end at once. */
.board .deck .is-pocketing.is-cut,
.board .deck .is-pocketing.is-cut .face,
.board .deck .type-card.is-pocketing.is-cut,
.board .deck .match-pair.is-pocketing.is-cut {
  transition: none;
}

/* The landed card is invisible (it fits the pocket at rest, but nothing of it shows while the
   pocket settles), in the board's copy too; a cut flight's card as well; and a card given to the
   pocket (pocket.js give) until it flies out. Visibility, not opacity: a card may hold a blended
   picture. */
.board .deck .is-pocketed,
.board .deck .is-pocketing.is-cut {
  visibility: hidden;
}

/* A card taken out of the pocket (pocket.js give, CR-79): it stands at a landed card's values
   with no transition (is-still), then moves to its place over 300ms ease-out, POCKET_FLY_MS, over
   everything on the board, as the flight in does. */
.board .deck .is-unpocketing {
  z-index: 6;
  pointer-events: none;
  transition: translate 300ms ease-out, rotate 300ms ease-out, scale 300ms ease-out;
}

.board .deck .match-pair.is-unpocketing,
.board .deck .match-picture.is-unpocketing {
  position: relative;
}

.board .deck .is-unpocketing.is-still {
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .board .deck .is-pocketing,
  .board .deck .is-pocketing .face,
  .board .deck .type-card.is-pocketing,
  .board .deck .match-pair.is-pocketing,
  .board .deck .is-unpocketing {
    transition: none;
  }
}

/* The copy of a dragged tile or card (SPEC 5.9, RTW.dragTo in modes/common.js), in Pick,
   Listen, Match and Pick the word: drawn whole above everything with the card's shadow, never
   faded. The script sets its size, the source's measured sizes on the copy and its transform. */
.drag-ghost {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 100;
  pointer-events: none;
}

.drag-ghost > * {
  box-shadow: var(--shadow);
}

.drag-ghost.is-flying {
  transition: transform 200ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .drag-ghost.is-flying {
    transition: none;
  }
}

/* Bottom row: the attempts, inside the board under the deck. It has its height only in the
   modes that show attempts, so the faces stay put there; elsewhere it collapses and the deck
   gets the space. app.js mirrors the deck's data-mode on it. */

.bottom {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(calc(var(--deck-wide) + 2 * var(--pad)), 100%);
  height: 0;
  margin: 0 auto;
  padding: 0 var(--pad);
  overflow: hidden;
}

.bottom[data-mode="type"],
.bottom[data-mode="match"],
.bottom[data-mode="match-reverse"],
.bottom[data-mode="meaning"],
.bottom[data-mode="dictation"] {
  height: var(--bottom);
  overflow: visible;
}

/* Pick the word with one life shows no faces (RTW.attempts.hide), so the row collapses as in
   Pick and the deck gets the space (SPEC 5.13). */
.bottom[data-mode="meaning"]:not(:has(> .attempts.is-on)) {
  height: 0;
  overflow: hidden;
}

/* The faces: a smiling face per attempt left, a sad one per attempt lost. The lost face
   is faded red, lighter than the full red of a wrong answer; the thin outline needs more
   than the 25% the filled heart had. Invisible unless a mode shows them. */
.attempts {
  display: flex;
  justify-content: center;
  gap: 10px;
}

.attempts:not(.is-on) {
  visibility: hidden;
}

.attempt {
  width: 24px;
  height: 24px;
  background-color: var(--ok);
  -webkit-mask: url(icons/smile.svg) center / 24px no-repeat;
  mask: url(icons/smile.svg) center / 24px no-repeat;
}

.attempt.is-lost {
  background-color: var(--bad);
  opacity: 0.4;
  -webkit-mask-image: url(icons/frown.svg);
  mask-image: url(icons/frown.svg);
}

/* Footer */

.footer {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* 6 px at the sides puts each arrow box's centre 30 px from the edge, under the centre
     of the outermost header button (8 px header padding plus half of 44 px): back on the
     left, the voice selector on the right. Nothing on top: the board ends at the pager row. */
  padding: 0 6px 4px;
}

/* "Tap or swipe to continue" or, in Cards, "Tap to turn" over "Swipe once remembered": the shell's,
   the board's last row, above the pager in every mode; its margins are the footer's old top
   padding and its old gap to the pager, so the hint and the pager keep their places. The row is always laid out, so the hint showing moves
   nothing; Cards' first line stands above the row, where the bottom row is in the modes with
   attempts and nothing is in Cards, so the stage keeps its height (SPEC 5.3, 5.5). */
.hint {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex: none;
  height: 24px;
  margin: var(--pad) 0 4px;
  color: var(--page-text);
  font-size: 14px;
  font-style: italic;
  visibility: hidden;
}

.hint.is-on {
  visibility: visible;
  /* A slow pulse in the page's text colour so the eye finds it. */
  animation: hint-pulse 1.6s ease-in-out infinite;
}

@keyframes hint-pulse {
  0%,
  100% {
    opacity: 0.35;
  }

  50% {
    opacity: 1;
  }
}

.hint-icon {
  flex: none;
  width: 18px;
  height: 18px;
  background-color: var(--page-text);
  -webkit-mask: url(icons/tap.svg) center / 18px no-repeat;
  mask: url(icons/tap.svg) center / 18px no-repeat;
}

/* The continue hint shows the tap and the swipe icons; Cards' line shows the swipe icon alone. */
.hint-icon-swipe,
.hint[data-kind="swipe"] > .hint-icon-tap {
  -webkit-mask-image: url(icons/swipe.svg);
  mask-image: url(icons/swipe.svg);
}

.hint[data-kind="swipe"] > .hint-icon-swipe,
.hint[data-kind="swipe"] .hint-tap,
.hint:not([data-kind="swipe"]) .hint-swipe,
.hint:not([data-kind="swipe"]) .hint-turn {
  display: none;
}

.hint-turn {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 4px);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

@media (prefers-reduced-motion: reduce) {
  .hint.is-on {
    animation: none;
    opacity: 1;
  }
}

/* The pager: an empty arrow box, the learned pocket with "/ <count>" beside it, the next
   arrow. The row is as high as the pocket (--pocket-height, 64 px since CR-63 round 4), so the
   pocket sits 4 px above the footer's bottom; the 48 px arrow boxes are centred on it. */
.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  align-self: stretch;
}

/* The learned pocket and "/ <count>" (SPEC 5.22). Laid out always, so the footer keeps its
   height while there is no set state; above the flying card's 6, so the card goes under the
   lid, and above the entering board's sweep. */
.learned {
  display: flex;
  align-items: center;
  gap: 8px;
  position: relative;
  z-index: 7;
  visibility: hidden;
}

.learned.is-on {
  visibility: visible;
}

/* --pocket-height high, the drawing's 480 x 494 giving the width. The shadow is --shadow as two drop
   shadows (a drop-shadow blur is a standard deviation, half a box-shadow's), so it follows the
   dome. A tap does nothing yet. */
.pocket {
  position: relative;
  display: block;
  flex: none;
  height: var(--pocket-height);
  width: var(--pocket-width);
  padding: 0;
  border: 0;
  background: none;
  filter: drop-shadow(0 8px 12px rgba(20, 30, 50, 0.12)) drop-shadow(0 2px 3px rgba(20, 30, 50, 0.08));
  transform-origin: center;
}

.pocket:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The drawing and the number together, so the shake (below) turns them as one about the
   pocket's bottom centre while the button grows about its own centre. */
.pocket-body {
  position: absolute;
  inset: 0;
  display: block;
  transform-origin: 50% 100%;
}

.pocket .pocket-svg {
  display: block;
  width: 100%;
  height: 100%;
}

.pocket-svg .is-off {
  display: none;
}

/* The drawing's greys recoloured (CR-63 round 4): a CSS fill or stroke outweighs the SVG's
   presentation attributes. Prefix selectors, so the state layers need no listing; the lenses'
   linear gradients stay as drawn, and pocket.js's visibility rule is untouched. */
.pocket-svg [id^="pocket-folder-back"] {
  fill: var(--pocket-back);
  stroke: var(--pocket-line);
}

.pocket-svg [id^="pocket-lid-"] {
  fill: var(--pocket-lid);
}

.pocket-svg [id^="pocket-outline-"] {
  stroke: var(--pocket-line);
}

.pocket-svg [id^="pocket-stitch-"] {
  stroke: var(--pocket-line);
}

.pocket-svg [id^="pocket-radialGradient-"] stop {
  stop-color: var(--pocket-crease);
}

/* It grows with the flight, 300ms as POCKET_FLY_MS in pocket.js, and settles back once the
   card has landed, 200ms as POCKET_SETTLE_MS. */
.pocket.is-taking {
  transform: scale(var(--pocket-grow));
  transition: transform 300ms ease-out;
}

.pocket.is-settling {
  transform: scale(1);
  transition: transform 200ms ease-in;
}

/* "/ <count>" steps aside by half the pocket's extra width while it is grown, so the two never
   overlap; 300ms is POCKET_FLY_MS and 200ms POCKET_SETTLE_MS in pocket.js, as the pocket's. */
.pocket.is-taking + .pocket-total {
  translate: calc(var(--pocket-width) * (var(--pocket-grow) - 1) / 2) 0;
  transition: translate 300ms ease-out;
}

.pocket.is-settling + .pocket-total {
  translate: 0 0;
  transition: translate 200ms ease-in;
}

/* A card taken out of the pocket (pocket.js give) and a review failed (lose), CR-79: the pocket
   grows over POCKET_SHAKE_MS (350 ms, the entry's ENTRY_MS) while its drawing and number shake
   three times about the bottom centre, to POCKET_SHAKE_DEG (6) either way, lifted by
   POCKET_SHAKE_LIFT_PX (3) at every extreme; "/ <count>" steps aside as in the flight in. It
   settles with is-settling. */
.pocket.is-shaking {
  transform: scale(var(--pocket-grow));
  transition: transform 350ms ease-out;
}

.pocket.is-shaking .pocket-body {
  animation: pocket-shake 350ms ease-in-out;
}

.pocket.is-shaking + .pocket-total {
  translate: calc(var(--pocket-width) * (var(--pocket-grow) - 1) / 2) 0;
  transition: translate 350ms ease-out;
}

@keyframes pocket-shake {
  0% { transform: none; }
  14.29% { transform: translateY(-3px) rotate(6deg); }
  28.57% { transform: translateY(-3px) rotate(-6deg); }
  42.86% { transform: translateY(-3px) rotate(6deg); }
  57.14% { transform: translateY(-3px) rotate(-6deg); }
  71.43% { transform: translateY(-3px) rotate(6deg); }
  85.71% { transform: translateY(-3px) rotate(-6deg); }
  100% { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .pocket.is-taking,
  .pocket.is-settling,
  .pocket.is-shaking,
  .pocket.is-taking + .pocket-total,
  .pocket.is-settling + .pocket-total,
  .pocket.is-shaking + .pocket-total {
    transition: none;
  }

  .pocket.is-shaking .pocket-body {
    animation: none;
  }
}

/* Over the lid, which runs from y 209 to 439 of the drawing's 494. The sizes were 17 and 13 px
   at 56 px high and scale with the pocket. */
.pocket-count {
  position: absolute;
  left: 0;
  right: 0;
  top: 42.3%;
  height: 46.6%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--pocket-count);
  font-size: calc(var(--pocket-height) * 17 / 56);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  pointer-events: none;
}

.pocket-count.is-wide {
  font-size: calc(var(--pocket-height) * 13 / 56);
}

/* On the number's line, not the pocket's middle: 0.656 of the pocket's height is the centre of
   .pocket-count's box (its top 42.3% plus half its height 46.6%), half of it the middle; if
   .pocket-count's top or height ever change, this changes with them. A top, not a translate:
   round 1's step aside during the growth is the translate, and the two compose. */
.pocket-total {
  position: relative;
  top: calc(var(--pocket-height) * 0.656 - var(--pocket-height) / 2);
  color: var(--page-muted);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

.pocket-total b {
  font-weight: 600;
}

/* The sheets (CR-64, shared since CR-65, windows since CR-66 round 1, anchored to their openers
   since round 2): the words screen (words.css) and the feedback sheet (feedback.css) are one box
   over the cards screen, header included, 10 px in from the sides, on the sheet's dark base; above
   the learned block (7), under the hint stickers (10) and the modals (11). Each sets the edge it grows from, 10 px inside the
   cards block (which sits inside the body's safe-area padding): the words sheet its bottom, a
   bottom sheet out of the pocket; the feedback sheet its top, a top sheet out of the bug button.
   A flex column as tall as its content and at most the block less 20 px: the bar stays, the body
   under it shrinks and scrolls inside.
   It grows out of its opener (--sheet-origin-x and -y, the opener's centre in its box) and goes
   back into it. The rounded corners clip the content, always, the bar's corners too. Each sheet
   maps its own variables onto these. The two are never open together: the top bar, which holds
   the other's opener, is inert while one is open. z-index 9: over the glass (8), which follows
   #cards in the body; #cards makes no stacking context, so the two compare at the root. */
.sheet {
  position: absolute;
  left: 10px;
  right: 10px;
  max-height: calc(100% - 20px);
  z-index: 9;
  display: flex;
  flex-direction: column;
  border-radius: 20px;
  background: var(--sheet-bg);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
  overflow: hidden;
  transform-origin: var(--sheet-origin-x, 50%) var(--sheet-origin-y, 100%);
}

/* A top sheet (CR-68, the feedback sheet's rules of CR-66 rounds 2 and 4, shared with the
   settings sheet): its top at the top bar's bottom edge since CR-89 (sheet.js sets --sheet-top from the header's box
   on every opening), the icons sharp and inert above the glass (CR-66 round 13), as a popover from
   its button; growing downward with its view, so the nested views change its height downward and
   its top edge never moves; at most down to 10 px above the cards block's bottom. Grown out of its
   button (sheet.js sets --sheet-origin-x and -y, the button's centre in its box, on the section)
   and going back into it. */
.sheet.sheet-top {
  top: var(--sheet-top, 10px);
  bottom: auto;
  max-height: calc(100% - var(--sheet-top, 10px) - 10px);
}

/* The sheet's 1 px light edge runs under the bar only (CR-66 round 2): drawn as the sheet's own
   border it lies over the bar's top and sides, since overflow clips the bar to the padding box,
   inside the border, and reads as a light plate fading upward. Over the content, not taking
   pointer events. */
.sheet::after {
  content: "";
  position: absolute;
  inset: 40px 0 0;
  border: 1px solid var(--sheet-edge);
  border-top: 0;
  border-radius: 0 0 20px 20px;
  pointer-events: none;
}

/* A centred sheet (the goal card of SPEC 5.29; shared with the intro card of CR-103, gone in CR-109): its
   own size in the cards screen's middle, as tall as its content; it grows out of its opener and
   goes back into it with the sheets' drop (sheet.js), the transform composing with the centring
   translate. */
.sheet.sheet-centred {
  top: 50%;
  left: 50%;
  right: auto;
  bottom: auto;
  width: min(320px, 100% - 40px);
  max-height: none;
  translate: -50% -50%;
  padding: 24px;
}

/* No bar: the light edge runs all round. */
.sheet-centred::after {
  inset: 0;
  border-top: 1px solid var(--sheet-edge);
  border-radius: 20px;
}

/* The glass (CR-66 round 13): under an open sheet the page is not dimmed but blurred, from 8 px
   above the top bar's bottom edge (--glass-top, in viewport pixels, set by words.js and feedback.js
   on every opening; the icons end 10 px above it, so they stay off the glass) to the screen's
   bottom. Its top edge is feathered (round 14): the mask takes the blur from nothing at the top to
   full over those 8 px (CR-89; 32 px before), so the blur is full from the bar's bottom edge, where
   the sheets start, and no sharp strip of the card shows over them; the feather never reaches
   the page's top pixels; the page keeps its colour, so nothing at the top or in the status
   bar has to follow. The top bar stays sharp and live above it: its buttons switch sheets or
   leave (CR-69). A tap on it closes the sheet. The two top sheets share one, #sheet-glass, which
   stays shown through a switch. It follows #cards in the body, so its fixed box is the viewport whatever #cards
   does (the shake's transform); z-index 8, under the sheets (9), the Undo chip (9, later), the hint
   stickers (10) and the modals (11). */
.sheet-scrim {
  position: fixed;
  top: calc(var(--glass-top) - 8px);
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 8;
  background: transparent;
  -webkit-backdrop-filter: blur(var(--glass));
  backdrop-filter: blur(var(--glass));
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 8px);
  mask-image: linear-gradient(to bottom, transparent 0, black 8px);
}

/* The blur grows from nothing with the sheet's drop and clears with its return: WORDS_OPEN_MS and
   FEEDBACK_OPEN_MS (350 ms) in, WORDS_CLOSE_MS and FEEDBACK_CLOSE_MS (250 ms) out; the glass is
   hidden when the closing ends; the feather is static, only the radius moves. Reduced motion: the
   full blur at once. */
.sheet-scrim.is-opening {
  animation: glass-in 350ms ease-out both;
}

.sheet-scrim.is-closing {
  animation: glass-out 250ms ease-in both;
}

@keyframes glass-in {
  from { -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0); }
  to { -webkit-backdrop-filter: blur(var(--glass)); backdrop-filter: blur(var(--glass)); }
}

@keyframes glass-out {
  from { -webkit-backdrop-filter: blur(var(--glass)); backdrop-filter: blur(var(--glass)); }
  to { -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0); }
}

/* A drop at the opener, taller before wider, its corners settling to the sheet's 20 px as it
   fills the area, its text fading in (no pictures on a sheet, so opacity is allowed):
   WORDS_OPEN_MS and FEEDBACK_OPEN_MS, and back into the opener, WORDS_CLOSE_MS and
   FEEDBACK_CLOSE_MS. */
.sheet.is-opening,
.sheet.is-closing {
  pointer-events: none;
  will-change: transform;
}

.sheet.is-opening {
  animation: sheet-open 350ms ease-out both;
}

.sheet.is-closing {
  animation: sheet-close 250ms ease-in both;
}

@keyframes sheet-open {
  0% { transform: scale(0.06, 0.06); border-radius: 28px; opacity: 0; }
  60% { transform: scale(0.6, 0.9); border-radius: 20px; opacity: 1; }
  100% { transform: scale(1, 1); border-radius: 20px; opacity: 1; }
}

@keyframes sheet-close {
  0% { transform: scale(1, 1); border-radius: 20px; opacity: 1; }
  40% { transform: scale(0.6, 0.9); border-radius: 20px; opacity: 1; }
  100% { transform: scale(0.06, 0.06); border-radius: 28px; opacity: 0; }
}

/* The title bar of every sheet: 40 px, darker than the sheet with a darker line under it, the
   title centred, the cross at the right; the back chevron at the left only where a view has a
   parent, otherwise hidden in its place (is-off) so the title stays centred. Its fill reaches the
   sheet's outer edge, rounded by the sheet's corners; the light edge starts under it (.sheet::after),
   so nothing lighter lies above or beside it (CR-66 round 2). The 44 px buttons stand 2 px over the
   row above and below; they have no fill. */
.sheet-bar {
  flex: none;
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  align-items: center;
  height: 40px;
  margin: 0;
  padding: 0 3px;
  background: var(--sheet-bar);
  border-bottom: 1px solid var(--sheet-bar-line);
  overflow: visible;
}

.sheet-title {
  grid-column: 2;
  min-width: 0;
  margin: 0;
  color: var(--sheet-text);
  font-size: 16px;
  font-weight: 700;
  line-height: 40px;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sheet-back,
.sheet-close {
  position: relative;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
}

.sheet-back {
  grid-column: 1;
}

.sheet-close {
  grid-column: 3;
}

.sheet-back::before,
.sheet-close::before {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--sheet-text);
}

.sheet-back::before {
  -webkit-mask: url(icons/arrow-back.svg) center / 24px no-repeat;
  mask: url(icons/arrow-back.svg) center / 24px no-repeat;
}

.sheet-close::before {
  -webkit-mask: url(icons/clear.svg) center / 22px no-repeat;
  mask: url(icons/clear.svg) center / 22px no-repeat;
}

.sheet-back:active,
.sheet-close:active {
  background: var(--sheet-line);
}

.sheet-back.is-off,
.sheet-close.is-off {
  visibility: hidden;
}

.sheet-back:focus-visible,
.sheet-close:focus-visible {
  outline: 2px solid var(--sheet-accent);
  outline-offset: -2px;
}

/* A section on a sheet: a divider line before it, then a lighter plate heading the group, the
   caption bold at the left (the centred text is the window's title alone); the group right under
   the plate keeps only its bottom corners. */
.sheet-divider {
  height: 1px;
  margin: 12px 0;
  border: 0;
  background: var(--sheet-line);
}

.sheet-caption {
  height: 32px;
  margin: 0;
  padding: 0 16px;
  border-radius: 12px 12px 0 0;
  background: var(--sheet-plate);
  color: var(--sheet-text);
  font-size: 14px;
  font-weight: 700;
  line-height: 32px;
}

.sheet-caption + .feedback-group {
  border-radius: 0 0 14px 14px;
}

/* A list's group (the words list, My reports): the list tier, quieter than the buttons' surface;
   its rows' primary text in the rows' text tier. */
.sheet-group {
  background: var(--sheet-surface-low);
}

/* A text field sunk into the sheet, one rule for every sheet's field (the words search, the
   feedback comment): darker than the sheet, a light border all round, brighter on focus, no
   outline, no inner line. */
.sheet-field {
  border: 1px solid var(--sheet-field-edge);
  border-radius: 12px;
  background: var(--sheet-field);
  background-clip: padding-box;
  box-shadow: none;
  color: var(--sheet-text);
  caret-color: var(--sheet-text);
  outline: none;
}

.sheet-field::placeholder {
  color: var(--sheet-muted);
}

.sheet-field:focus,
.sheet-field:focus-visible {
  border-color: var(--sheet-field-focus);
  outline: none;
}

/* A sheet's button (the feedback sheet's Send and a report's actions, the goal card's Another
   round): 44 px, a 2 px border and text in the learned green on the buttons' surface; the feedback
   report's buttons change the colour. */
.sheet-button {
  height: 44px;
  padding: 0 12px;
  border: 2px solid var(--sheet-learned);
  border-radius: 12px;
  background: var(--sheet-surface);
  color: var(--sheet-learned);
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  white-space: nowrap;
}

.sheet-button:disabled {
  border-color: color-mix(in srgb, var(--sheet-muted) 50%, transparent);
  background: transparent;
  color: color-mix(in srgb, var(--sheet-muted) 50%, transparent);
  cursor: default;
}

/* The sheet's footer slot, under the scrolling body: what must stay reachable however long the
   list is (the feedback sheet's Archive card). */
.sheet-footer {
  flex: none;
  padding: 8px 16px 16px;
}

/* The sheets' link card (the feedback sheet's Archive card, the home panel's Acknowledgment): not a
   row of a group but a card of its own in the footer, 8 px under the group or pinned at the bottom
   when the list overflows; a bluer fill, the text and the icons in the lightened accent. Each
   screen gives the icon its mask. */
.sheet-link {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 48px;
  margin: 0;
  padding: 10px 14px;
  border: 0;
  border-radius: 14px;
  background: var(--sheet-link);
  color: var(--sheet-accent);
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.sheet-link:active {
  background: color-mix(in srgb, black 12%, var(--sheet-link));
}

.sheet-link:focus-visible {
  outline: 2px solid var(--sheet-accent);
  outline-offset: -2px;
}

.sheet-link-title {
  flex: 1 1 auto;
  min-width: 0;
}

.sheet-link-icon,
.sheet-chevron {
  flex: none;
  width: 22px;
  height: 22px;
  -webkit-mask: center / contain no-repeat;
  mask: center / contain no-repeat;
}

.sheet-link .sheet-link-icon,
.sheet-link .sheet-chevron {
  background-color: var(--sheet-accent);
}

.sheet-chevron {
  -webkit-mask-image: url(icons/chevron-right.svg);
  mask-image: url(icons/chevron-right.svg);
}

.sheet-chevron[hidden] {
  display: none;
}

/* The light sheets (CR-97 round 5, DESIGN.md "Colour"): the acknowledgment and the install sheet,
   long text that reads as dark on light. The body and the footer on --sheet-light-bg with the cards'
   text (CR-100 round 2: the cards' white was harsh; CR-97 round 6: the page colour read dull). The
   bar in --sheet-surface, lighter than the blurred dark home (CR-100 round 2: the sheets' own bar
   merged with it), with no line under it; the sheets' light edge goes round the bar's top and sides
   only, so the window has a silhouette against the dark and nothing lies on the light body. The
   box, shadow, mechanic and motion are the sheets'. */
.sheet.sheet-light {
  background: var(--sheet-light-bg);
}

.sheet.sheet-light::after {
  inset: 0 0 auto;
  height: 40px;
  border: 1px solid var(--sheet-edge);
  border-bottom: 0;
  border-radius: 20px 20px 0 0;
}

.sheet-light .sheet-bar {
  background: var(--sheet-surface);
  border-bottom: 0;
}

/* A sheet's checkbox (the words rows, the feedback's Word form): the input is there for the label
   and the keyboard, the box is drawn. */
.sheet-check {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.sheet-box {
  flex: none;
  position: relative;
  width: 22px;
  height: 22px;
  border: 2px solid color-mix(in srgb, var(--sheet-muted) 60%, transparent);
  border-radius: 6px;
  overflow: visible;
}

/* The box never fills: checked, the New card's Skip mark (Andrey's brushed check) lies over it
   in the learned green, 26 px wide by the shape's aspect, its centre 2 px right of and 2 px above
   the box's, spilling over the box's right and top edges like a tick put down by hand. */
.sheet-check:checked + .sheet-box {
  border-color: color-mix(in srgb, var(--sheet-muted) 60%, transparent);
  background: transparent;
}

.sheet-check:checked + .sheet-box::after {
  content: "";
  position: absolute;
  width: 26px;
  height: 22.4px;
  left: calc(50% - 13px + 2px);
  top: calc(50% - 11.2px - 2px);
  background-color: var(--flower-10);
  -webkit-mask: url(icons/check-brush.svg) center / contain no-repeat;
  mask: url(icons/check-brush.svg) center / contain no-repeat;
}

.sheet-check:focus-visible + .sheet-box {
  outline: 2px solid var(--sheet-accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .sheet.is-opening,
  .sheet.is-closing,
  .sheet-scrim.is-opening,
  .sheet-scrim.is-closing {
    animation: none;
  }
}

/* The hint stickers (CR-109, SPEC 5.34, MOTION.md section 15): a sticky note over the screen,
   z-index 10 with its glass, over the sheets (9) and under the modals (11).
   The glass: the sheets' glass with a lighter blur (--glass-hint), from 8 px above its top edge
   (--hint-top, the top bar's bottom edge or the status-bar plate's on the home screen, set by
   hint.js on every opening) to the screen's bottom, the same feathered top; the bar above it stays
   sharp and live. A tap on it closes the sticker. */
.hint-glass {
  position: fixed;
  top: calc(var(--hint-top, 0px) - 8px);
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
  background: transparent;
  -webkit-backdrop-filter: blur(var(--glass-hint));
  backdrop-filter: blur(var(--glass-hint));
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 8px);
  mask-image: linear-gradient(to bottom, transparent 0, black 8px);
}

/* The blur grows with the note and clears with it: HINT_OPEN_MS and HINT_CLOSE_MS in hint.js. */
.hint-glass.is-opening {
  animation: hint-glass-in 350ms ease-out both;
}

.hint-glass.is-closing {
  animation: hint-glass-out 250ms ease-in both;
}

@keyframes hint-glass-in {
  from { -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0); }
  to { -webkit-backdrop-filter: blur(var(--glass-hint)); backdrop-filter: blur(var(--glass-hint)); }
}

@keyframes hint-glass-out {
  from { -webkit-backdrop-filter: blur(var(--glass-hint)); backdrop-filter: blur(var(--glass-hint)); }
  to { -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0); }
}

/* The sticker's box: centred on the screen (round 1; centred under the top bar it sat low), by
   top and left at 50% and the individual translate, which composes with the motion's transform as
   the goal card's does; as tall as its content, at most 320 px wide (40 px less than the screen on
   a narrow one). It carries the motion only, about the "?" it came out of (--hint-origin-x and -y,
   the button's centre in its box, set by hint.js); the paper inside carries the look and its turn. */
.hint-sticker {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 10;
  width: min(320px, 100% - 40px);
  /* --hint-shift: off the element the sticker explains, when that lies under its centre (hint.js). */
  translate: -50% calc(-50% + var(--hint-shift, 0px));
  transform-origin: var(--hint-origin-x, 50%) var(--hint-origin-y, 50%);
}

/* Out of the "?" with a turn, the card entry's spirit (MOTION.md 1): from 0.1 at the button's
   centre turned -25 degrees to its place (the paper's own degree added) over HINT_OPEN_MS, ease-out;
   back into it over HINT_CLOSE_MS, ease-in. The paper holds no picture, so it may fade in. */
.hint-sticker.is-opening,
.hint-sticker.is-closing {
  pointer-events: none;
  will-change: transform;
}

.hint-sticker.is-opening {
  animation: hint-open 350ms ease-out both;
}

.hint-sticker.is-closing {
  animation: hint-close 250ms ease-in both;
}

@keyframes hint-open {
  from { transform: scale(0.1) rotate(-26deg); opacity: 0; }
  30% { opacity: 1; }
  to { transform: none; opacity: 1; }
}

@keyframes hint-close {
  from { transform: none; opacity: 1; }
  70% { opacity: 1; }
  to { transform: scale(0.1) rotate(-26deg); opacity: 0; }
}

/* The note: the cards' text on the lit yellow, 12 px corners, turned a degree, the sheets' shadow. */
.hint-paper {
  position: relative;
  padding: 24px 20px 20px;
  border-radius: 12px;
  background: var(--hint-bg);
  color: var(--text);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
  rotate: 1deg;
  text-align: left;
}

/* A strip of tape over the top edge, the translucent white of a real one. It sticks on from its
   left edge to its right once the note has landed (round 1: hint.js adds is-stuck at the open's
   end; 200 ms ease-out). */
.hint-tape {
  position: absolute;
  top: -11px;
  left: 50%;
  width: 96px;
  height: 22px;
  margin-left: -48px;
  background: rgba(255, 255, 255, 0.45);
  rotate: -3deg;
  transform: scaleX(0);
  transform-origin: left center;
}

.hint-sticker.is-stuck .hint-tape {
  animation: hint-tape-on 200ms ease-out both;
}

@keyframes hint-tape-on {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* The sticker's title over its text, a blank line (a line of the text) under it (round 5). */
.hint-title {
  margin: 0 0 24px;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
}

/* The labels' own line breaks, kept: a paragraph each (hint.js), 6 px apart. 18 px since round 5
   (17 read small on the phone). */
.hint-text {
  font-size: 18px;
  line-height: 1.4;
  white-space: pre-line;
}

.hint-text > p {
  margin: 0;
}

.hint-text > p + p {
  margin-top: 6px;
}

.hint-note {
  margin: 10px 0 0;
  color: var(--muted);
  font-size: 16px;
  font-style: italic;
  line-height: 1.45;
  white-space: pre-line;
}

/* The tour: one row per button, its icon at the left in the top bar's colour. */
.hint-tour {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
  white-space: normal;
}

.hint-tour-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.hint-tour-icon {
  flex: none;
  width: 22px;
  height: 22px;
  color: var(--pocket-line);
}

.hint-tour-icon svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* The gear inline in the words in progress' text, in the text's colour. */
.hint-gear {
  width: 18px;
  height: 18px;
  vertical-align: middle;
}

/* The bug's badge stays off, as in the bar at rest. */
.hint-tour-icon .badge {
  display: none;
}

/* A pill in the note's own palette (round 1: the blue primary pill fought the yellow), centred
   24 px under the text. */
.hint-button {
  display: block;
  min-width: 160px;
  min-height: 44px;
  margin: 24px auto 0;
  padding: 0 24px;
  border: 0;
  border-radius: 999px;
  background: var(--hint-button);
  color: var(--text);
  font-size: 16px;
  font-weight: 600;
}

.hint-button:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .hint-sticker.is-opening,
  .hint-sticker.is-closing,
  .hint-glass.is-opening,
  .hint-glass.is-closing,
  .hint-sticker.is-stuck .hint-tape {
    animation: none;
  }

  .hint-tape {
    transform: none;
  }
}

/* Modals */

/* z-index 11 since CR-109: over the hint stickers (10), so the update modal covers one. */
.modal {
  position: fixed;
  inset: 0;
  z-index: 11;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
}

.modal-box {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(92vw, 360px);
  max-height: 70vh;
  border-radius: 16px;
  background: var(--surface);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.modal-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* The height a close button gives, so heads without one line up. */
  min-height: 53px;
  padding: 4px 4px 4px 16px;
  border-bottom: 1px solid var(--line);
}

.modal-title {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 700;
}

.remove-word {
  margin: 0;
  padding: 20px 16px 8px;
  font-size: 1.4rem;
  font-weight: 700;
  text-align: center;
  overflow-wrap: anywhere;
}

/* Kept in the layout while hidden, so the box does not grow when it shows. */
.remove-error {
  margin: 0;
  padding: 4px 16px 0;
  font-size: 14px;
  color: var(--bad);
  text-align: center;
  visibility: hidden;
}

.remove-error.is-on {
  visibility: visible;
}

/* The tester popup (SPEC 4.11, 5.25): over the feedback sheet, the shell's light modal; the
   error line is reserved, so the box does not grow when it shows. */
.tester-text {
  margin: 0;
  padding: 16px 16px 12px;
  font-size: 15px;
  line-height: 1.45;
}

.tester-password {
  display: block;
  width: calc(100% - 32px);
  height: 44px;
  margin: 0 16px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #ffffff;
  color: var(--text);
  font: inherit;
  /* 16 px: below it iOS zooms the page into the field. */
  font-size: 16px;
  user-select: text;
  -webkit-user-select: text;
}

.tester-password:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.tester-error {
  min-height: 1lh;
  margin: 0;
  padding: 6px 16px 0;
  font-size: 14px;
  color: var(--bad);
  visibility: hidden;
}

.tester-error.is-on {
  visibility: visible;
}

/* The update modal has no close button; the title may take the whole head. */
.update-head {
  padding-right: 16px;
}

.update-line {
  margin: 0;
  padding: 16px 16px 0;
  font-size: 15px;
  line-height: 1.45;
}

/* The acknowledgment (SPEC 5.20, CR-97 round 2): a top sheet over the home screen, nearly the
   whole screen: its top 10 px under the status-bar plate (sheet.js's --sheet-top), its bottom 10 px
   above the safe-area inset. The text scrolls between the bar and the footer, so Agreed is always
   in view. The install sheet (5.32) has the same box. */
.ack.sheet-top,
.install.sheet-top {
  height: calc(100% - var(--sheet-top, 10px) - 10px - env(safe-area-inset-bottom));
  max-height: calc(100% - var(--sheet-top, 10px) - 10px - env(safe-area-inset-bottom));
}

.ack-body,
.install-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px 16px 16px;
  overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
}

.ack-text,
.ack-list {
  flex: none;
  margin: 0;
  color: var(--text);
  font-size: 15px;
  line-height: 1.45;
  text-align: left;
}

.ack-heading {
  margin-bottom: -8px;
  color: var(--text);
  font-weight: 600;
}

.ack-list {
  padding: 0 0 0 20px;
}

.ack-link {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  overflow-wrap: anywhere;
}

/* Agreed: the app's primary pill (the sign-in button's, the update modal's Restart), centred and
   sized by its text; its colours and its disabled look are shared with .modal-button.is-primary. */
.ack-footer {
  text-align: center;
}

.ack-agree {
  display: inline-block;
  min-width: 200px;
  width: auto;
  min-height: 44px;
  padding: 0 24px;
  border: 0;
  border-radius: 999px;
  font-size: 16px;
  font-weight: 600;
}

/* Under the button, its line kept while hidden so the button never moves. */
.ack-error {
  margin: 8px 0 0;
  color: var(--bad);
  font-size: 14px;
  line-height: 1.3;
  text-align: center;
  visibility: hidden;
}

.ack-error.is-on {
  visibility: visible;
}

/* The install sheet (SPEC 5.32, CR-100): the acknowledgment's text, then the platform's steps,
   each its semibold line over a picture tile with the pictures' corners and a hairline. The tile
   at most 280 px and hung under the step's text, after its number (round 1: at the body's width
   the pictures outweighed the lines). */
.install-text,
.install-after,
.install-steps {
  flex: none;
  margin: 0;
  color: var(--text);
  font-size: 15px;
  line-height: 1.45;
  text-align: left;
}

.install-steps {
  padding: 0;
  list-style: none;
}

.install-step {
  margin: 0 0 16px;
}

.install-step-line {
  margin: 0 0 8px;
  font-weight: 600;
}

.install-step-number {
  margin-right: 6px;
}

.install-step-picture {
  display: block;
  width: calc(100% - 28px);
  max-width: 280px;
  height: auto;
  margin-left: 28px;
  border: 1px solid var(--line);
  border-radius: 12px;
}

.install-after {
  color: var(--muted);
}

/* Continue in browser: the secondary action, a text button (installing is the system's). */
.install-footer {
  text-align: center;
}

.install-continue {
  min-height: 44px;
  padding: 0 16px;
  border: 0;
  background: transparent;
  color: var(--accent);
  font-size: 16px;
  font-weight: 600;
}

.modal-actions {
  flex: none;
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 16px;
}

.modal-button {
  min-height: 40px;
  padding: 0 16px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font-weight: 600;
}

/* The primary button's colours, one place: the modals' primary and the acknowledgment's Agreed
   (the sign-in button has them too, on a link). */
.modal-button.is-primary,
.ack-agree {
  background: var(--accent);
  color: var(--accent-text);
}

.modal-button:disabled,
.ack-agree:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Debug panel (SPEC 5.17): with the debug flag on a wide viewport, a column at the left of the
   page; the screens keep their layout and centring to its right. Testing only, so none of the
   card look applies. */
.debug {
  display: none;
}

@media (min-width: 1024px) {
  html.has-debug body {
    padding-left: calc(320px + env(safe-area-inset-left));
  }

  html.has-debug .debug:not([hidden]) {
    display: block;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: 320px;
    overflow: auto;
    padding: 8px;
    background: var(--bg);
    border-right: 1px solid var(--line);
    color: var(--text);
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 12px;
    line-height: 1.35;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    user-select: text;
    -webkit-user-select: text;
  }
}
