/*
  PURPOSE: Visual design for the LINC Keyboarding Practice prototype.
  WHY: Uses large touch-friendly controls, simple spacing, and low visual clutter for CLB 1L/2L learners.
*/

@font-face {
  font-family: "Inter Local";
  src: url("fonts/Inter-latin.woff2?v=d2515ec5f11d") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Andika Local";
  src: url("fonts/andika-latin-400-normal.woff2?v=d2515ec5f11d") format("woff2");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Andika Local";
  src: url("fonts/andika-latin-700-normal.woff2?v=d2515ec5f11d") format("woff2");
  font-style: normal;
  font-weight: 700;
  font-display: swap;
}

@font-face {
  font-family: "Comic Neue Local";
  src: url("fonts/comic-neue-latin-400-normal.woff2?v=d2515ec5f11d") format("woff2");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Comic Neue Local";
  src: url("fonts/comic-neue-latin-700-normal.woff2?v=d2515ec5f11d") format("woff2");
  font-style: normal;
  font-weight: 700;
  font-display: swap;
}

@font-face {
  font-family: "Atkinson Hyperlegible Local";
  src: url("fonts/atkinson-hyperlegible-latin-400-normal.woff2?v=d2515ec5f11d") format("woff2");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Atkinson Hyperlegible Local";
  src: url("fonts/atkinson-hyperlegible-latin-700-normal.woff2?v=d2515ec5f11d") format("woff2");
  font-style: normal;
  font-weight: 700;
  font-display: swap;
}

:root {
  --page-bg: #f4f7fb;
  --card-bg: #ffffff;
  --text-main: #172033;
  /* Darkened from #465061, which read 6.86:1 on the settings drawer — just under
     the 7:1 AAA target. This clears it on every surface with room to spare. */
  --text-muted: #424c5b;
  --border: #d8e0ec;
  --field-border: #7387ab;
  --primary: #2e39a6;
  /* One hint colour for every marker, in every activity. Deliberately neutral:
     yellow means "wrong", green means "correct", blue means "press me" — a
     marker that only says "look here" must not borrow any of those. The solid
     edge carries the signal; the fill stays pale so text over it stays legible. */
  --hint-bg: #dddfe2;
  --hint-border: #424c5b;
  --primary-soft: #e9ebf7;
  --space-cue: #5679c4;
  --focus-blue: #4351c9;
  --keyboard-key: #eef1f5;
  --success-bg: #d7efdc;
  --success-text: #10532c;
  --success-border: #3f7f57;
  --warning-bg: #fff3d9;
  --warning-text: #5f3700;
  --warning-border: #9a6200;
  --mode-subtitle: #4a4f58;
  --mode-tile-tint: #e9ebf7;
  --sample-border: #c3c9ee;
  /* The chip's own border, not --sample-border: the chip sits on white with a
     white face, so its outline is the only thing drawing the box. At #c3c9ee it
     measured 1.63:1 and a lone "." read as a speck. This is --text-muted rather
     than a new colour: 8.69:1, well past the 3:1 a box outline needs, and
     neutral so the chip stays paper next to the keycap's blue-grey key. The
     boxes that use --sample-border sit on a tinted fill and do not have this
     problem. */
  --chip-border: #424c5b;
  /* These colour the named word in a task instruction, so they are read as text
     and must clear the 7:1 target, not just AA. Teal was 6.09:1 and green
     6.32:1 on white; darkened to 8.14 and 8.25. Purple already cleared at 9.11. */
  --accent-keyboard: #0c5768;
  --accent-typing: #31581f;
  --accent-fix: #5e3a6e;
  --accent: var(--primary);
  --surface-tint: #fbfcfe;
  /* The arrow that points at the blinking cursor in a demo. Its own token
     because it is a teaching cue, not the semantic success green. */
  --cursor-arrow: #1f9d4d;
  /* Greyed sample text in the text-cursor demo, so the green arrow and the
     blinking cursor carry the eye. Darkened from #8a8a8a, which read 3.45:1 —
     enough for AA at this size, short of the 4.5:1 large text needs for AAA.
     This clears it at 4.54:1 and still reads as muted grey. */
  --demo-text: #767676;
  --demo-ink: #222;
  --scrim: rgba(23, 32, 51, 0.58);
  --focus-glow: rgba(28, 63, 163, 0.14);
  --shadow: 0 10px 30px rgba(20, 34, 61, 0.08);
  --radius-lg: 22px;
  --radius-md: 16px;
  --radius-pill: 999px;
}

/* THE DISPLAY SCALE. Classroom interactive panels run their host PC at 250-300%
   Windows scaling, which reports a SHORT css viewport, not a large one: the
   measured board is 1280x585 where a desktop is 1562x911. Width is only 18%
   off; height is 36% off, and that is what makes the card feel oversized —
   the layout is drawn for ~911px of height and gets 585px.

   So the scale is keyed on viewport HEIGHT, and every size that decides how
   much room the layout takes is in rem below, which means this one line moves
   all of them together. Fitted to the two measured devices: 911px tall
   computes to 16.00px (the 100% ceiling clamps, so desktop is unchanged) and
   585px tall computes to 10.73px — 67% of today, matching what an instructor
   currently gets by setting browser zoom to 67% by hand.

   The gate cannot catch a desktop or a tablet. A panel is driven by a PC with
   a mouse AND a touch surface, so it is the only thing reporting both pointer
   types: a plain desktop has no coarse pointer, an iPad has no fine one, and
   phones and portrait tablets are under 1000px. Percentages rather than px so
   a learner's own browser font-size setting still applies at both ends.

   Not zoom/transform: those break pointer position and text sharpness on a
   touch panel, which is the one device this exists for. */
@media (min-width: 1000px) and (any-pointer: coarse) and (any-pointer: fine) {
  html {
    font-size: clamp(67%, calc(1.62vh + 1.25px), 100%);
  }
}

* {
  box-sizing: border-box;
}

/* Opening Settings makes the page taller than the viewport, and the scrollbar
   that appears used to shove the header buttons and the card 8px left. Reserve
   the gutter so nothing moves. */
html {
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--page-bg);
  color: var(--text-main);
  font-family: "Inter", "Inter Local", sans-serif;
}

