:root {
  color-scheme: light;
  --color-bg: #f3f4f6;
  --color-panel: #ffffff;
  --color-border: #d1d5db;
  --color-text: #111827;
  --color-muted: #6b7280;
  --color-accent: #2563eb;
  --color-correct: #16a34a;
  --color-incorrect: #dc2626;
  --radius: 10px;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* --app-bg is the reader's chosen background colour; the token behind it is
     the default the page ships with. An image or camera feed, when chosen, is
     drawn by .bg-layer on top of this rather than replacing it. */
  background: var(--app-bg, var(--color-bg));
  color: var(--color-text);
  line-height: 1.4;
}

.app-header {
  position: relative;
  padding: 1.25rem 3.5rem 0.5rem;
  text-align: center;
}

.app-header h1 {
  margin: 0 0 0.25rem;
  font-size: 1.6rem;
}

/* The only way in to the pools and the cube controls. Parked in the header
   corner so the page below it is nothing but the question and the answer. */
.settings-toggle {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  line-height: 1;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-panel);
  color: var(--color-text);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease, transform 0.2s ease;
}

.settings-toggle:hover {
  background: #eef2ff;
}

.settings-toggle[aria-expanded="true"] {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: #fff;
  transform: rotate(60deg);
}

/* Screen-reader-only: keeps a heading in the outline for panels whose visible
   title has moved elsewhere (the question now titles the net). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.app-main {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  max-width: 960px;
  margin: 0 auto;
  padding: 1rem;
}

.settings-subheading {
  margin: 1.25rem 0 0.5rem;
  font-size: 1rem;
}

/* --panel-bg carries the Box transparency setting, which app.js resolves to a
   concrete rgba() rather than leaving CSS to compute an alpha from a number:
   a custom property holding a bare number cannot be interpolated into rgba()
   without calc() gymnastics, and the token fallback below has to stay a plain
   colour for the signed-out default anyway. */
.panel {
  background: var(--panel-bg, var(--color-panel));
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1rem;
}

.panel h2 {
  margin-top: 0;
  font-size: 1.1rem;
}

.hint {
  color: var(--color-muted);
  margin-top: -0.5rem;
  font-size: 0.9rem;
}

/* --- Character controls --- */

.pool-fieldset {
  border: none;
  padding: 0;
  margin: 0 0 0.75rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.pool-fieldset legend {
  font-weight: 600;
  padding: 0;
  margin-bottom: 0.25rem;
}

.pool-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;
}

.pool-label input {
  width: 20px;
  height: 20px;
}

.pool-error {
  color: var(--color-incorrect);
  min-height: 1.2em;
  font-size: 0.9rem;
}

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.btn {
  min-height: 44px;
  padding: 0.6rem 1rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: #f9fafb;
  color: var(--color-text);
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.1s ease, opacity 0.15s ease;
}