body[data-activity="start"] {
  --accent: var(--accent-keyboard);
  --page-bg: color-mix(in srgb, var(--accent-keyboard) 6%, #ffffff);
}

body[data-activity="more"] {
  --accent: var(--accent-typing);
  --page-bg: color-mix(in srgb, var(--accent-typing) 6%, #ffffff);
}

body[data-activity="fix"] {
  --accent: var(--accent-fix);
  --page-bg: color-mix(in srgb, var(--accent-fix) 6%, #ffffff);
}

/* Pointer images. Named --pointer-*, NOT --cursor-*: --cursor-arrow was already
   taken, above, by the green teaching arrow in the Text Cursor demo, and a
   second meaning on that name silently turned that arrow black.

   The arrow and the I-beam are built in script.js (applyCursorSettings) because
   size and colour are learner choices and a data URI cannot be tinted by a CSS
   variable. The hand is a real Windows large-link bitmap, so it cannot be tinted
   or resized at run time either — instead each colour is a pre-tinted copy, and
   Normal size falls back to the system hand rather than shrinking a bitmap.
   The trailing keyword is the fallback if an image fails; the numbers before it
   are the hotspot, the pixel that actually clicks.
   Touch screens have no cursor, so none of this reaches a finger. */
:root {
  --pointer-hand-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAD8ElEQVR42u2be0gUQRzHD4QgCIThF1aIguRIhiGKYKUpWWYYhiSKUSIVhvRHYgmBdWEvS0IMS3pYhmKYYmWIFokRSg9LtIdiCJIYkpiiYYkhTHyXXTk8Tw29273d/cLAsT/uxvncjPN7ncWyRDHi2Yx4LyP+nRHPtRhJjHjxgbRjoq+vXwwO/hBZ2acFI15ilMVHRMckiunpaWGrlNQMQNhvBACV1TVPxWy9efsBAFqMAKCrp6fXDsDo6BgAjBgBwIhwoLXem4QRAIw5AuDrF2oCMAGYAEwAJgATgAnAwAAYcU95nGLEX8sDrz2NAiDIa91GkXfuqmh73yENvMYz2GZ91mpGfIXeAJSU3qu0s+GZEjIjakTg5B8QroApZcRDdQGAvDaIyclJOxuewcaIxwcEbpGiR0XlFdVSHMGIB7o9AIwFdkj9s+fNdrbbdypgK5bnSGfE3zHieDbGiOcz4j66AIAtPzuZAo2P/1IWmxAStkO0t3+ceV5YdFPZPSvdHkBgUORCO6TxRdMrO1vumXzYst0eQFBw1EIApufaIcg/IgmrewCO3g8o8o2xwpAAbB0tE4AJQKMAvH2DjQ3Azz9M4A53pLDwXSI2LlnM9/7gkO3uCwD1ApTKHKmz84vo6v7q0I54ofJBrWsBMOIHGfEyBCooey0FgLO1rAAY8TVY9NHMHIk66O+OT8UEHYx4hhEAFF8pKLab5NPnbpG4Lx0TVTHiUboF4CgoUVT7qF7y2xnxAl0CwH/c+QBAExO/xaEjWZi0nBHfrLcjUIVveTGqevhYuuMZ8Rw9AYjCMWh+2bqoyb/1D4iY2CShGwAyhER8aG9v36L+gKmpv+L8xUKhGwAyhOPw3tDgoHU5zRNkxC8kJKZJ37AhAcgQitDtpWUhSepsP7/k+o27mlw80uqM+B9nA1iP7OtcaWu1hVuIEe9zRbQXj7M2X9SmhpApRsbYVSFvJuLy4Z8jmgFw6fI1ALC6Mu7PQ3SolZtBjlQjLK4UbgaEy2prYGBQqQx5qJEBKkFpSk2hvI4eA7VSYD6g39DQpMrikWKTAzH1coGMeBzK12rcDCdOnhWa+D0CbgYkSIaGhl22+JbWNm2Uw2wgWBESI1HiCscHHSSMeIrWGiQKtkXvdepOQE/A1sg9UgJGq10iVhwHpYlhOYUyOMJzdIVovVUmHecTHtpyOUuIQeSQN9viDpLb3IpQ8rJtevpfIRWflHxY6h9CLGJxNyltb0iqwF9YzI7AOX9S1yjw67OFijHuBCKOEa+D44LiCjy4svtVMwPHBUkX2GTXtgZ5SYvexIivYsR3yi2yt2yGFd+0bPOwmDJlypQpU6roH4GcyVqRfhvyAAAAAElFTkSuQmCC") 17 0, pointer;
  /* Same Windows bitmap, its white fill remapped to the swatch colour with the
     black outline and the anti-aliased edge left intact (scratchpad/tint_hand.py).
     Files, not base64: two more inline blobs would add ~4KB to every page load
     for something most learners never switch to. The service worker caches
     /images/ at runtime, so they still work offline after first use. */
  /* The one pointer colour with a value: script.js reads this token to tint the
     generated arrow and I-beam, and the swatch below paints it directly, so the
     swatch cannot drift from the pointer it advertises. */
  --cursor-yellow: #f2c200;
  --pointer-hand-yellow: url("images/cursor-hand-yellow.png") 17 0, pointer;
  /* Normal size still needs a real file: CSS cannot resize a bitmap cursor.
     Half the 64px one, so the hotspot halves with it. */
  --pointer-hand-yellow-small: url("images/cursor-hand-yellow-small.png") 8 0, pointer;
  --pointer-arrow: auto;
  --pointer-hand: pointer;
  --pointer-text: text;
}

body {
  cursor: var(--pointer-arrow);
}

/* cursor inherits, so the arrow on body would otherwise replace the I-beam the
   typing box gets for free — and Text Cursor mode is entirely about seeing
   exactly which two letters you are clicking between. */
.typing-area {
  cursor: var(--pointer-text);
}

button {
  font: inherit;
}

button:hover:not(:disabled):not([aria-disabled="true"]) {
  filter: brightness(0.97);
}

button:active:not(:disabled):not([aria-disabled="true"]) {
  filter: brightness(0.9);
}

/* The landing grid uses the wider measure; the activity view is pinned back to
   1100px below, since long lines of typed text get harder to read, not easier. */
.app-shell {
  width: min(78.125rem, calc(100% - 2rem));
  margin: 0 auto;
  padding: 1.5rem 0 2.25rem;
}

.intro-view {
  position: relative;
  min-height: calc(100vh - 3.75rem);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

/* The logo sits on the same line as the fixed Help button (centred on its 48px
   height) rather than floating in a tall band below it, so the credits logo
   stays on screen without scrolling. */
/* Scoped to .intro-view so it outranks the later .app-header block, which
   defines its own margin-bottom at the same specificity. */
.intro-view .intro-header {
  justify-content: center;
  min-height: auto;
  /* auto here and on .intro-footer splits any leftover room equally between the
     top and bottom gaps; the card's own margins are the floor for both. */
  margin-bottom: auto;
  padding: 0 24px;
  text-align: center;
}

.intro-heading {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 16px;
  width: 100%;
  margin: 0 auto;
}

.intro-heading h1 {
  margin: 0;
}

.intro-brand-logo {
  display: block;
  width: min(160px, 50vw);
  height: auto;
}

.help-button {
  position: fixed;
  top: max(16px, env(safe-area-inset-top, 0px));
  right: max(16px, env(safe-area-inset-right, 0px));
  z-index: 1000;
}

.home-button {
  position: fixed;
  top: max(16px, env(safe-area-inset-top, 0px));
  left: max(16px, env(safe-area-inset-left, 0px));
  z-index: 1000;
}

/* The three coloured groups already read as blocks, so the landing card drops
   its white fill and padding and sits straight on the page canvas. The activity
   view still needs the shared .mode-card/.task-card treatment. */
.intro-view .mode-card {
  /* width is explicit because the auto side margins would otherwise stop this
     flex item stretching and collapse it to its content width. */
  width: 100%;
  margin-left: auto;
  margin-right: auto;
  /* No top margin: the instruction row's auto margin above supplies that gap. */
  margin-top: 0;
  margin-bottom: clamp(1rem, calc(10vh - 3rem), 2.5rem);
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
}

/* The legend is hidden until a mode is completed, so the logo keeps itself on
   the right with margin-left rather than relying on space-between.
   margin-top: auto drops the whole strip to the bottom of any leftover room;
   the card's own margin-bottom supplies the minimum gap when there is none. */
.intro-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  width: 100%;
  /* -24px bottom pulls the strip into most of .app-shell's 36px bottom padding,
     which is shared with the activity view and so must not be reduced itself. */
  margin: auto auto -24px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.footer-links {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 0.125rem;
}

.footer-link {
  min-height: 3rem;
  border: 0;
  border-radius: 0.75rem;
  background: transparent;
  padding: 0.5rem;
  color: var(--primary);
  font: inherit;
  font-weight: 700;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: var(--pointer-hand);
}

.footer-link:hover {
  background: var(--primary-soft);
}

.footer-link:active {
  background: color-mix(in srgb, var(--primary) 16%, var(--card-bg));
}

.footer-link-separator {
  color: var(--text-muted);
}

.linc-suite-logo-mark {
  width: min(120px, 40vw);
  margin-left: auto;
}

.linc-suite-logo {
  display: block;
  width: 100%;
  height: auto;
}

.activity-group-title:focus-visible {
  outline: 4px solid var(--focus-blue);
  outline-offset: 3px;
  box-shadow: 0 0 0 2px var(--card-bg);
}

.activity-group-title:focus:not(:focus-visible) {
  outline: none;
}

/* The landing page's only spoken cue says what to do now; Help explains how the
   page works, so the two do not overlap. */
.intro-instruction-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 12px;
  /* A sibling of the header rather than part of the tile card, so this auto and
     the header's own bottom auto take equal shares of the leftover room — the
     row stays centred between the logo and the first tile row at any height.
     The padding is the floor: on a page too tall to have leftover room the autos
     collapse to zero, and it keeps equal space on both sides regardless. */
  margin: 0 0 auto;
  padding-block: 14px;
}

.intro-instruction-icon {
  flex: 0 0 auto;
  width: auto;
  height: 34px;
}

/* The row gap pulls the icon against the text; the audio button's extra margin
   keeps it clearly separate from the sentence. */
.intro-instruction-row .audio-button {
  margin-left: 0.5rem;
}

.intro-instruction {
  margin: 0;
  font-size: 1.375rem;
  line-height: 1.25;
}

.activity-groups {
  display: flex;
  flex-direction: column;
  gap: clamp(1.125rem, calc(10vh - 2.625rem), 2.875rem);
}

.activity-group[data-activity="start"] {
  --tile-accent: var(--accent-keyboard);
}

.activity-group[data-activity="more"] {
  --tile-accent: var(--accent-typing);
}

.activity-group[data-activity="fix"] {
  --tile-accent: var(--accent-fix);
}

.activity-group-title {
  margin: 0 0 12px;
  color: var(--tile-accent);
  font-size: 1.375rem;
  line-height: 1.25;
}

/* Tiles hold their own width and the leftover room becomes gap. All three grids
   declare the same four tracks, so the 3-tile Fix row still lines up with the
   rows above it. */
.activity-grid {
  display: grid;
  /* 230px, not less: below this "Capitals, Spaces, and Punctuation" wraps to a
     third line and the Fix row outgrows the other two. */
  grid-template-columns: repeat(4, minmax(0, 14.375rem));
  justify-content: space-between;
  gap: 1rem;
}

.activity-tile {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr) 28px;
  grid-template-rows: auto 1fr;
  align-items: start;
  column-gap: 0.625rem;
  row-gap: 0.5rem;
  width: 100%;
  /* Must stay at or above the Fix row's natural height, whose 3-line title is
     the tallest, or that row alone grows and the rows stop matching. Scales
     with the type below it so the relationship holds on short viewports. */
  min-height: clamp(7.25rem, calc(12.2vh + 1.875rem), 8.625rem);
  border-radius: 14px;
  border: 2px solid color-mix(in srgb, var(--tile-accent) 75%, #ffffff);
  background: color-mix(in srgb, var(--tile-accent) 10%, #ffffff);
  padding: 0.875rem;
  color: var(--text-main);
  cursor: var(--pointer-hand);
  text-align: left;
}

.activity-tile-number {
  grid-column: 1;
  grid-row: 1;
  color: var(--tile-accent);
  font-size: 2.5rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.activity-tile-title {
  grid-column: 2 / 4;
  grid-row: 1;
  align-self: start;
  min-width: 0;
  font-size: 1.125rem;
  font-weight: 700;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

/* The state reads as a word, not a bare symbol — the footer legend that used to
   decode ✓ and ↻ is gone, so each pill has to be legible on its own. */
.activity-tile-status {
  grid-column: 1 / 4;
  grid-row: 2;
  align-self: end;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: var(--radius-pill);
  border: 1px solid;
  padding: 3px 10px;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.2;
}

.activity-tile-status:empty {
  display: none;
}

@media (min-width: 1000px) {
  .activity-group {
    display: grid;
    grid-template-columns: 136px minmax(0, 1fr);
    align-items: start;
    gap: 20px;
  }

  .activity-group-title {
    margin: 14px 0 0;
  }
}

.activity-tile-status.is-complete {
  background: var(--success-bg);
  border-color: var(--success-border);
  color: var(--success-text);
}

.activity-tile-status.is-try-again {
  background: var(--keyboard-key);
  border-color: var(--field-border);
  color: var(--text-muted);
}

.activity-tile-status-symbol {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  font-size: 1rem;
  line-height: 1;
  letter-spacing: 0;
}

/* Free Practice takes the empty 4th cell of the Fix row. No number and no group
   tint — a white surface with the action-blue border marks it as the open-ended
   option rather than a twelfth numbered step. */
.free-practice-tile {
  grid-template-columns: minmax(0, 1fr);
  border-color: var(--primary);
  background: var(--card-bg);
}

/* Larger than the other tile titles because this tile has no number to anchor
   its top-left corner — the title takes that job instead. Still well below the
   2.5rem numbers, so it reads as different rather than louder. */
.free-practice-tile .activity-tile-title {
  grid-column: 1;
  grid-row: 1;
  font-size: 1.5rem;
}

/* Both of this tile's children sit in column 1, and .activity-tile sizes that
   column to max-content — which here is the whole unwrapped description, so on
   a 375px screen it ran 10px past the tile's own border. The number and
   status-pill columns are empty on this tile, so one shrinkable column replaces
   all three and the description wraps instead of overflowing. Compounded, not
   single, class: the 640px block restates .activity-tile's template later in
   the file and would otherwise win on source order. */
.activity-tile.free-practice-tile {
  grid-template-columns: minmax(0, 1fr);
}

.free-practice-tile-description {
  grid-column: 1;
  grid-row: 2;
  /* start, not end: it belongs to the title above it, not to the tile's floor
     the way the other tiles' status pills do. */
  align-self: start;
  font-size: 1rem;
  line-height: 1.25;
}

@media (hover: hover) and (pointer: fine) {
  .activity-tile:hover:not(:disabled) {
    background: color-mix(in srgb, var(--tile-accent) 18%, #ffffff);
  }

  .activity-tile.is-complete:hover:not(:disabled) {
    background: color-mix(in srgb, var(--tile-accent) 24%, #ffffff);
  }
}

.activity-tile.is-complete {
  background: color-mix(in srgb, var(--tile-accent) 17%, #ffffff);
}

@media (hover: none) and (pointer: coarse) {
  .activity-tile:hover:not(:active) {
    background: color-mix(in srgb, var(--tile-accent) 10%, #ffffff);
    filter: none;
  }

  .activity-tile.is-complete:hover:not(:active) {
    background: color-mix(in srgb, var(--tile-accent) 17%, #ffffff);
  }
}

.activity-tile:active:not(:disabled) {
  background: color-mix(in srgb, var(--tile-accent) 18%, #ffffff);
  box-shadow: inset 0 0 0 2px var(--tile-accent);
}

.activity-tile.is-complete:active:not(:disabled) {
  background: color-mix(in srgb, var(--tile-accent) 24%, #ffffff);
}

/* Free Practice sits inside the Fix grid, so it inherits that group's purple
   --tile-accent. These carry the extra .activity-tile class to outrank the
   generic tile states above by specificity rather than by source order. */
@media (hover: hover) and (pointer: fine) {
  .activity-tile.free-practice-tile:hover:not(:disabled) {
    background: color-mix(in srgb, var(--primary) 10%, #ffffff);
  }
}

@media (hover: none) and (pointer: coarse) {
  .activity-tile.free-practice-tile:hover:not(:active) {
    background: color-mix(in srgb, var(--primary) 6%, #ffffff);
  }
}

.activity-tile.free-practice-tile:active:not(:disabled) {
  background: color-mix(in srgb, var(--primary) 16%, #ffffff);
  box-shadow: inset 0 0 0 2px var(--primary);
}

.mode-icon-tile {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 14px;
  background: var(--mode-tile-tint);
}

.mode-icon-glyph--text {
  font-family: "Atkinson Hyperlegible Local", sans-serif;
  font-weight: 700;
  font-size: 1.375rem;
  color: var(--accent);
}

.mode-icon-glyph--bars {
  display: flex;
  flex-direction: column;
  gap: 5px;
  width: 30px;
}

.mode-icon-bar {
  height: 4px;
  border-radius: 3px;
  background: var(--accent);
}

.mode-icon-glyph--pencil {
  width: 26px;
  height: 26px;
  fill: var(--accent);
}

.mode-icon-img {
  width: 32px;
  height: 32px;
  display: block;
}

.mode-icon-badge {
  position: absolute;
  right: -4px;
  bottom: -4px;
  width: 20px;
  height: 20px;
}

.mode-icon-badge circle {
  fill: var(--accent);
}

.mode-icon-badge path {
  fill: none;
  stroke: var(--card-bg);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Home, Settings, Help and the logo all sit in normal flow and scroll with the
   card. They used to be position: fixed, which let the card slide underneath
   them; nothing is pinned now, so there is no band to reserve and nothing to
   scroll under. */
.activity-view {
  width: 100%;
  max-width: 68.75rem;
  margin: 0 auto;
  min-height: calc(100vh - 3.75rem);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 18px;
}

.activity-header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}

/* Keep Settings in the page flow. A sticky drawer covered the task card while
   the learner scrolled, making the content appear to disappear behind it. */
.activity-view.settings-open .settings-drawer {
  position: relative;
  top: auto;
}

.activity-brand {
  display: flex;
  align-items: center;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-left: auto;
}

.settings-control {
  position: relative;
  display: inline-flex;
}

/* Anchored to the control, not to a coordinate: Settings is fixed top-right on
   desktop and shrinks to an icon under 640px, so the arrow has to travel with
   it. Drawn in the hint colour, not amber: it appears on Show hint, so it means
   "look here", and amber is reserved for "not right yet". */
.settings-hint-arrow {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  width: 28px;
  height: 64px;
  transform: translateX(-50%);
  pointer-events: none;
  z-index: 5;
}

.settings-hint-arrow svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: none;
  stroke: var(--hint-border);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The Text Size and Font arrows sit inside the drawer, not on the fixed header
   button, so they only have the gap before the task card below to grow into.
   That gap is too short on mobile once the groups stack — shrink to fit. */
@media (max-width: 640px) {
  .text-size-controls .settings-hint-arrow,
  .font-control .settings-hint-arrow {
    top: calc(100% + 4px);
    width: 20px;
    height: 44px;
  }
}

.activity-view .activity-header {
  margin-bottom: 1.125rem;
  padding-top: 0;
}

/* Home and Help are fixed for the landing page; inside an activity they scroll
   with everything else. */
.activity-view .home-button,
.activity-view .help-button {
  position: static;
}

@media (max-width: 767px) {
  .activity-view .activity-header {
    grid-template-areas:
      "brand actions"
      "logo logo";
    grid-template-columns: 1fr auto;
  }

  .activity-view .activity-brand {
    grid-area: brand;
  }

  .activity-view .activity-logo {
    grid-area: logo;
  }

  .activity-view .header-actions {
    grid-area: actions;
  }
}

.settings-unavailable-tip {
  position: absolute;
  z-index: 10;
  top: 100%;
  right: 0;
  width: max-content;
  max-width: min(280px, calc(100vw - 32px));
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--text-main);
  color: var(--surface-tint);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.35;
  opacity: 0;
  visibility: hidden;
  pointer-events: auto;
  transform: translateY(-4px);
  transition: opacity 160ms ease-out, transform 160ms ease-out, visibility 160ms;
}

.settings-control.is-unavailable:not(.tooltip-suppressed):hover .settings-unavailable-tip,
.settings-control.is-unavailable:not(.tooltip-suppressed):focus-within .settings-unavailable-tip {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.tooltip-suppressed .settings-unavailable-tip,
.tooltip-suppressed .tool-label {
  opacity: 0 !important;
  visibility: hidden !important;
  transition: none !important;
}

.activity-logo {
  width: 190px;
  height: auto;
  justify-self: center;
}

h1,
h2,
p {
  margin-top: 0;
}

h1 {
  margin-bottom: 0;
  font-size: clamp(2rem, 4vw, 3.25rem);
  line-height: 1.05;
}

h2 {
  margin-bottom: 14px;
  font-size: 1.25rem;
}

/* The instruction is the one thing the learner must read, so it outranks every
   other label on the card. */
#taskTitle {
  font-size: 2.5rem;
  line-height: 1.3;
  margin: 0;
}

/* Audio sits beside the instruction or target and drops below it when the two no
   longer fit. Its middle aligns with the middle of the instruction. */
.task-heading-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 20px;
  margin: 42px 0 24px;
}

.task-heading-row #taskTitle {
  flex: 0 1 auto;
}

/* Words and Sentences empty this row: the title is hidden and the audio button
   sits beside the sample instead. Keep a gap above the word, just not one
   sized for a line of text that is not there. */
.task-heading-row.is-collapsed {
  margin: 0 0 16px;
}

.task-heading-row .audio-button {
  flex: 0 0 auto;
}

/* The task title is already bold, so the named word needs colour + an underline. */
#taskTitle strong {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 2px;
}

/* The arrow sits over the named letter: right for after, left for before. Its
   wider box stays centred on the letter without changing the spoken text. */
#taskTitle strong.cursor-target,
#titleDemoInstruction strong.cursor-target {
  position: relative;
  display: inline-block;
  padding-top: 0.58em;
  line-height: 1;
}

.cursor-target-arrow {
  position: absolute;
  top: 0;
  left: 50%;
  width: 1.2em;
  height: 0.58em;
  overflow: visible;
  fill: none;
  stroke: var(--accent);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  transform: translateX(-50%);
  pointer-events: none;
}

.cursor-target-before .cursor-target-arrow {
  transform: translateX(-50%) scaleX(-1);
}

/* A key named in an instruction is drawn as the key itself. Everything is in em
   so keycaps follow the text size and 200% zoom. */
.keycap {
  display: inline-block;
  padding: 0.1em 0.5em;
  border: 2px solid var(--field-border);
  border-bottom-width: 5px;
  border-radius: 8px;
  background: var(--keyboard-key);
  color: var(--text-main);
  font-family: inherit;
  font-size: 0.9em;
  font-weight: 700;
  line-height: 1.3;
  white-space: nowrap;
}

/* The real space bar has nothing printed on it: a wide blank key. Its own
   margin, not a template space, so a blank key never leaves stray whitespace
   sitting in front of the sentence's closing period. */
.keycap-space {
  width: 5em;
  margin-left: 0.3em;
}

.keycap-space::before {
  content: "\00a0";
}

/* A character to type is paper, not a key: white, flat, and evenly bordered so
   nothing about it says "press". Sized like the keycap so lines do not reflow. */
.type-chip {
  display: inline-block;
  min-width: 1.2em;
  padding: 0.1em 0.5em;
  border: 2px solid var(--chip-border);
  border-radius: 8px;
  background: var(--card-bg);
  color: var(--text-main);
  font-family: inherit;
  font-size: 0.9em;
  font-weight: 700;
  line-height: 1.3;
  text-align: center;
  white-space: nowrap;
}

/* When the chip is the entire instruction it stops being an inline mark and
   becomes the thing on screen. The wider minimum keeps a lone "." from reading
   as an empty box. */
.task-title-solo-chip .type-chip {
  min-width: 1.5em;
  padding: 0.08em 0.34em;
  font-size: 1.7em;
}

.key-combination-hint {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 4rem;
  /* The negative top tucks it under the instruction in the modes whose sample
     box is hidden — there the sample row collapses to nothing above it. */
  margin: -0.25rem 0 1.5rem;
  padding-top: 0.75rem;
}

/* Capital Letters is the only mode that shows a sample, so it is the only one
   where the keys can sit beside the word instead of under it. The sample box is
   taller than a keycap, so the row does not grow — revealing the hint moves
   nothing below it, which is the whole point. */
.sample-row:has(.sample-box:not(.hidden)) .key-combination-hint {
  min-height: 0;
  margin: 0;
  padding-top: 0;
}

/* The modes with no sample leave the hint alone in the row, where its bottom
   margin no longer collapses with the divider's the way it did as a sibling.
   Drop it and let the divider's own margin space it — 2px shy of where it sat. */
.sample-row:not(:has(.sample-box:not(.hidden))) .key-combination-hint {
  margin-bottom: 0;
}

.key-hint-keycap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 68px;
  height: 64px;
  border: 2px solid var(--field-border);
  border-bottom-width: 6px;
  border-radius: 10px;
  background: var(--keyboard-key);
  color: var(--text-main);
  padding: 6px 14px;
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
}

.key-hint-keycap.is-wide {
  min-width: 112px;
}

.key-hint-keycap.is-space {
  min-width: 168px;
}

.key-hint-keycap.has-shifted-label {
  display: grid;
  grid-template-rows: 1fr 1fr;
  justify-items: center;
  padding-block: 5px;
}

.key-hint-shifted-label,
.key-hint-base-label {
  line-height: 1;
}

.key-hint-plus {
  color: var(--text-muted);
  font-size: 2rem;
  font-weight: 700;
  line-height: 1;
}

.mode-card,
.task-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 1.125rem;
  margin-bottom: 1rem;
}

.task-divider {
  border: none;
  height: 1px;
  background: var(--border);
  margin: 1.375rem 0 1rem;
}

.activity-title-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.55fr) minmax(0, 1.45fr);
  gap: 36px;
  align-items: stretch;
}

.activity-title-identity {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  padding: 24px 18px;
}

.activity-title-number {
  margin: 0 0 28px;
  color: var(--accent);
  font-size: 4rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.activity-title-kicker {
  margin: 0 0 12px;
  color: var(--accent);
  font-size: 1.125rem;
  font-weight: 700;
  line-height: 1.3;
}

.activity-title-heading {
  margin: 0;
  color: var(--text-main);
  font-size: 2.5rem;
  line-height: 1.15;
}

.activity-title-heading:focus:not(:focus-visible) {
  outline: none;
}

.activity-title-instructions {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  border-left: 1px solid var(--border);
  padding: 24px 18px 24px 42px;
}

.activity-title-intro p {
  margin: 0 0 14px;
  color: var(--text-main);
  font-size: 1.25rem;
  line-height: 1.5;
}

.activity-title-steps {
  display: grid;
  gap: 16px;
  margin: 0;
  padding-left: 2.2rem;
  color: var(--text-main);
  font-size: 1.25rem;
  line-height: 1.45;
}

.activity-title-steps li {
  padding-left: 8px;
}

.activity-title-steps li::marker {
  color: var(--accent);
  font-weight: 700;
}

/* Watch-first demo. A scaled-down restatement of the task UI rather than the
   real components: the real Check button and feedback box are sized for the
   full card and overflow this column. Every colour, radius, and font comes
   from the same tokens, so it still reads as the same app. */
.title-demo-frame {
  position: relative;
  /* The demo's pointer is parked below the Check chip, which overshoots the
     frame on short modes. The poster only covers the frame, so an escaped arrow
     rendered at full strength on the bare card and read as a real cursor. */
  overflow: hidden;
  border: 2px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
  padding: 18px;
}

.title-demo-scene {
  display: grid;
  gap: 12px;
}

/* One control for the whole demo, so there is never a second play button on
   this slide competing with it. */
.title-demo-control {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  cursor: var(--pointer-hand);
  font-family: inherit;
  transition: opacity 0.35s ease;
}

/* Poster state: covers the frame until the learner presses it. Nothing here
   ever autoplays. */
.title-demo-control.is-poster {
  inset: 0;
  flex-direction: column;
  gap: 4px;
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.86);
  color: var(--primary);
  font-size: 1.375rem;
  font-weight: 700;
}

.title-demo-control.is-poster .title-demo-control-icon {
  font-size: 3.25rem;
  line-height: 1;
}

/* After the first press it shrinks to a centre play/pause. */
.title-demo-control:not(.is-poster) {
  top: 50%;
  left: 50%;
  width: 74px;
  height: 74px;
  transform: translate(-50%, -50%);
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.6);
  color: var(--card-bg);
  font-size: 1.75rem;
}

.title-demo-control:not(.is-poster) .title-demo-control-label {
  display: none;
}

/* Fades out while playing; hover, tap, and keyboard focus all bring it back. */
.title-demo.is-playing .title-demo-control:not(.is-poster) {
  opacity: 0;
}

.title-demo.is-playing .title-demo-frame:hover .title-demo-control,
.title-demo.is-playing.is-showing-controls .title-demo-control,
.title-demo .title-demo-control:focus-visible {
  opacity: 1;
}

/* The demo control speaks for the whole slide, so the separate instructions
   audio button would be a second play button saying something different. */
.activity-title-screen.has-title-demo .activity-title-instructions .audio-button,
.activity-title-screen.has-title-demo .activity-title-audio-status {
  display: none;
}

.title-demo-topline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.title-demo-instruction {
  margin: 0;
  color: var(--text-main);
  font-size: 1.125rem;
  font-weight: 700;
}

.title-demo-audio {
  border: 2px solid var(--primary);
  border-radius: 999px;
  padding: 5px 14px;
  color: var(--primary);
  font-size: 0.8125rem;
  font-weight: 700;
  white-space: nowrap;
}

.title-demo-audio.is-playing {
  background: var(--primary);
  color: var(--card-bg);
}

/* Mirrors the real Settings button so the demo points at something the learner
   will recognise in the corner of the page. */
.title-demo-gear {
  margin-left: auto;
  border: 2px solid var(--primary);
  border-radius: 10px;
  padding: 4px 10px;
  color: var(--primary);
  font-size: 1.125rem;
  line-height: 1.2;
}

/* Stand-in for the settings drawer: the size stepper, plus static font and
   spaces chips so the frame shows all three things Mode 7's instruction line
   names, not just the one control the demo actually drives. Wraps on narrow
   screens rather than overflowing the frame. */
.title-demo-drawer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-self: start;
  gap: 10px;
  border: 2px solid var(--sample-border);
  border-radius: var(--radius-md);
  background: var(--primary-soft);
  padding: 8px 14px;
}

.title-demo-key {
  display: inline-flex;
  align-items: center;
  border: 2px solid var(--primary);
  border-radius: 10px;
  padding: 4px 12px;
  color: var(--primary);
  font-size: 1.0625rem;
  font-weight: 700;
}

/* Free Practice's demo presses Bold, Underline, and the spaces chip — this
   is their "pressed" look, matching the real toolbar buttons' filled state. */
.title-demo-key.is-active {
  background: var(--primary);
  color: var(--card-bg);
}

.title-demo-spaces-icon {
  width: 24px;
  height: 10px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}

.title-demo-size {
  min-width: 2.2ch;
  color: var(--text-main);
  font-size: 1.375rem;
  font-weight: 700;
  text-align: center;
}

/* The thing being copied, for the modes that show one. Echoes the real sample
   box rather than reproducing it — the real one carries images and demos this
   frame has no room for. */
.title-demo-sample {
  justify-self: start;
  margin: 0;
  border: 2px solid var(--sample-border);
  border-radius: var(--radius-md);
  background: var(--primary-soft);
  padding: 8px 20px;
  font-family: "Atkinson Hyperlegible Local", sans-serif;
  font-size: 1.75rem;
  font-weight: 700;
}

.title-demo-field {
  display: flex;
  align-items: center;
  border: 3px solid var(--field-border);
  border-radius: var(--radius-lg);
  padding: 12px 14px;
  min-height: 2.4em;
  font-family: "Atkinson Hyperlegible Local", sans-serif;
  font-size: 1.5rem;
  line-height: 1.6;
  white-space: pre-wrap;
}

.title-demo-field.is-focused {
  border-color: var(--primary);
  box-shadow: 0 0 0 4px var(--focus-glow);
}

.title-demo-caret {
  display: none;
  width: 2px;
  min-height: 1.1em;
  margin: 0 -1px;
  background: currentColor;
}

.title-demo-field.is-focused .title-demo-caret {
  display: inline-block;
  animation: title-demo-blink 1s step-end infinite;
}

@keyframes title-demo-blink {
  50% { opacity: 0; }
}

.title-demo-feedback {
  display: none;
  align-items: center;
  gap: 10px;
  margin: 0;
  border-radius: var(--radius-md);
  padding: 10px 14px;
  font-size: 1.0625rem;
  font-weight: 700;
}

.title-demo-feedback.good {
  display: flex;
  border: 3px solid var(--success-border);
  background: var(--success-bg);
  color: var(--success-text);
}

.title-demo-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  justify-self: start;
  border-radius: 12px;
  background: var(--primary);
  padding: 10px 22px;
  color: var(--card-bg);
  font-size: 1.0625rem;
  font-weight: 700;
}

.title-demo-check-icon {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The fake pointer. Positioned against .title-demo-frame and moved by
   transform, so the step table never hard-codes a coordinate. */
.title-demo-pointer {
  position: absolute;
  top: 0;
  left: 0;
  width: 26px;
  height: 26px;
  transform: translate(50%, 240%);
  transition: transform 0.55s cubic-bezier(0.33, 0, 0.2, 1), opacity 0.3s ease;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.4));
}

/* The arrow only means something while the demo is running. Whenever the poster
   is up — before the first press and after the run ends — it is parked, so it
   would just be a stray cursor sitting on the frame. Keyed off the poster, not
   .is-playing, because pause keeps the frozen frame and its arrow readable. */
.title-demo-frame:has(.title-demo-control.is-poster) .title-demo-pointer {
  opacity: 0;
}

.title-demo-pointer svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: var(--card-bg);
  stroke: var(--text-main);
  stroke-width: 1.6;
  stroke-linejoin: round;
}

.title-demo-ring {
  position: absolute;
  top: 1px;
  left: 1px;
  width: 12px;
  height: 12px;
  border: 3px solid var(--primary);
  border-radius: 999px;
  opacity: 0;
}

.title-demo-pointer.is-clicking .title-demo-ring {
  animation: title-demo-ring 0.45s ease-out;
}

@keyframes title-demo-ring {
  0%   { opacity: 0.9; transform: scale(0.4); }
  100% { opacity: 0;   transform: scale(3.2); }
}

/* Reduced motion gets the written steps instead — the demo never runs, so a
   frozen half-finished frame would be worse than the list it replaced. */
@media (prefers-reduced-motion: reduce) {
  .title-demo {
    display: none;
  }
}

.activity-title-instructions .audio-button {
  align-self: flex-start;
  margin-top: 1.625rem;
}

.activity-title-audio-status {
  min-height: 1.4em;
  margin: 8px 0 0;
  color: var(--warning-text);
  font-weight: 700;
  line-height: 1.4;
}

.activity-title-audio-status:empty {
  display: none;
}

.activity-title-actions {
  margin-top: 1.5rem;
}

/* The card's own 18px padding left only 19px above the mode name while the space
   under Hear message came to 48px (36px here + .action-row's 12px margin), so
   the block sat high and looked top-cropped. Both gaps are now 36px:
   18px card padding + 18px here on top, 24px + 12px action-row margin below. */
.completion-screen {
  text-align: center;
  padding-top: 1.125rem;
  /* Matches padding-top so the card breathes equally above the mode name and
     below the last button. The card's own 18px is on both sides already, so
     these two values land both gaps on 36px. */
  padding-bottom: 1.125rem;
}

.completion-mode-label {
  margin: 0 0 8px;
  color: var(--accent);
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.3;
}

.completion-headline {
  font-size: 2.125rem;
  font-weight: 700;
  color: var(--text-main);
  line-height: 1.2;
  margin-bottom: 8px;
}

.completion-subtext {
  font-size: 1.25rem;
  color: var(--text-muted);
  margin-bottom: 24px;
}

.completion-icon {
  width: 44px;
  height: 44px;
  margin: 0 auto 14px;
}

.completion-icon svg {
  width: 100%;
  height: 100%;
}

.completion-check-icon circle {
  fill: var(--success-border);
}

.completion-check-icon path {
  fill: none;
  stroke: var(--card-bg);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.completion-screen.is-success .completion-subtext {
  color: var(--success-text);
  font-weight: 700;
}

/* The gap below the audio button is the only separator between the message and
   the two choices. It replaces a <hr> that added a seventh element to a screen
   already carrying too many. */
.completion-screen .audio-button {
  margin: 0 auto 1.5rem;
}

.editor-toolbar {
  padding: 18px 0 12px;
  margin-top: 32px;
  background: var(--card-bg);
  border-top: 1px solid var(--border);
}

.settings-drawer {
  position: relative;
  background: var(--mode-tile-tint);
  border: 1px solid var(--sample-border);
  border-radius: var(--radius-lg);
  padding: 0.375rem 1.125rem 1.125rem;
  margin-bottom: 1rem;
}

.settings-drawer[hidden] {
  display: none;
}

.settings-drawer .editor-toolbar {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
  background: transparent;
}

.action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.primary-button,
.secondary-button,
.audio-button {
  min-height: 3rem;
  border-radius: var(--radius-pill);
  border: 2px solid var(--primary);
  padding: 0.625rem 1.125rem;
  cursor: var(--pointer-hand);
  font-weight: 700;
}

.mode-button {
  cursor: var(--pointer-hand);
}

.secondary-button,
.audio-button,
.space-toggle-button {
  background: var(--card-bg);
  color: var(--primary);
}

.audio-button {
  min-height: 3.75rem;
  padding: 0.875rem 1.625rem;
  font-size: 1.25rem;
}

.primary-button {
  background: var(--primary);
  color: var(--card-bg);
}

#guidedActions #checkButton:not(.hidden),
#guidedActions #skipButton:not(.hidden),
#guidedActions #hintButton:not(.hidden),
#guidedActions #nextTaskButton:not(.hidden),
.activity-title-actions .primary-button,
.completion-actions .primary-button,
.completion-actions .secondary-button {
  flex: 1;
  min-height: 5.25rem;
  padding: 1.375rem;
  border-radius: 0.75rem;
  font-size: 1.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.875rem;
  box-shadow: 0 4px 12px rgba(28, 63, 163, 0.3);
}

#guidedActions #skipButton:not(.hidden),
#guidedActions #hintButton:not(.hidden),
.completion-actions .secondary-button {
  box-shadow: 0 4px 12px rgba(20, 34, 61, 0.12);
}

/* Completion actions stack instead of sharing a row. Side by side at equal
   width they read as two equal choices, which contradicts the one-primary rule;
   stacked, "More practice" is plainly the main path and the alternative sits
   below it, quieter and shorter. */
.completion-actions {
  flex-direction: column;
  /* The card runs the full 1100px activity width, so an unconstrained stack
     produces two very wide, very short bars. 520px matches the dialog width
     already used elsewhere and keeps both actions in one glance. */
  max-width: 520px;
  margin-inline: auto;
}

.completion-actions .primary-button,
.completion-actions .secondary-button {
  flex: none;
  width: 100%;
}

.completion-actions .secondary-button {
  min-height: 4.25rem;
  font-size: 1.5rem;
}

#guidedActions #checkButton:not(.hidden) {
  flex: 0.95;
}

#guidedActions #hintButton:not(.hidden) {
  flex: 1.05;
}

#guidedActions #hintButton:disabled {
  box-shadow: none;
}

.hint-icon-hide {
  display: none;
}

#hintButton[aria-pressed="true"] .hint-icon-show {
  display: none;
}

#hintButton[aria-pressed="true"] .hint-icon-hide {
  display: block;
}

.mode-button:focus-visible,
.activity-tile:focus-visible,
.primary-button:focus-visible,
.secondary-button:focus-visible,
.audio-button:focus-visible,
.home-button:focus-visible,
.help-button:focus-visible,
.settings-button:focus-visible,
.back-button:focus-visible,
.footer-link:focus-visible,
.space-toggle-button:focus-visible,
.tool-button:focus-visible {
  outline: 4px solid var(--focus-blue);
  outline-offset: 3px;
  box-shadow: 0 0 0 2px var(--card-bg);
}

.home-button,
.help-button,
.settings-button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 3rem;
  border: 2px solid var(--primary);
  border-radius: 0.75rem;
  background: var(--surface-tint);
  color: var(--primary);
  padding: 0.625rem 1rem;
  font-size: 1rem;
  font-weight: 700;
  cursor: var(--pointer-hand);
  box-shadow: 0 2px 5px rgba(20, 34, 61, 0.12);
}

.home-button:hover:not(:disabled),
.help-button:hover:not(:disabled),
.settings-button:hover:not(:disabled):not([aria-disabled="true"]) {
  background: var(--primary-soft);
}