.btn:active {
  transform: scale(0.97);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn--primary {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: white;
}

.btn--primary:disabled {
  background: #93b4f2;
  border-color: #93b4f2;
}

/* --- Cube net editor --- */

.panel--net h2 {
  text-align: center;
  font-size: 1.15rem;
  transition: color 0.2s ease;
}

/* The heading and the "New Puzzle" button that follows it. The gap below the
   pair lives here rather than on the heading, so the button cannot sit lower
   than the sentence it belongs to. It wraps to its own line when the heading
   fills the width, which on a phone it does. */
.net-heading-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

/* Sized to sit inside a line of text rather than in a row of its own. */
.btn--heading {
  min-height: 32px;
  padding: 0.25rem 0.7rem;
  font-size: 0.85rem;
  white-space: nowrap;
}

/* Net and folded cube occupy the same footprint, so folding never moves the
   buttons or reflows the page. Sized to the net, since the net is the larger
   of the two and the cube is drawn to fit inside it. */
.net-stage {
  --net-cell: clamp(56px, 18vw, 92px);
  position: relative;
  width: max-content;
  margin: 0 auto;
}

.net-grid {
  width: max-content;
  display: grid;
  /* Tracks are one border-width short of a full cell, while the squares
     themselves stay a full cell wide. Each square therefore overlaps its
     neighbour by exactly the 1px they would otherwise both draw, so every fold
     line in the net is a single hairline instead of two stacked borders. */
  grid-template-columns: repeat(4, calc(var(--net-cell) - 1px));
  grid-template-rows: repeat(3, calc(var(--net-cell) - 1px));
  grid-template-areas:
    ".    u    .    ."
    "l    f    r    b"
    ".    d    a    a";
  /* No gap: the squares butt up against each other so the net reads as one
     connected sheet of paper, the way it would before being folded. */
  gap: 0;
}

.net-input {
  width: var(--net-cell);
  height: var(--net-cell);
  text-align: center;
  /* Character glyph should fill ~80-85% of the square's height. */
  font-size: calc(var(--net-cell) * 0.82);
  line-height: 1;
  font-weight: 700;
  padding: 0;
  /* Same hairline as .cube-3d-face's edges. Square corners, so touching cells
     meet cleanly with no rounded notches between them. The colour follows the
     face: dark fills get a light edge instead — see colors.js. */
  border: 1px solid var(--cube-edge-color, rgba(17, 24, 39, 0.6));
  border-radius: 0;
  /* The cube's colorway, so a character looks the same in the net as it does
     once folded — the color pickers and the Black and White toggle all feed
     these. The face color is taken opaque: transparency is a property of the
     stacked 3D faces, not of a flat net. Fallbacks match the cube's defaults. */
  background: var(--cube-face-color, #4682ff);
  color: var(--cube-font-color, #ffffff);
  /* WebKit paints form-control text with -webkit-text-fill-color, which wins
     over `color` wherever the UA sets it — on iOS especially. Without this the
     background follows the Cube color picker while the character stubbornly
     stays white. */
  -webkit-text-fill-color: var(--cube-font-color, #ffffff);
  opacity: 1; /* iOS dims disabled/readonly-looking inputs; keep the glyph solid. */
}

.net-input:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.net-input--u { grid-area: u; }
.net-input--l { grid-area: l; }
.net-input--f { grid-area: f; }
.net-input--r { grid-area: r; }
.net-input--b { grid-area: b; }
.net-input--d { grid-area: d; }

/* Fold, in the box's bottom-right corner: it shares the bottom row with the D
   square rather than starting a line of its own. The grid's tracks are all
   explicitly sized, so hiding the squares while folded leaves this cell exactly
   where it is. */
.net-actions {
  grid-area: a;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  gap: 0.35rem;
  padding-left: 0.5rem;
  /* Above the folded cube, which is drawn across the whole stage. */
  position: relative;
  z-index: 2;
}

.btn--net {
  min-height: 36px;
  padding: 0.3rem 0.6rem;
  font-size: clamp(0.7rem, 2.4vw, 0.9rem);
  background: var(--color-panel);
  white-space: nowrap;
}

/* Folded: the squares go, the box stays. */
.net-grid--folded .net-input {
  visibility: hidden;
}

/* --- The fold between net and cube --- */

/* Shown only while the transition plays. It occupies exactly the box the cube
   occupies, and shares its perspective, so the assembly it hands over to the
   real cube lands where the real cube already is. */
.fold-overlay {
  position: absolute;
  inset: 0 var(--net-cell) 0 0;
  /* Level with the cube it stands in for, and so below the Fold button that
     sits at z-index 2 — pressing Fold must never be painted over. */
  z-index: 1;
  perspective: 900px;
  /* A picture of a transition, never the thing you drag. */
  pointer-events: none;
}

.fold-scene {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  transition: transform var(--fold-ms, 900ms) ease;
}

.fold-face {
  position: absolute;
  /* Children are positioned against their parent face's own box, so flush with
     it by default; the offsets below step them one whole cell off it. Only F,
     whose parent is the zero-sized scene, needs centring on the origin. */
  top: 0;
  left: 0;
  width: var(--fold-cell);
  height: var(--fold-cell);
  display: flex;
  align-items: center;
  justify-content: center;
  transform-style: preserve-3d;
  backface-visibility: visible;
  /* The same hairline the net squares and the cube's faces both draw. */
  border: 1px solid var(--cube-edge-color, rgba(17, 24, 39, 0.6));
  color: var(--cube-font-color, #ffffff);
  font-weight: 700;
  line-height: 1;
  transition:
    transform var(--fold-ms, 900ms) ease,
    background-color var(--fold-ms, 900ms) ease,
    font-size var(--fold-ms, 900ms) ease;
}

.fold-glyph {
  color: var(--cube-font-color, #ffffff);
  -webkit-text-fill-color: var(--cube-font-color, #ffffff);
}

/* F straddles the scene's origin, so the assembly turns about the cube's own
   centre once F has stepped forward half a cell. */
.fold-face--f {
  top: calc(var(--fold-cell) / -2);
  left: calc(var(--fold-cell) / -2);
}

/* Each child sits one whole cell off its parent, pivoting on the edge they
   share. Together these reproduce the net's cross exactly. */
.fold-face--u { top: calc(var(--fold-cell) * -1); transform-origin: 50% 100%; }
.fold-face--d { top: var(--fold-cell); transform-origin: 50% 0%; }
.fold-face--l { left: calc(var(--fold-cell) * -1); transform-origin: 100% 50%; }
.fold-face--r { left: var(--fold-cell); transform-origin: 0% 50%; }
.fold-face--b { left: var(--fold-cell); transform-origin: 0% 50%; }

/* Used for the one frame that plants the starting state. */
.fold-overlay--instant .fold-scene,
.fold-overlay--instant .fold-face {
  transition: none;
}

/* --- 3D cube --- */

.cube-scene {
  position: relative;
  width: 100%;
  max-width: 320px;
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  perspective: 900px;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Inside the net box the cube is an overlay filling the net's footprint, not a
   square of its own — its size comes from the box it replaces. */
.net-stage .cube-scene {
  position: absolute;
  /* Stops one column short of the right edge: that last column is where the
     Fold button sits, so the cube never has to share space with it. */
  inset: 0 var(--net-cell) 0 0;
  width: auto;
  max-width: none;
  aspect-ratio: auto;
  margin: 0;
  z-index: 1;
}

.cube-3d {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
}

.cube-3d-face {
  position: absolute;
  top: calc(var(--cube-half, 80px) * -1);
  left: calc(var(--cube-half, 80px) * -1);
  width: var(--cube-size, 160px);
  height: var(--cube-size, 160px);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--cube-size, 160px) * var(--cube-glyph-scale, 0.5));
  font-weight: 700;
  background: var(--cube-face-bg, rgba(70, 130, 255, 0.75));
  border: 1px solid var(--cube-edge-color, rgba(17, 24, 39, 0.6));
  color: var(--cube-font-color, #ffffff);
  backface-visibility: visible;
  box-sizing: border-box;
}

.cube-3d-glyph {
  display: inline-block;
  /* Stated outright rather than inherited from the face, so nothing between the
     two can intercept it. */
  color: var(--cube-font-color, #ffffff);
  -webkit-text-fill-color: var(--cube-font-color, #ffffff);
}

/* A <fieldset>, so the browser's own disabled state gates all four controls at
   once. The UA border and padding are cleared — the grouping is semantic here,
   not visual. */
.cube-appearance {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 1rem 0 0;
  padding: 0;
  border: none;
  justify-content: center;
}

/* Locked controls read as unavailable rather than broken: dimmed, and the
   cursor says so on the way past. */
.cube-appearance:disabled {
  opacity: 0.45;
}

.cube-appearance:disabled .appearance-label {
  cursor: not-allowed;
}

.lock-note {
  margin: 0.5rem 0 0;
  text-align: center;
  font-size: 0.85rem;
  color: var(--color-muted);
}

.appearance-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--color-muted);
}

.appearance-label input[type="range"] {
  width: 120px;
  min-height: 44px;
}

.appearance-label input[type="color"] {
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: none;
}

/* Square, and filled with the spectrum rather than labelled: the button deals a
   brand-new colourway, and a swatch of every hue says that faster than a word
   would. Sized to match the colour inputs beside it so the row stays even. */
.rainbow-btn {
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  cursor: pointer;
  background: conic-gradient(
    from 0deg,
    #ff004d, #ff8a00, #ffe600, #38d430,
    #00c2d1, #2b6fff, #8b2bff, #ff00a8, #ff004d
  );
}

.rainbow-btn:hover:not(:disabled) {
  filter: brightness(1.08);
}

.rainbow-btn:disabled {
  filter: grayscale(0.8);
  opacity: 0.45;
  cursor: not-allowed;
}

/* The copy that lives in the net box's corner. Sized to the buttons it shares
   that corner with rather than to the colour inputs in the settings panel. */
.rainbow-btn--net {
  width: 36px;
  height: 36px;
  flex: none;
}

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

/* --- Page background --- */

/* Image and camera are painted by fixed layers behind the page rather than on
   <body>, because a camera feed is a <video> and cannot be a background-image
   at all. Both sit under everything and never take a pointer event; the solid
   colour stays on <body> underneath, so it still shows while a chosen image is
   loading or a camera is being granted. */
.bg-layer {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.bg-layer[hidden] {
  display: none;
}

#bg-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.background-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-end;
  justify-content: center;
  margin: 1rem 0 0;
  padding: 0;
  border: none;
}

.background-controls:disabled {
  opacity: 0.45;
}

/* The file input is driven by its own button, so the control itself never has
   to be seen — but it stays in the DOM and focusable rather than display:none,
   which would drop it from the accessibility tree entirely. */
.bg-file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.bg-mode-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  justify-content: center;
}

/* A premium-only control still shows, so the feature is discoverable; it is the
   press that is refused, with the reason in the title. */
.btn--locked {
  opacity: 0.55;
}

.bg-message {
  width: 100%;
  margin: 0.25rem 0 0;
  text-align: center;
  font-size: 0.85rem;
  color: var(--color-muted);
}

.bg-message--error {
  color: var(--color-incorrect);
}

/* --- Account --- */

.account-blurb {
  margin: 0 0 0.5rem;
  color: var(--color-text);
}

.account-benefits {
  margin: 0 0 1rem;
  padding-left: 1.2rem;
  color: var(--color-muted);
  font-size: 0.9rem;
}

.account-benefits li {
  margin-bottom: 0.25rem;
}

.account-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-end;
}

.account-field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--color-muted);
  flex: 1 1 220px;
}

.account-field input {
  min-height: 44px;
  padding: 0.5rem 0.7rem;
  font-size: 1rem;
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: #fff;
}

.account-field input:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.account-actions {
  flex: 1 1 100%;
}

/* Reserves its line whether or not there is a message, so submitting does not
   shunt the buttons down the page. */
.account-message {
  min-height: 1.2em;
  margin: 0.6rem 0 0;
  font-size: 0.9rem;
  color: var(--color-muted);
}

.account-message--error {
  color: var(--color-incorrect);
}

.account-message--success {
  color: var(--color-correct);
}

.account-identity {
  margin: 0 0 0.75rem;
}

.score-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
  gap: 0.6rem;
  margin: 0 0 1rem;
}

.score-cell {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.5rem;
  text-align: center;
}

.score-cell dt {
  font-size: 0.75rem;
  color: var(--color-muted);
}

.score-cell dd {
  margin: 0.2rem 0 0;
  font-size: 1.2rem;
  font-weight: 700;
}

.field-hint {
  font-weight: 400;
  opacity: 0.8;
}

.account-name-form {
  margin-bottom: 0.25rem;
}

/* --- Leaderboard --- */

.leaderboard-note {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
  color: var(--color-muted);
}

/* The table can outgrow a phone; it scrolls inside its own box rather than
   pushing the page sideways. */
.leaderboard {
  overflow-x: auto;
}

.leaderboard table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.leaderboard th,
.leaderboard td {
  padding: 0.45rem 0.6rem;
  text-align: right;
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}

.leaderboard th:nth-child(2),
.leaderboard td:nth-child(2) {
  text-align: left;
  white-space: normal;
  /* A 24-character name with no spaces would otherwise widen the whole table. */
  word-break: break-word;
}

.leaderboard th {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-muted);
}

.leaderboard tbody tr:last-child td {
  border-bottom: none;
}

/* The reader's own row, so it can be found without reading every name. */
.leaderboard-row--you {
  background: #eff6ff;
}

.leaderboard-row--you td {
  font-weight: 700;
}

/* A row pulled in from below the cut sits apart from the top of the board. */
.leaderboard-row--detached td {
  border-top: 2px dashed var(--color-border);
}

.leaderboard-empty {
  margin: 0;
  color: var(--color-muted);
  font-size: 0.9rem;
}

.leaderboard-actions {
  margin-top: 0.75rem;
}

.app-footer {
  max-width: 960px;
  margin: 0 auto;
  padding: 0 1rem 1.25rem;
  text-align: right;
  color: var(--color-muted);
  font-size: 0.85rem;
}

/* --- Quiz answer options --- */

/* All four options on one line, so they can be compared at a glance without
   scrolling between them. Narrow screens fall back to two rows of two rather
   than four cramped slivers. */
.answer-options {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

@media (max-width: 700px) {
  .answer-options {
    grid-template-columns: repeat(2, 1fr);
  }
}

.answer-option {
  display: block;
  position: relative; /* anchors the A/B/C/D badge to this box's own corner */
  width: 100%;
  padding: 0.5rem;
  border: 3px solid var(--color-border);
  border-radius: var(--radius);
  background: #fff;
  cursor: pointer;
  min-height: 44px;
  transition: border-color 0.2s ease, background-color 0.2s ease, transform 0.1s ease;
  font: inherit;
  color: inherit;
}

.answer-option:active {
  transform: scale(0.98);
}

.answer-option:disabled {
  cursor: default;
}

/* The option cubes are canvases produced by exportImage.js — the same drawing
   "Straighten and Copy" puts on the clipboard. Their colors and line weights
   come from that renderer, not from here; all this does is fit them to the box.
   Every orientation is a corner view and crops to the same size, so scaling
   them all to the box width leaves them at one consistent scale. */
.answer-cube {
  display: block;
  width: 100%;
  height: auto;
}

/* The option's letter, in a filled disc in its top-left corner. Lifted above the
   drawing so it stays legible over the cube, which is scaled to the full width
   of the box and can reach the corner. */
.answer-badge {
  position: absolute;
  top: 0.4rem;
  left: 0.4rem;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 999px;
  background: var(--color-text);
  color: var(--color-panel);
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1;
}

/* The badge follows the box: once an option has a verdict, its letter is worn
   in that verdict's colour rather than staying neutral against it. */
.answer-option--selected .answer-badge {
  background: var(--color-accent);
}

.answer-option--correct .answer-badge {
  background: var(--color-correct);
}

.answer-option--incorrect .answer-badge {
  background: var(--color-incorrect);
}

.answer-option--selected {
  border-color: var(--color-accent);
}

.answer-option--correct {
  border-color: var(--color-correct);
  background: #f0fdf4;
}

.answer-option--incorrect {
  border-color: var(--color-incorrect);
  background: #fef2f2;
}

.answer-explanation {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--color-muted);
  white-space: pre-line;
  text-align: left;
  /* Deliberately not transitioned. A `transition: color` here does not merely
     slow the change down — the colour never arrives at all, and the explanation
     stays muted while the flag is on. The flag is meant to be immediate anyway. */
}

/* Tapping a wrong option after the reveal turns the model to that option's
   corner; this is the box answering back, and it stays on until another option
   is tapped or a new question starts.
   Deliberately an outline rather than a thicker border: a border would eat into
   the content box and shrink the drawing, and the flinch would read as the cube
   moving. An outline is drawn outside the box entirely, so nothing inside it
   shifts by a pixel. */
.answer-option--flagged-wrong {
  outline: 4px solid var(--color-incorrect);
  outline-offset: 0;
}

.answer-option--flagged-wrong .answer-explanation {
  color: var(--color-incorrect);
  font-weight: 700;
}

/* The running build, small enough to ignore and legible enough to read back
   when something looks wrong. */
.build-id {
  display: inline-block;
  margin-left: 0.5rem;
  font-size: 0.75rem;
  opacity: 0.6;
  font-variant-numeric: tabular-nums;
}

.feedback-summary {
  min-height: 1.4em;
  font-weight: 700;
  font-size: 1.1rem;
}

.feedback-summary--correct {
  color: var(--color-correct);
}

.feedback-summary--incorrect {
  color: var(--color-incorrect);
}