.settings-button.is-active {
  background: var(--primary);
  color: var(--card-bg);
}

.settings-button.is-active:hover:not(:disabled) {
  background: var(--primary);
}

.settings-button[aria-disabled="true"] {
  border-color: var(--border);
  background: var(--keyboard-key);
  color: var(--text-muted);
  cursor: not-allowed;
  box-shadow: none;
}

/* The gear and the word "Settings" inline in a sentence. Deliberately carries
   none of .settings-button's border, fill or shadow: it names the control, it
   is not a copy of it, and a copy is what learners were tapping. */
/* Stays plain inline so "Settings" sits on the sentence's own baseline. An
   inline-flex wrapper here dropped the word 5px below the words either side of
   it. Only the gear is shifted, and only to centre it on the text. */
/* No side margins: the sentence already supplies its own space before this, and
   a right margin put a visible gap between "Settings" and the period. */
.inline-settings-name {
  white-space: nowrap;
  /* The same blue as the control it names, so the sentence and the button in the
     corner read as the same thing. Action blue on a few inline words is what the
     Restrained Blue Rule allows; it is naming an action, not tinting a surface. */
  color: var(--primary);
  font-weight: 700;
}

.inline-settings-name .settings-icon {
  margin-right: 0.2em;
  vertical-align: middle;
}

.home-icon,
.help-icon,
.settings-icon,
.check-icon,
.hint-icon {
  width: 1.45rem;
  height: 1.45rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex-shrink: 0;
}

.task-topline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1.125rem;
}

.back-button {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: 3rem;
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: 1rem;
  font-weight: 600;
  padding: 0.375rem 0.25rem;
  cursor: var(--pointer-hand);
}

.back-button:hover:not(:disabled) {
  color: var(--text-main);
  text-decoration: underline;
}

.back-arrow {
  font-size: 1.2rem;
  line-height: 1;
}

.task-status {
  margin: 0 0 0 auto;
  color: var(--text-muted);
  font-size: 1rem;
  font-weight: 600;
}

.activity-mode-label {
  margin: 0;
}

/* Matches the landing tile and title slide: the number in the group's accent. */
.task-mode-number {
  margin-right: 8px;
  color: var(--accent);
  font-size: 1.25rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.task-status .task-count::before {
  content: "·";
  margin: 0 8px;
}

.audio-button.active {
  background: var(--primary);
  color: var(--card-bg);
}

.audio-button[aria-busy="true"] {
  background: var(--primary-soft);
  color: var(--text-main);
  cursor: progress;
}

.title-demo-control[aria-busy="true"] {
  cursor: progress;
}

.task-count {
  margin: 0;
  color: var(--text-muted);
  font-weight: 700;
}

.task-progress-track {
  display: flex;
  gap: 0.25rem;
  height: 0.375rem;
  margin-bottom: 1.375rem;
}

.task-progress-segment {
  flex: 1;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--border);
  transition: background-color 200ms ease-out;
}

.task-progress-segment.is-complete {
  background: var(--accent);
}

.sample-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 0.75rem;
}

#guidedPanel .sample-row {
  flex-wrap: wrap;
}

#guidedPanel .sample-box {
  flex-shrink: 0;
}

.sample-row .audio-button {
  flex: 0 0 auto;
}

.sample-box {
  width: fit-content;
  max-width: 100%;
  background: var(--primary-soft);
  border: 2px solid var(--sample-border);
  border-radius: var(--radius-md);
  padding: 1rem;
}

.sample-box.has-image,
.sample-box.has-demo {
  display: flex;
  align-items: center;
  gap: 1rem;
}

/* Let the animated demos drop below the word instead of overflowing on narrow screens. */
.sample-box.has-demo {
  flex-wrap: wrap;
}

.typing-demo {
  display: inline-flex;
  align-items: center;
  min-width: 150px;
  padding: 12px 18px;
  border: 2px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  font-family: ui-monospace, Menlo, Consolas, "Courier New", monospace;
  font-size: 2.5rem;
  font-weight: 700;
}

.typing-demo.demo-muted #demoText {
  color: var(--demo-text);
}

.demo-cursor {
  margin-left: 2px;
  animation: demo-blink 1s step-end infinite;
}

/* Text-cursor task: a green arrow points down at the blinking cursor. */
.cursor-arrow {
  display: none;
  width: 0.85em;
  height: 0.85em;
  color: var(--cursor-arrow);
  transform: translate(-0.43em, -0.6em);
}

.typing-demo.has-arrow .cursor-arrow {
  display: inline-block;
}

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

@keyframes demo-blink {
  50% {
    opacity: 0;
  }
}

/* "type" task: a looping video of hands typing. */
.video-demo {
  flex-shrink: 0;
  height: 100px;
  width: auto;
  max-width: 100%;
  border-radius: var(--radius-md);
}

/* "click" task: the mouse photo dips like a press, and a ring pulses from the button. */
.click-demo {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  line-height: 0;
}

.click-demo-img {
  height: 100px;
  width: auto;
  border-radius: var(--radius-md);
  animation: click-press 1.6s ease-in-out infinite;
}

.click-demo-ring {
  position: absolute;
  top: 22%;
  left: 36%;
  width: 13px;
  height: 13px;
  margin: -6.5px 0 0 -6.5px;
  border: 3px solid var(--card-bg);
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
  opacity: 0;
  animation: click-ring 1.6s ease-out infinite;
}

@keyframes click-press {
  0%, 100% {
    transform: translateY(0);
  }
  10% {
    transform: translateY(3px);
  }
  20% {
    transform: translateY(0);
  }
}

@keyframes click-ring {
  0% {
    transform: scale(0.4);
    opacity: 0;
  }
  10% {
    opacity: 0.9;
  }
  55% {
    transform: scale(2.6);
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}

/* Keyboard-key demos: the animated symbols.
   Pure CSS like the click demo. One #keyDemo holds all scenes; [data-mode] shows one.
   SPEED: --demo-dur below. SIZE: the clamp() font-size (keys + text scale with it).
   COLOURS: --border (keys), --primary (the ? and ! marks), --demo-ink (text/cursor). */
.key-demo {
  --demo-dur: 4.5s;
  display: inline-flex;
  flex-shrink: 0;
  font-family: ui-monospace, Menlo, Consolas, "Courier New", monospace;
  font-weight: 700;
  font-size: clamp(1.1rem, 3.4vw, 1.75rem);
  color: var(--demo-ink);
}

.key-demo .key-scene {
  display: none;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6em;
}

.key-demo[data-mode="underscore"] .key-scene[data-scene="underscore"],
.key-demo[data-mode="dash"] .key-scene[data-scene="dash"],
.key-demo[data-mode="question"] .key-scene[data-scene="question"],
.key-demo[data-mode="exclaim"] .key-scene[data-scene="exclaim"],
.key-demo[data-mode="period"] .key-scene[data-scene="period"],
.key-demo[data-mode="press"] .key-scene[data-scene="press"],
.key-demo[data-mode="hold"] .key-scene[data-scene="hold"] {
  display: inline-flex;
}

/* press / hold. One keycap, two timings — that difference is the whole lesson,
   so nothing else about the two scenes changes. The 3D edge is a box-shadow
   rather than a bottom border because the key is animated: shadow and transform
   are paint and composite, while animating border-width would be layout. */
.ks-key {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 68px;
  height: 60px;
  border: 2px solid var(--field-border);
  border-radius: 10px;
  background: var(--keyboard-key);
  box-shadow: 0 5px 0 var(--field-border);
  color: var(--text-main);
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
}

/* The contact point: a fingertip resting on the lower part of the key. Blinks on
   for press, rests for hold — a key sitting down on its own reads as stuck, and
   the finger is what makes it read as held. Sits right of centre so it never
   covers the letter on the keycap. Outlined rather than filled with a skin
   tone, matching the pointer in the title demo: this is a diagram of a hand,
   not a picture of one, so it does not have to belong to anybody. */
.ks-key-touch {
  position: absolute;
  right: 6px;
  bottom: -2px;
  width: 22px;
  height: 27px;
  opacity: 0;
}

.ks-key-touch svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: var(--card-bg);
  stroke: var(--text-main);
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ks-key-press {
  animation: ks-key-press var(--demo-dur) ease-in-out infinite;
}

.ks-key-press .ks-key-touch {
  animation: ks-touch-press var(--demo-dur) ease-in-out infinite;
}

.ks-key-hold {
  animation: ks-key-hold var(--demo-dur) ease-in-out infinite;
}

.ks-key-hold .ks-key-touch {
  animation: ks-touch-hold var(--demo-dur) ease-in-out infinite;
}

/* Down and straight back up: contact is brief. */
@keyframes ks-key-press {
  0%, 22% { transform: none; box-shadow: 0 5px 0 var(--field-border); }
  32%, 42% { transform: translateY(4px); box-shadow: 0 1px 0 var(--field-border); }
  52%, 100% { transform: none; box-shadow: 0 5px 0 var(--field-border); }
}

@keyframes ks-touch-press {
  0%, 26% { opacity: 0; }
  32%, 42% { opacity: 1; }
  48%, 100% { opacity: 0; }
}

/* Down, and stays down for most of the loop: that dwell is what "hold" means. */
@keyframes ks-key-hold {
  0%, 14% { transform: none; box-shadow: 0 5px 0 var(--field-border); }
  24%, 78% { transform: translateY(4px); box-shadow: 0 1px 0 var(--field-border); }
  88%, 100% { transform: none; box-shadow: 0 5px 0 var(--field-border); }
}

@keyframes ks-touch-hold {
  0%, 18% { opacity: 0; }
  24%, 78% { opacity: 1; }
  84%, 100% { opacity: 0; }
}

/* Symbols appear with a calm scale. (Static shows them.) */
.ks-mark {
  display: inline-block;
  color: var(--primary);
  transform-origin: center bottom;
}

/* Symbol demos show the character alone, larger. */
.ks-mark-solo {
  font-size: 1.9em;
}

/* Symbol demos use the same white field as the special-characters demo. */
.ks-symbol-field {
  justify-content: center;
}

.ks-symbol-field .ks-mark-solo {
  font-size: 1em;
}

.key-demo[data-mode="underscore"] .ks-mark,
.key-demo[data-mode="dash"] .ks-mark,
.key-demo[data-mode="question"] .ks-mark,
.key-demo[data-mode="exclaim"] .ks-mark,
.key-demo[data-mode="period"] .ks-mark {
  animation: ks-mark var(--demo-dur) ease-in-out infinite;
}

@keyframes ks-mark {
  0%, 30% { opacity: 0; transform: scale(1); }
  40% { opacity: 1; transform: scale(1.25); }
  50% { transform: scale(1); }
  82% { opacity: 1; }
  90%, 100% { opacity: 0; transform: scale(1); }
}

.sample-text {
  margin-bottom: 0;
  font-weight: 700;
}

/* Capital Letters: the missing capital is a blank line the answer will fill.
   No height, so the border sits exactly on the baseline of the rest of the name. */
.sample-blank {
  display: inline-block;
  width: 0.7em;
  margin-right: 0.06em;
  border-bottom: 4px solid currentColor;
}

/* Answered: the capital drops into the blank, underline still under it. */
.sample-blank.is-filled {
  width: auto;
  text-align: center;
}

.sample-image {
  flex-shrink: 0;
  height: 84px;
  width: auto;
  max-width: 100%;
  object-fit: contain;
}

/* visibility, not display: the name photos are a fixed 84px square, so hiding
   the element across a src swap must not collapse the row and shift the word. */
.sample-image.is-swapping {
  visibility: hidden;
}

/* Cursor vocabulary: the pointer glides in, then rests long enough to study. */
.sample-image.is-cursor-demo {
  animation: cursor-icon-glide 4.5s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}

.sample-image.is-spacebar-image {
  width: min(180px, 32vw);
  height: auto;
}

/* Name photos are pre-cropped to a circle on a white square background;
   mask the square away so only the circle shows. */
.sample-image.is-name-photo {
  width: 84px;
  border-radius: 50%;
  object-fit: cover;
}

@media (max-width: 640px) {
  .sample-image.is-name-photo {
    width: 56px;
  }

  /* No room beside the word on a phone, so the keys go back under it. Full
     basis forces the wrap at the breakpoint rather than leaving it to whatever
     the name happens to measure. */
  .sample-row:has(.sample-box:not(.hidden)) .key-combination-hint {
    flex-basis: 100%;
    margin-top: 0.75rem;
  }
}

@keyframes cursor-icon-glide {
  0%, 24% {
    opacity: 0;
    transform: translate3d(24px, 0, 0);
  }

  38%, 82% {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }

  92%, 100% {
    opacity: 0;
    transform: translate3d(0, 0, 0);
  }
}

@media (max-width: 900px) {
  .activity-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .sample-image {
    height: 56px;
  }

  .sample-image.is-keyboard-image {
    width: min(120px, 34vw);
    height: auto;
  }

  .typing-demo {
    min-width: 120px;
    padding: 10px 14px;
    font-size: 1.75rem;
  }

  .click-demo-img,
  .video-demo {
    height: 76px;
  }
}

.feedback-message {
  min-height: 2rem;
  margin-top: 0.75rem;
  font-weight: 700;
}

.feedback-message.good:not(.hidden) {
  display: flex;
  align-items: center;
  gap: 1rem;
  background: var(--success-bg);
  color: var(--success-text);
  border: 3px solid var(--success-border);
  border-radius: var(--radius-md);
  padding: 1rem 1.25rem;
  font-size: 1.625rem;
  line-height: 1.3;
}

/* Pressing Check again without changing anything repeats the same message, so
   nothing on screen moves and the button reads as broken. This pulse is the
   only acknowledgement that the press landed. */
.feedback-message.is-repeat {
  animation: feedback-repeat 0.45s ease-out;
}

@keyframes feedback-repeat {
  0%, 100% { transform: none; box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }
  30% { transform: scale(1.02); box-shadow: 0 0 0 6px var(--warning-border); }
}

/* Same acknowledgement without the movement. */
@media (prefers-reduced-motion: reduce) {
  @keyframes feedback-repeat {
    0%, 100% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }
    30% { box-shadow: 0 0 0 6px var(--warning-border); }
  }
}

.feedback-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  color: var(--card-bg);
  font-size: 1.375rem;
  font-weight: 700;
  line-height: 1;
}

.feedback-message.good .feedback-icon {
  background: var(--success-border);
}

.feedback-message.check .feedback-icon {
  background: var(--warning-border);
}

.feedback-message.check:not(.hidden) {
  display: flex;
  align-items: center;
  gap: 1rem;
  background: var(--warning-bg);
  color: var(--warning-text);
  border: 3px solid var(--warning-border);
  border-radius: var(--radius-md);
  padding: 1rem 1.25rem;
  font-size: 1.625rem;
  line-height: 1.4;
}

.feedback-message.skip .feedback-icon {
  background: var(--primary);
}

.feedback-message.skip:not(.hidden) {
  display: flex;
  align-items: center;
  gap: 1rem;
  background: var(--primary-soft);
  color: var(--text-main);
  border: 3px solid var(--sample-border);
  border-radius: var(--radius-md);
  padding: 1rem 1.25rem;
  font-size: 1.625rem;
  line-height: 1.4;
}

.sample-space {
  display: inline-block;
  width: 0.33em;
  height: 0.8em;
  background: var(--space-cue);
  vertical-align: -0.08em;
}

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 18px 40px;
}

/* 1rem, not 0.9: the DESIGN.md Body floor. These name the controls a learner
   opens Settings to find, so they are not decoration. */
.toolbar-group-title {
  margin: 0 0 8px;
  color: var(--text-muted);
  font-size: 1rem;
}

.toolbar-group-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(76px, 1fr));
  gap: 10px;
}

.space-toggle-button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 4rem;
  border: 2px solid var(--primary);
  border-radius: 0.875rem;
  padding: 0.5rem 0.875rem;
  cursor: var(--pointer-hand);
  font-weight: 700;
}

.space-toggle-button.active {
  background: var(--primary);
  color: var(--card-bg);
}

.space-toggle-icon {
  display: inline-flex;
  align-items: center;
}

.spacebar-icon {
  width: 28px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}

.space-toggle-label {
  display: inline-block;
  /* Wide enough that the button does not resize when Show flips to Hide, and
     no wider — it was 100px back when the label carried the word "spaces". */
  min-width: 2.75rem;
  text-align: left;
}

.text-size-controls {
  display: flex;
  align-items: flex-start;
  gap: 1.25rem;
}

.size-control {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.font-toolbar-group .toolbar-group-controls {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.text-format-toolbar-group .toolbar-group-controls {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.font-control {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 0 0 64px;
  width: 64px;
  min-width: 0;
  gap: 0.25rem;
}

.font-toolbar-group .font-control {
  position: relative;
}

.font-toolbar-group .tool-label {
  position: absolute;
  z-index: 2;
  top: 100%;
  left: 50%;
  width: max-content;
  max-width: 160px;
  margin: 0;
  border-radius: 6px;
  background: var(--text-main);
  color: var(--card-bg);
  padding: 5px 8px;
  font-size: 0.72rem;
  line-height: 1.2;
  opacity: 0;
  pointer-events: auto;
  transform: translate(-50%, -4px);
  visibility: hidden;
}

@media (hover: hover) {
  .font-control:not(.tooltip-suppressed):hover .tool-label {
    opacity: 1;
    transform: translate(-50%, 0);
    visibility: visible;
  }
}

/* :has(:focus-visible), not :focus-within — a Fonts task focuses its target font
   button on render, and :focus-within made that reveal the button's name with no
   pointer near it, naming the answer on the one task where the font itself is
   the question. :focus-visible only matches focus the browser attributes to the
   keyboard, so a learner tabbing here still hears and sees the name. */
.font-control:not(.tooltip-suppressed):has(:focus-visible) .tool-label {
  opacity: 1;
  transform: translate(-50%, 0);
  visibility: visible;
}

.tool-button {
  min-height: 4rem;
  min-width: 4rem;
  border: 2px solid var(--primary);
  border-radius: 0.875rem;
  background: var(--card-bg);
  color: var(--primary);
  cursor: var(--pointer-hand);
  padding: 0.5rem;
}

.tool-button.active {
  background: var(--primary);
  color: var(--card-bg);
}

button:disabled,
#guidedActions #checkButton[aria-disabled="true"] {
  border-color: var(--border);
  background: var(--keyboard-key);
  color: var(--text-muted);
  cursor: not-allowed;
}

.font-choice.unavailable {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Cursor group. Sits straight after Spaces rather than pushed right, so the
   sizes and the swatches fit on one row and the drawer stays one row tall.
   The toolbar's own 40px column gap separates the two halves, matching the gap
   between every other pair of groups. */
.cursor-toolbar-group {
  display: grid;
  grid-template-columns: auto auto;
  align-items: center;
  column-gap: 40px;
}

/* An author display rule beats the browser default for [hidden], so without
   this the hidden attribute set by updateCursorToolAvailability() does nothing
   and the group stays on screen. The spaces group avoids this by toggling a
   class instead of the attribute. */
.cursor-toolbar-group[hidden] {
  display: none;
}

/* Phones and tablets never get the cursor controls. Deliberately NOT a width
   rule: browser zoom shrinks the CSS viewport, so any max-width big enough to
   catch a tablet would also strip the group from a desktop at 200% zoom, which
   this app supports. A bare touch device is caught here; a tablet with a
   trackpad reports hover and a fine pointer and is caught by isPhoneOrTablet
   in script.js instead. */
@media (hover: none) and (pointer: coarse) {
  .cursor-toolbar-group {
    display: none !important;
  }
}

.cursor-size-controls {
  display: flex;
  gap: 14px;
}

.cursor-size-choice {
  display: grid;
  place-items: center;
}

/* Both buttons stay 4rem so they remain equal touch targets — only the arrow
   inside changes size. A genuinely small button would be the hardest thing in
   the drawer to hit, a poor reward for asking for a smaller pointer. */
.cursor-size-glyph path {
  fill: var(--card-bg);
  stroke: var(--primary);
  stroke-width: 4;
  stroke-linejoin: round;
}

.cursor-size-choice.active .cursor-size-glyph path {
  stroke: var(--text-main);
  stroke-width: 3.5;
}

.cursor-size-glyph--normal {
  width: 24px;
  height: 24px;
}

.cursor-size-glyph--bigger {
  width: 42px;
  height: 42px;
}

.cursor-colour-controls {
  display: flex;
  gap: 8px;
}

/* Deliberately px, not rem, and the one place in this drawer that breaks the
   rem rule: the classroom board runs a 67% root, which would shrink a rem-sized
   44px swatch to about 29px — under the minimum touch target. */
.cursor-swatch {
  position: relative;
  width: 44px;
  height: 44px;
  border: 2px solid color-mix(in srgb, var(--text-main) 22%, transparent);
  border-radius: 10px;
  padding: 0;
  cursor: var(--pointer-hand);
}

/* Without its own dark edge the white swatch disappears into the drawer tint. */
.cursor-swatch--white {
  border-color: var(--text-muted);
  background: #ffffff;
}

.cursor-swatch--yellow {
  background: var(--cursor-yellow);
}

.cursor-swatch.active {
  box-shadow: 0 0 0 3px var(--primary);
}

/* Colour alone cannot carry the state — a learner who cannot separate white
   from yellow still has to see which one is on. */
.cursor-swatch-check {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 22px;
  height: 22px;
  fill: none;
  stroke: var(--text-main);
  stroke-width: 3.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
}

.cursor-swatch.active .cursor-swatch-check {
  opacity: 1;
}

/* Fonts mode dims the control groups the current task is not about, at the same
   strength as an unavailable font so the drawer has one "not now" look. The
   buttons stay in the tab order — see updateFontToolAvailability(). */
.tool-button.is-off-task,
.space-toggle-button.is-off-task,
.cursor-swatch.is-off-task {
  opacity: 0.55;
  cursor: not-allowed;
}

.tool-icon,
.tool-label {
  display: block;
  text-align: center;
}

.tool-icon {
  font-size: 1.6rem;
  line-height: 1.1;
  font-weight: 700;
}

.bold-tool-icon {
  font-family: "Inter", "Inter Local", sans-serif;
  font-size: 2rem;
}

.underline-tool-icon {
  font-family: "Inter", "Inter Local", sans-serif;
  font-size: 2rem;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Mirrors the settings drawer's font button so the task points at the thing the
   learner has to tap, without naming the font. */
.task-font-swatch {
  display: inline-block;
  min-width: 2.2em;
  padding: 0.05em 0.25em;
  border: 2px solid var(--primary);
  border-radius: 0.28em;
  color: var(--primary);
  background: var(--card-bg);
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
  vertical-align: baseline;
}

/* 1rem, not 0.82: "Smaller" and "Bigger" label the controls for making text
   readable, so they were the smallest text in the app. The font names keep the
   smaller tooltip size below — they stay hidden by default on purpose. */
.tool-label {
  margin-top: 4px;
  color: var(--text-muted);
  font-size: 1rem;
  font-weight: 700;
}

.setting-status {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 4rem; /* matches .tool-button min-height so the number centers on the button, not the button+label stack */
  margin: 0;
  color: var(--text-main);
  font-size: 1.5rem;
  font-weight: 700;
}

.editor-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 0.875rem;
}

.editor-heading-row h2 {
  margin-bottom: 0;
}

.typing-area-wrap {
  --typing-line-limit: 5;
  position: relative;
  border-radius: var(--radius-lg);
  background: var(--card-bg);
}

.typing-area-wrap.is-correct .typing-area {
  border-color: var(--success-border);
}

.typing-area-wrap.is-incorrect .typing-area {
  border-color: var(--warning-border);
}

.typing-space-mirror,
.typing-hint-mirror,
.typing-area {
  min-height: calc(1 * 1.6em + 2.25rem + 0.375rem);
  max-height: calc(var(--typing-line-limit) * 1.6em + 2.25rem + 0.375rem);
  border: 3px solid var(--field-border);
  border-radius: var(--radius-lg);
  padding: 1.125rem;
  font-family: "Atkinson Hyperlegible Local", sans-serif;
  font-size: 18px;
  line-height: 1.6;
  overflow-wrap: break-word;
  white-space: pre-wrap;
}

.typing-space-mirror,
.typing-hint-mirror {
  position: absolute;
  z-index: 0;
  inset: 0;
  border-color: transparent;
  color: transparent;
  overflow: hidden;
  pointer-events: none;
}

.typing-space-marker {
  color: transparent;
  background: linear-gradient(var(--space-cue), var(--space-cue)) center / 100% 0.8em no-repeat;
}

.typing-hint-marker {
  background: var(--hint-bg);
  border-radius: 2px;
  box-shadow: inset 0 -4px var(--hint-border);
}

/* Stays inline (not inline-block) so the bar's containing block is the text's own
   font box: top/bottom then track the line exactly, whichever font is chosen. */
.typing-hint-marker.is-insertion {
  position: relative;
  display: inline;
  background: transparent;
  border-radius: 0;
  box-shadow: none;
}

.typing-hint-marker.is-insertion::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0.16em;
  min-width: 7px;
  /* Same colour as the marker above, not the activity accent: both mean
     "look here", so both look the same everywhere. Shape carries the
     difference — a block over letters, a bar between them. */
  background: var(--hint-bg);
  box-shadow: inset 0 -4px var(--hint-border);
  border-radius: 2px;
  transform: translateX(-50%);
}

/* text-decoration-skip-ink: by default a browser breaks the underline around
   every descender, so a word full of j/g/y turns the line into scattered
   dashes — worst in Poppins, whose descenders are long. Draw one straight line
   instead. No underline-offset: each font places its own, and pushing it below
   the tails reads as detached. Set on the box, not on `u`: the property
   inherits, so it still applies if an engine's execCommand builds the
   underline from a styled span instead of a <u>. */
.typing-area {
  position: relative;
  z-index: 1;
  background: transparent;
  outline: none;
  overflow: auto;
  text-decoration-skip-ink: none;
}

.typing-area:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 4px var(--focus-glow);
}

/* Clicking in gets the quiet glow above; arriving by keyboard gets the same
   4px ring every button uses. The glow alone is 14% alpha — too faint to be a
   focus indicator on its own for the learners being taught what focus is. */
.typing-area:focus-visible {
  outline: 4px solid var(--focus-blue);
  outline-offset: 3px;
  box-shadow: 0 0 0 2px var(--card-bg);
}

.cursor-practice-mode .typing-area {
  cursor: var(--pointer-text);
  font-weight: 700;
  letter-spacing: 0.04em;
}

.free-practice-mode .editor-heading-row {
  margin-top: 0.25rem;
}

.typing-limit-help,
.typing-input-status {
  margin: 8px 0 0;
  line-height: 1.4;
}

.typing-limit-help {
  color: var(--text-muted);
  font-size: 0.95rem;
}

.typing-input-status {
  color: var(--warning-text);
  font-weight: 700;
}

.typing-input-status:empty {
  display: none;
}

.help-dialog {
  width: min(520px, calc(100% - 32px));
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
  color: var(--text-main);
  box-shadow: var(--shadow);
  padding: 0;
  overflow: hidden;
}

.help-dialog::backdrop {
  background: var(--scrim);
}

.help-dialog-content {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  overflow: hidden;
  padding: 24px;
}

.info-dialog {
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  overflow: hidden;
}

.info-dialog-content {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  overflow: hidden;
}

.info-dialog-body {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 4px;
}

[data-info-dialog-title]:focus-visible {
  border-radius: 4px;
  outline: 3px solid var(--focus-blue);
  outline-offset: 3px;
}

/* Three headings take programmatic focus so screen readers announce them: the
   activity title, the landing page's group title, and an info dialog's title.
   Phone and tablet browsers count that focus as :focus-visible where desktop
   Chrome does not, so a tap drew a ring around the title on touch only. All
   three are listed together because they are one bug, not three - patching only
   the dialog titles left "Text Cursor" still boxed. Hidden only where there is
   no keyboard to need it: a SMART Board reports coarse AND hover, so it keeps
   the ring, and so does every plain desktop. box-shadow is here for
   .activity-group-title, whose ring is a shadow as well as an outline. */
@media (any-pointer: coarse) and (any-hover: none) {
  .activity-title-heading:focus-visible,
  .activity-group-title:focus-visible,
  [data-info-dialog-title]:focus-visible {
    outline: none;
    box-shadow: none;
  }
}

.help-dialog-topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.help-dialog-topbar .audio-button {
  flex: 0 0 auto;
  white-space: nowrap;
}

.help-dialog-topbar h2 {
  margin: 0;
  font-size: 1.5rem;
}

.help-dialog-heading {
  display: flex;
  align-items: center;
  gap: 12px;
}

.dialog-icon {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
}

.dialog-icon svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.dialog-icon--help {
  background: var(--primary-soft);
  color: var(--primary);
}

.dialog-icon--leave {
  background: var(--warning-bg);
  color: var(--warning-text);
}

.dialog-icon--info {
  background: var(--primary-soft);
  color: var(--primary);
}

.help-dialog-body {
  font-size: 20px;
  line-height: 1.5;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 4px;
}

.help-dialog-body p {
  margin: 0 0 12px;
}

.help-dialog-body p:last-child {
  margin-bottom: 0;
}

.info-dialog-list {
  margin: 0 0 12px;
  padding-left: 1.35em;
}

.info-dialog-list li {
  margin-bottom: 8px;
}

.info-dialog-list li:last-child {
  margin-bottom: 0;
}

.info-dialog-list li::marker {
  color: var(--primary);
}

.help-dialog-body .help-list {
  margin: 0;
  padding-left: 1.35em;
}

.help-dialog-body .help-list li {
  margin: 0 0 12px;
}

.help-dialog-body .help-list li:last-child {
  margin-bottom: 0;
}

.help-dialog-body .help-list li::marker {
  color: var(--primary);
}

.help-dialog-body .help-sublist {
  margin: 8px 0 0;
  padding-left: 1.35em;
}

.help-dialog-body .help-sublist li {
  margin-bottom: 4px;
}

.help-inline-icon-wrap {
  display: inline-flex;
  align-items: center;
  vertical-align: -0.15em;
  color: var(--primary);
}

.help-inline-icon {
  width: 1.25em;
  height: 1.25em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.help-inline-play {
  font-size: 0.85em;
  color: var(--primary);
}

.help-dialog-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 20px;
}

.help-dialog-actions .primary-button {
  min-width: 6rem;
}

.leave-practice-dialog .help-dialog-actions {
  flex-wrap: wrap;
  gap: 12px;
}

.leave-practice-dialog .help-dialog-actions .primary-button,
.leave-practice-dialog .help-dialog-actions .secondary-button {
  min-width: 9.375rem;
}

.info-dialog-body a {
  color: var(--primary);
  font-weight: 700;
}

.about-elevenlabs-logo {
  display: block;
  margin: 12px auto 0;
  max-width: 180px;
  width: 100%;
  height: auto;
}

.about-ircc-logo {
  display: block;
  margin: 20px auto 0;
  max-width: 420px;
  width: 100%;
  height: auto;
  transform: translateX(-10px);
}

#activityCard .action-row {
  margin-top: 0.75rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

#activityCard .action-row.has-success {
  margin-top: 1.25rem;
  padding-top: 0;
  border-top: 0;
}

#activityCard .action-row.has-check-feedback {
  margin-top: 1.25rem;
  padding-top: 0;
  border-top: 0;
}

#activityCard > .feedback-message:empty {
  min-height: 0;
  margin-top: 0;
}

.hidden {
  display: none;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 640px) {
  .app-shell {
    width: min(100% - 20px, 1100px);
    padding-top: 14px;
  }

  #taskTitle {
    font-size: 2.25rem;
  }

  .task-topline {
    align-items: flex-start;
  }

  /* Stacks label over count to stay narrow, but sits in the card's top-right
     corner beside Back rather than taking a full row under it. */
  .task-status {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
    /* Capped so the longest mode names wrap here instead of shoving the whole
       block onto its own row below Back. */
    max-width: 58%;
    margin-left: auto;
    line-height: 1.25;
    text-align: right;
  }

  .task-status .task-count::before {
    content: none;
    margin: 0;
  }

  #guidedActions #checkButton:not(.hidden) {
    padding-inline: 8px;
    font-size: 1.5rem;
  }

  #guidedActions #hintButton:not(.hidden) {
    gap: 0.375rem;
    padding-inline: 8px;
    font-size: 1.5rem;
  }

  #guidedActions #hintButton .hint-icon {
    width: 1.25rem;
    height: 1.25rem;
  }

  .hint-button-label {
    line-height: 1.15;
  }

  .mode-card,
  .task-card {
    padding: 14px;
  }

  .activity-title-layout {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .activity-title-identity {
    padding: 10px 6px 24px;
  }

  .activity-title-number {
    margin-bottom: 18px;
    font-size: 3rem;
  }

  .activity-title-heading {
    font-size: 2rem;
  }

  .activity-title-instructions {
    border-top: 1px solid var(--border);
    border-left: 0;
    padding: 24px 6px 8px;
  }

  .activity-title-steps,
  .activity-title-intro p {
    font-size: 1.125rem;
  }

  .activity-title-steps {
    gap: 14px;
  }

  .intro-view .mode-card {
    padding: 0;
  }

  .linc-suite-logo-mark {
    width: min(108px, 40vw);
  }

  .footer-links {
    width: 100%;
    justify-content: center;
  }

  .footer-link-separator {
    display: none;
  }

  .intro-view .intro-header {
    min-height: auto;
    margin-bottom: 28px;
    padding: 0;
  }

  .intro-heading {
    gap: 10px;
  }

  .activity-groups {
    gap: 16px;
  }

  .activity-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
  }

  .activity-tile {
    grid-template-columns: max-content minmax(0, 1fr) 24px;
    column-gap: 8px;
    min-height: 140px;
    padding-inline: 10px;
  }

  .activity-tile-number {
    font-size: 2.125rem;
  }

  .activity-tile-title {
    font-size: 0.9375rem;
  }

  .activity-tile-status {
    gap: 4px;
    padding: 2px 8px;
    font-size: 0.875rem;
  }

  .activity-tile-status-symbol {
    width: 16px;
    height: 16px;
    font-size: 0.875rem;
  }

  /* Still larger than the mobile tile titles, but 1.5rem would not fit on one
     line in a 172px tile. */
  .free-practice-tile .activity-tile-title {
    font-size: 1.125rem;
  }

  .free-practice-tile-description {
    font-size: 0.9375rem;
  }

  .mode-icon-tile {
    width: 48px;
    height: 48px;
  }

  .editor-toolbar {
    padding: 14px 0 12px;
    margin-top: 26px;
  }

  .toolbar {
    flex-direction: column;
    gap: 20px;
  }

  .toolbar-group {
    width: 100%;
  }

  .font-toolbar-group .toolbar-group-controls {
    flex-wrap: wrap;
  }

  .completion-actions .primary-button,
  .completion-actions .secondary-button {
    width: 100%;
    flex: 1 1 100%;
  }

  /* At 375px "Practice something else" wraps to two lines at 1.5rem and the
     secondary ends up TALLER than the primary, inverting the hierarchy the
     stack exists to create. One step down keeps it on one line. */
  .completion-actions .secondary-button {
    font-size: 1.25rem;
  }

  /* Card padding drops to 14px here, so both gaps need 4px more to stay level
     with the 36px below Hear message. */
  .completion-screen {
    padding-top: 22px;
    padding-bottom: 22px;
  }

  .leave-practice-dialog .help-dialog-actions .primary-button,
  .leave-practice-dialog .help-dialog-actions .secondary-button {
    width: 100%;
  }

  .activity-view .app-header {
    align-items: center;
    padding-top: 0;
  }

  .activity-view .header-actions {
    width: auto;
    justify-content: flex-end;
    gap: 10px;
  }

  .home-button span,
  .settings-button span,
  .help-button span {
    display: none;
  }

  .home-button,
  .settings-button,
  .help-button {
    gap: 0;
    min-width: 3rem;
    padding: 0.5rem;
    justify-content: center;
  }

  /* One button on the left and two on the right leave a centred logo with
     78px of clearance one side and 14px the other, so it reads as off-centre
     even though it is not. It gets its own full-width row below the buttons
     (see the grid-template-areas above): that centres correctly whether one,
     two or three buttons are showing. */
  .activity-view .activity-logo {
    justify-self: center;
    width: auto;
    height: 26px;
    /* The header's own 12px row gap sits above; 6px more makes the space above
       the logo match the 18px below it. */
    margin: 6px 0 0;
  }

}

@media (max-width: 340px) {
  .activity-grid {
    grid-template-columns: 1fr;
  }

  .activity-tile {
    min-height: 120px;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* A checkmark on a button that restores the sentence says the wrong thing, so
   the icon goes while the Fix Mistakes modes are offering Try again. */
#checkButton.is-retry .check-icon {
  display: none;
}
