@font-face {
  font-family: "Roboto";
  /* woff2 first (218 KB), ttf kept as the fallback (477 KB) for any browser
     that cannot read it. Browsers download only the first format they support. */
  src: url("../assets/fonts/Roboto-Variable.woff2") format("woff2"),
       url("../assets/fonts/Roboto-Variable.ttf") format("truetype");
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 75% 100%;
  font-display: swap;
}

:root {
  color-scheme: light;
  --ink: oklch(24% 0.022 245);
  --muted: oklch(48% 0.022 245);
  --line: oklch(84% 0.018 235);
  --line-strong: oklch(65% 0.03 235);
  --surface: oklch(98.5% 0.006 90);
  --panel: oklch(96% 0.012 215);
  --page: oklch(99.5% 0.004 90);
  --accent: oklch(48% 0.12 235);
  --accent-hover: oklch(42% 0.12 235);
  --accent-soft: oklch(93% 0.035 225);
  --success: oklch(42% 0.10 155);
  --success-soft: oklch(94% 0.035 155);
  --warning: oklch(43% 0.10 75);
  --warning-soft: oklch(95% 0.04 80);
  --error: oklch(47% 0.17 28);
  --error-hover: oklch(41% 0.16 28);
  --error-soft: oklch(95% 0.035 28);
  --focus: oklch(62% 0.17 75);
  --radius-sm: 0.4rem;
  --radius-md: 0.7rem;
  --preview-backdrop: oklch(93.5% 0.014 235);
  /* The three shadows the system allows, and the only three. Everything else in
     the chrome is flat with a 1px hairline. */
  --shadow-page: 0 1.4rem 3.2rem oklch(25% 0.025 245 / 0.22), 0 0 0 1px oklch(55% 0.02 245 / 0.18);
  --shadow-popover: 0 0.9rem 2rem oklch(25% 0.025 245 / 0.18);
  --shadow-dialog: 0 1.4rem 4rem oklch(20% 0.025 245 / 0.3);
  font-family: "Roboto", "Segoe UI", Arial, sans-serif;
  font-synthesis: none;
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

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

html {
  background: var(--panel);
}

body {
  /* Deliberately px, not rem: at 200% text size a rem floor becomes 640px and
     forces horizontal scrolling on a narrow screen (WCAG 1.4.4). */
  min-width: 320px;
  margin: 0;
  color: var(--ink);
  background: var(--panel);
  /* Column so the footer settles at the foot of a short viewport instead of
     pushing past it. The workspace takes the slack (see .workspace). */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

button,
input,
select {
  font: inherit;
}

button,
select,
input {
  border-radius: var(--radius-sm);
}

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

.app-header {
  min-height: 5rem;
  padding: 1rem clamp(1rem, 2.5vw, 2rem);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}

.app-logo {
  width: auto;
  height: 2.6rem;
  flex: none;
}

/* Keeps the title block left of the badge now that the logo is the first item. */
.app-header > div {
  margin-right: auto;
}

.app-header h1,
.preview-heading-row h2,
.controls h2,
.controls h3,
.worksheet-page h2,
.preview-heading-row p {
  margin: 0;
}

.app-header h1 {
  margin-top: 0.15rem;
  font-size: 1.25rem;
  line-height: 1.2;
}

/* One micro-label style for both the preview eyebrow and the Step labels.
   Kept at 0.8rem: uppercase and letter-spaced text gets hard to read below
   about 12px, and the Step labels carry the workflow order. */
.eyebrow,
.step-label {
  color: var(--accent);
  font-size: 0.8rem;
  font-weight: 750;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.help-button {
  flex: none;
}

.help-button-mark {
  width: 1.15rem;
  height: 1.15rem;
  display: grid;
  place-items: center;
  border: 1px solid currentColor;
  border-radius: 50%;
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1;
}

.preview-document-meta {
  display: inline-flex;
  align-items: center;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
}

.page-count,
.page-size {
  padding: 0.3rem 0.55rem;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 700;
  white-space: nowrap;
}

.page-count {
  color: var(--ink);
}

.page-size {
  border-left: 1px solid var(--line);
}

.workspace {
  /* basis auto, never 0: print gives this an explicit height, and the Life Goals
     multi-page rules release it to auto — a 0 basis would collapse both. */
  flex: 1 0 auto;
  display: grid;
  grid-template-columns: minmax(19rem, 23rem) minmax(0, 1fr);
}

/* Always-visible attribution. The dialog behind the link carries the detail; the
   notice itself must not depend on finding a control. */
.app-footer {
  padding: 0.85rem clamp(1rem, 2.5vw, 2rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 1rem;
  border-top: 1px solid var(--line);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.85rem;
}

.app-footer p {
  margin: 0;
}

.app-footer-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font-size: inherit;
  font-weight: 650;
  text-decoration: underline;
  cursor: pointer;
}

/* The Build/Preview toggle is a phone affordance only; desktop keeps both panels
   side by side, so it stays out of the grid entirely (display:none, not a cell). */
.view-toggle {
  display: none;
}

/* Likewise the in-preview print button: desktop prints from the controls panel.
   Specificity matches `.button` (which sets inline-flex) so this hide wins. */
.button.print-button-mobile {
  display: none;
}

.controls {
  padding: 1.5rem clamp(1rem, 2.5vw, 1.75rem) 2rem;
  border-right: 1px solid var(--line);
  background: var(--surface);
  /* Own scroll so the preview stays in view while working down a long theme
     list. The panel sticks to the viewport once the header scrolls off. */
  position: sticky;
  top: 0;
  align-self: start;
  max-height: 100vh;
  overflow-y: auto;
}

.controls-intro h2 {
  font-size: 1.35rem;
}

.controls-intro p,
.section-help {
  max-width: 65ch;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.controls-intro p {
  margin: 0.45rem 0 1.35rem;
}

.field-group label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.95rem;
  font-weight: 700;
}

/* The detail form stacks several fields, so space each label/input pair. */
#detail-controls {
  display: grid;
  gap: 1.15rem;
}

/* The bundled Roboto file has no italic face and the app disables synthetic
   styles globally, so allow synthesis only for these example placeholders. */
#detail-controls input::placeholder {
  font-style: italic;
  font-synthesis: style;
}

.workflow-stage + .workflow-stage {
  margin-top: 1.7rem;
  padding-top: 1.15rem;
  border-top: 1px solid var(--line);
}

.step-label {
  margin: 0 0 0.3rem;
}

.field-group select,
.field-group input,
.theme-select {
  width: 100%;
  min-height: 2.75rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line-strong);
  color: var(--ink);
  background: var(--page);
}

/* Selects need extra right room for the native dropdown arrow. */
.field-group select,
.theme-select {
  padding-right: 2rem;
}

.field-group select:hover,
.field-group input:hover,
.theme-select:hover {
  border-color: var(--accent);
}

.field-group input[aria-invalid="true"],
.field-group select[aria-invalid="true"],
.theme-select[aria-invalid="true"] {
  border-color: var(--error);
  box-shadow: 0 0 0 1px var(--error);
}

.section-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.section-heading h3 {
  font-size: 1rem;
}

.section-heading span {
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 700;
}

.section-help {
  margin: 0.3rem 0 0.7rem;
}

/* Two stacked choices rather than a segmented row: the labels carry a topic count
   and a page count each, so side by side they would either wrap or shrink the
   text below the control panel's 19rem minimum. */
.topic-count-options {
  display: grid;
  gap: 0.5rem;
}

.topic-count-option {
  min-height: 2.75rem;
  padding: 0.6rem 0.75rem;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--page);
  font-size: 0.95rem;
  font-weight: 700;
}

.topic-count-option:hover {
  border-color: var(--accent);
}

/* The native radio keeps the keyboard and screen-reader behaviour; only the box
   around it is styled. :has lets the chosen one read as chosen without JS. */
.topic-count-options:has(:checked) .topic-count-option:has(:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.topic-count-option input {
  accent-color: var(--accent);
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
}

/* A consequence of the choice just made, not help text for making it — so it
   sits below the options and carries the ochre the warning dialogs use, rather
   than the muted grey of `.section-help` above them. */
.topic-count-note {
  max-width: 65ch;
  margin: 0.7rem 0 0;
  padding: 0.6rem 0.75rem;
  border-left: 3px solid oklch(70% 0.12 75);
  border-radius: var(--radius-sm);
  background: oklch(96.5% 0.03 85);
  color: oklch(35% 0.05 75);
  font-size: 0.9rem;
  line-height: 1.5;
}

.theme-control {
  margin: 0;
  padding: 0.85rem 0;
  border: 0;
  border-top: 1px solid var(--line);
}

.theme-control:last-child {
  border-bottom: 1px solid var(--line);
}

.theme-control legend {
  width: 100%;
  padding: 0 0 0.45rem;
  font-size: 0.92rem;
  font-weight: 750;
}

.theme-input-row {
  display: grid;
  grid-template-columns: 2.7rem minmax(0, 1fr);
  align-items: center;
  gap: 0.55rem;
}

.theme-picker {
  position: relative;
  min-width: 0;
}

.theme-picker-input {
  appearance: none;
  padding-right: 2.4rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position:
    calc(100% - 1rem) 50%,
    calc(100% - 0.72rem) 50%;
  background-size: 0.32rem 0.32rem, 0.32rem 0.32rem;
  background-repeat: no-repeat;
}

.theme-picker-input::-webkit-search-cancel-button {
  appearance: none;
}

.theme-picker-listbox {
  position: absolute;
  z-index: 30;
  top: calc(100% + 0.35rem);
  right: 0;
  left: 0;
  max-height: min(24rem, 55vh);
  overflow-y: auto;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--page);
  box-shadow: var(--shadow-popover);
}

.theme-control:nth-last-child(-n + 2) .theme-picker-listbox {
  top: auto;
  bottom: calc(100% + 0.35rem);
}

.theme-picker-group + .theme-picker-group {
  border-top: 1px solid var(--line);
}

.theme-picker-group-heading {
  position: sticky;
  z-index: 1;
  top: 0;
  margin: 0;
  padding: 0.55rem 0.75rem;
  color: var(--muted);
  background: var(--panel);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.theme-picker-option {
  min-height: 3.25rem;
  padding: 0.35rem 0.65rem;
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr);
  align-items: center;
  gap: 0.65rem;
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1.25;
}

.theme-picker-option:hover,
.theme-picker-option[data-active] {
  background: var(--accent-soft);
}

.theme-picker-option[aria-selected="true"] {
  color: var(--accent);
  font-weight: 750;
}

.theme-picker-option-image {
  width: 2.5rem;
  height: 2.5rem;
  display: grid;
  place-items: center;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--muted);
  background: var(--panel);
  font-size: 1.3rem;
}

.theme-picker-option-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.theme-picker-option-clear {
  border-bottom: 1px solid var(--line);
  color: var(--muted);
}

.theme-picker-empty {
  margin: 0;
  padding: 1rem;
  color: var(--muted);
  font-size: 0.9rem;
  text-align: center;
}

.theme-thumb {
  width: 2.7rem;
  height: 2.7rem;
  display: grid;
  place-items: center;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--muted);
  background: var(--panel);
  font-size: 0.72rem;
  font-weight: 800;
}

.theme-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.status {
  min-height: 2.8rem;
  margin-top: 1rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--muted);
  background: var(--panel);
  font-size: 0.9rem;
  line-height: 1.4;
}

.status[data-tone="success"] {
  border-color: oklch(72% 0.08 155);
  color: var(--success);
  background: var(--success-soft);
}

.status[data-tone="error"] {
  border-color: oklch(72% 0.10 28);
  color: var(--error);
  background: var(--error-soft);
}

.status[data-tone="warning"] {
  border-color: oklch(75% 0.08 75);
  color: var(--warning);
  background: var(--warning-soft);
}

.actions {
  margin-top: 1rem;
  display: grid;
  gap: 0.65rem;
}

.button {
  min-height: 2.75rem;
  padding: 0.65rem 1rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: 1px solid transparent;
  font-weight: 750;
  cursor: pointer;
  transition: background-color 180ms cubic-bezier(0.22, 1, 0.36, 1), border-color 180ms cubic-bezier(0.22, 1, 0.36, 1);
}

.button-icon {
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.button:active {
  transform: translateY(1px);
}

.button:disabled {
  border-color: var(--line);
  color: var(--muted);
  background: var(--panel);
  cursor: not-allowed;
  opacity: 0.72;
}

.button:disabled:active {
  transform: none;
}

.button-primary {
  min-height: 3rem;
  color: oklch(99% 0.005 235);
  background: var(--accent);
  font-size: 1.02rem;
}

.button-primary:hover {
  background: var(--accent-hover);
}

.button-secondary {
  border-color: var(--line-strong);
  color: var(--ink);
  background: var(--page);
}

.button-secondary:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.button-danger {
  color: oklch(99% 0.005 28);
  background: var(--error);
}

.button-danger:hover {
  background: var(--error-hover);
}

.button:disabled:hover {
  border-color: var(--line);
  color: var(--muted);
  background: var(--panel);
}

.draft-help {
  /* Was -0.35rem, which tucked this under the print hint that used to sit
     between it and the buttons. With that hint gone the negative margin pulled
     it onto the last button, so it now carries the gap itself. */
  margin: 0.85rem 0 0;
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1.4;
}

.confirmation-dialog {
  width: min(calc(100% - 2rem), 29rem);
  max-width: none;
  /* Cap the height so a tall dialog (Help on a short screen) can never overflow
     the viewport. The content scrolls inside; the flex column lets it fill and
     scroll while the rounded corners stay clipped. dvh tracks the mobile toolbar. */
  max-height: calc(100dvh - 2rem);
  margin: auto;
  padding: 0;
  /* flex-direction only bites once [open] flips display to flex below; a closed
     dialog must keep the UA's display:none, or it can never hide again. */
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  color: var(--ink);
  background: var(--surface);
  box-shadow: var(--shadow-dialog);
}

/* Only an open dialog becomes a flex column (to cap height and scroll its body).
   Closed, it keeps the UA display:none so close() actually hides it. */
.confirmation-dialog[open] {
  display: flex;
}

.confirmation-dialog::backdrop {
  background: oklch(20% 0.02 245 / 0.62);
}

.confirmation-dialog-content {
  padding: 1.5rem;
  /* Scrolls within the capped dialog height when the content is taller. */
  overflow-y: auto;
}

.confirmation-dialog-heading {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 0.9rem;
}

/* Help modal: numbered walkthrough, then a short list of things worth knowing. */
.help-steps,
.help-notes {
  margin: 0.9rem 0 0;
  padding-left: 1.4rem;
  display: grid;
  gap: 0.55rem;
  line-height: 1.45;
}

.help-subheading {
  margin: 1.4rem 0 0;
  font-size: 1rem;
}

.confirmation-dialog-icon {
  width: 2.5rem;
  height: 2.5rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--error);
  background: var(--error-soft);
}

/* Changing the document or level clears work as a side effect; it is not a
   deletion, so it gets the caution pair rather than the destructive red. */
.confirmation-dialog-icon[data-intent="warning"] {
  color: var(--warning);
  background: var(--warning-soft);
}

/* Informational dialogs take the accent tint; only a destructive confirmation
   keeps the red. These must stay BELOW the base rule: they match at the same
   specificity, so source order decides, and above it the red silently won —
   which is why the draft dialog wore a destructive icon until 2026-07-27. */
.draft-dialog-icon {
  color: var(--accent);
  background: var(--accent-soft);
}

/* An aside, not a third step. It sat 6.4px below the list while the list's own
   items sat 8.8px apart, so the tighter gap read it as an unnumbered step; and
   at the description's size it read as a second intro. Demoted and separated. */
/* Scoped to the dialog on purpose: the base rule is `.confirmation-dialog p`,
   which outranks a lone class, so `.pdf-dialog-note` alone silently loses. */
.confirmation-dialog .pdf-dialog-note {
  margin-top: 0.85rem;
  font-size: 0.8rem;
  line-height: 1.4;
}

.confirmation-dialog-icon svg {
  width: 1.25rem;
  height: 1.25rem;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.8;
}

.confirmation-dialog h2 {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.25;
}

.confirmation-dialog p {
  margin: 0.4rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

/* 38ch is the measure for a one-line consequence, not for prose. Credits reads
   at the dialog's own width; capping it there just wastes a quarter of the box. */
#confirm-dialog p,
#draft-dialog p {
  max-width: 38ch;
}

/* Help is prose, not a one-line confirmation, so give it room to reach a
   normal reading measure instead of the confirmation dialog's 38ch. */
#help-dialog {
  width: min(calc(100% - 2rem), 36rem);
}

/* A bare URL is one unbreakable word, and this one is longer than the dialog is
   wide, so it has to be allowed to break mid-string or it pushes the dialog out.
   Scoped through `.confirmation-dialog` for the usual reason: the base
   `.confirmation-dialog p` sets the colour a lone class could not beat. */
.confirmation-dialog .credits-feedback-link {
  overflow-wrap: anywhere;
  color: var(--accent);
}

.confirmation-dialog .credits-feedback-link:hover,
.confirmation-dialog .credits-feedback-link:focus-visible {
  color: var(--accent-hover);
}

/* Funder attribution, sized to the dialog rather than the 38ch prose measure. */
.credits-ircc-logo {
  display: block;
  width: 100%;
  max-width: 22rem;
  height: auto;
  margin: 1.5rem auto 0;
}

.confirmation-dialog-actions {
  margin-top: 1.5rem;
  display: flex;
  justify-content: flex-end;
  gap: 0.65rem;
}

.preview-area {
  min-width: 0;
  padding: 1.35rem clamp(1rem, 3vw, 2.5rem) 3rem;
  background: var(--preview-backdrop);
}

.preview-heading-row {
  max-width: 8.5in;
  margin: 0 auto 0.9rem;
  display: flex;
  /* At 200% text size the rem-sized controls column doubles and leaves this row
     less width than its own contents, which then pushed the page-size pill past
     the viewport (WCAG 1.4.4). The tools drop below the heading instead. */
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: 1rem;
}

.preview-heading-row h2 {
  margin-top: 0.12rem;
  font-size: 1.1rem;
}

.preview-tools {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.fit-button {
  min-height: 2.25rem;
  padding: 0.4rem 0.75rem;
  font-size: 0.85rem;
}

.preview-frame {
  display: flex;
  justify-content: center;
  /* flex-start keeps the reduced frame height from also squeezing the page's
     layout box, which would apply the scale twice. */
  align-items: flex-start;
  overflow: hidden;
}

/* Multi-page documents stack their sheets top to bottom, whatever the count.
   app.mjs closes the gap the transform leaves behind with a negative margin,
   since each page scales from its own top but keeps a full-height layout box. */
.preview-frame[data-page-count]:not([data-page-count="1"]) {
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
}

/* The page always lays out at its true printed size and is only scaled for
   display, the way a document editor shows a zoomed page. Sizing it responsively
   instead used to desynchronise the percentage-based layout from the type,
   overflowing the fixed-height page and colliding the theme cards.
   app.mjs owns --preview-scale and the frame height, since a transform does not
   shrink the layout box; print.css clears both. */
.preview-frame .worksheet-page,
.preview-frame .worksheet-placeholder {
  flex: none;
  transform: scale(var(--preview-scale, 1));
  transform-origin: top center;
}

.worksheet-placeholder {
  width: 8.5in;
  min-width: 0;
  aspect-ratio: 8.5 / 11;
  margin: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.9rem;
  padding: 2rem;
  text-align: center;
  color: oklch(53% 0.025 235);
  background: oklch(97% 0.01 220);
  border: 2px dashed oklch(74% 0.025 235);
  border-radius: var(--radius-md, 0.5rem);
}

.worksheet-placeholder-icon {
  width: 3.25rem;
  height: 3.25rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.7;
}

.worksheet-placeholder-message {
  max-width: 30ch;
  margin: 0;
  color: var(--ink);
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.3;
}

.worksheet-placeholder-hint {
  max-width: 34ch;
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.45;
}

.worksheet-page {
  /* Always the real printed width. Display scaling happens on .preview-frame. */
  width: 8.5in;
  aspect-ratio: 8.5 / 11;
  min-width: 0;
  /* iOS Safari defaults this to `auto`, which inflates type inside any block far
     wider than the viewport. This page is exactly that — 816px laid out inside a
     ~343px phone column — and Safari measures the layout width, not the transform
     that scales it down, so it boosted every font on the sheet. The page is a
     fixed 11in with `overflow: hidden`, so the extra height was clipped off the
     bottom on screen. 100%, not `none`: `none` would also block the reader's own
     text-size preference (WCAG 1.4.4); this only turns off Safari's automatic
     boosting. Scoped to the page on purpose — the app UI around it is responsive
     and should keep the boosting. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Everything inside sizes against this box via cqw, not the browser window,
     so the preview matches the printed page at every window size. */
  container-type: inline-size;
  /* Fixed, not a percentage: a percentage here resolves against the frame, so
     it varied with the window and moved every cqw inside with it. 2.7rem is
     0.45in, exactly the margin print.css uses. */
  padding: 2.7rem;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  overflow: hidden;
  color: oklch(20% 0.012 245);
  background: var(--page);
  box-shadow: var(--shadow-page);
  font-family: "Roboto", "Segoe UI", Arial, sans-serif;
}

.worksheet-header {
  position: relative;
  padding-bottom: 2.4%;
  text-align: left;
}

.worksheet-logo {
  position: absolute;
  top: 0;
  right: calc(4% - 6px);
  left: auto;
  width: clamp(4.4rem, 22.807cqw, 7.6rem);
  height: auto;
  margin: 0;
  display: block;
  aspect-ratio: 625 / 250;
  object-fit: contain;
  object-position: right top;
}

.worksheet-header h2 {
  min-height: clamp(2.2rem, 10.526cqw, 3.5rem);
  padding: 0 clamp(4.8rem, 24.561cqw, 8rem) 0 4%;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  font-size: clamp(1.2rem, 5.439cqw, 1.75rem);
  line-height: 1.15;
  text-align: left;
}

.worksheet-page[data-template-id="needs-assessment"] .worksheet-header h2 {
  padding-left: 5.6%;
}

.worksheet-title-stack {
  display: grid;
  gap: 0.08em;
}

/* The class level is metadata under the worksheet name: smaller and lighter, so
   the title stays the thing that stands out. em keeps it in step with the title,
   which is fluid on screen and fixed in print. */
.worksheet-title-level {
  font-size: 0.571em; /* ~16px against the 28px title */
  font-weight: 500;
  line-height: 1.15;
  /* Chroma 0, deliberately. At 0.012 this was rgb(80, 86, 92) — a slate blue that
     reads grey on a backlit screen but makes a colour printer mix it from CMY
     inks instead of black, so it printed navy. rgb(85, 85, 85) is the same
     darkness with no hue, so screen looks unchanged and paper prints true grey.
     Same lightness is what keeps it matching; do not darken it to compensate. */
  color: oklch(45% 0 245);
}

.worksheet-meta {
  width: 94.6%;
  margin-top: 3.2%;
  margin-left: 4%;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6%;
  font-size: clamp(0.7rem, 2.807cqw, 1rem);
  font-weight: 650;
  text-align: left;
}

.worksheet-meta span {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: end;
  gap: clamp(0.2rem, 1.053cqw, 0.35rem);
}

.worksheet-meta b {
  font-weight: inherit;
}

.worksheet-meta i {
  min-width: 0;
  height: 1rem;
  flex: 1;
  border-bottom: 1px solid oklch(35% 0.012 245);
}

.worksheet-page[data-template-id="needs-assessment"] .worksheet-meta {
  width: 93%;
  margin-left: 5.6%;
}

.worksheet-instructions {
  width: 93%;
  margin-top: 3.2%;
  padding: 0;
  overflow: hidden;
  border: clamp(1.5px, 0.351cqw, 2px) solid oklch(70% 0 0);
  border-radius: clamp(0.45rem, 2.456cqw, 0.8rem);
  color: oklch(30% 0 0);
  background: oklch(96.5% 0 0);
}

.worksheet-page[data-layout-id="needs-assessment-two"] .worksheet-instructions,
.worksheet-page[data-layout-id="needs-assessment-four"] .worksheet-instructions,
.worksheet-page[data-template-id="my-clb-levels"] .worksheet-instructions,
.worksheet-page[data-template-id="life-goals"] .worksheet-instructions {
  margin-left: 5.6%;
}

.worksheet-page[data-layout-id="needs-assessment-six"] .worksheet-instructions {
  margin-left: 5.6%;
  transform: translateY(-0.5rem);
}

.worksheet-instructions-heading {
  padding: clamp(0.45rem, 2.018cqw, 0.65rem) clamp(0.7rem, 3.509cqw, 1.15rem);
  display: flex;
  align-items: center;
  gap: clamp(0.4rem, 1.93cqw, 0.65rem);
  border-bottom: 1px solid oklch(70% 0 0);
  color: oklch(18% 0 0);
  background: oklch(84% 0 0);
}

.worksheet-instructions-heading h3 {
  margin: 0;
  font-size: clamp(0.9rem, 3.333cqw, 1.125rem);
  line-height: 1.15;
}

.worksheet-instructions-icon {
  width: clamp(1.25rem, 4.912cqw, 1.8rem);
  height: clamp(1.25rem, 4.912cqw, 1.8rem);
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
}

.worksheet-instructions ol {
  margin: 0;
  padding: clamp(0.55rem, 2.456cqw, 0.8rem) clamp(0.7rem, 3.509cqw, 1.15rem)
    clamp(0.65rem, 2.807cqw, 0.9rem) clamp(2rem, 8.421cqw, 2.7rem);
  display: grid;
  gap: clamp(0.16rem, 1.228cqw, 0.4rem);
  font-size: clamp(0.72rem, 2.807cqw, 1rem);
  line-height: 1.25;
}

.worksheet-instructions li {
  padding-left: 0.15rem;
}

.instruction-checkbox {
  width: 1.15em;
  height: 1.15em;
  margin: 0 0.18em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid oklch(28% 0.012 245);
  color: var(--success);
  background: var(--page);
  font-size: 1.05em;
  font-weight: 850;
  line-height: 1;
  vertical-align: -0.08em;
}

.worksheet-grid {
  min-height: 0;
  padding: 4.5% 0 3%;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr));
  column-gap: 3.2%;
  row-gap: 4.5%;
}

/* Foundation's two-topic sheet: one centred column, two rows.

   Stacked rather than side by side because the pictures are landscape and the
   page is height-bound here. Two columns would leave each card at the four-box
   grid's own column width — the same size as today, for half the pictures —
   while one column lets a card run 26rem wide. The height is what caps it: two
   cards plus the row gap have to fit the 621px grid, which puts the picture at
   416 x 234 against the four-box sheet's 299 x 224, about 45% more picture.

   The viewport is 16/9 rather than the four-box sheet's 4/3 because that is the
   source art's own ratio (1200 x 675), so nothing is cropped. */
.worksheet-page[data-layout-id="needs-assessment-two"] .worksheet-grid {
  padding: 4.7% 5.6% 1.5%;
  grid-template-columns: minmax(0, 26rem);
  grid-template-rows: repeat(2, minmax(0, 1fr));
  justify-content: center;
  row-gap: 4%;
}

.worksheet-page[data-layout-id="needs-assessment-two"] .worksheet-image-wrap {
  aspect-ratio: 16 / 9;
}

/* The low-level preview has taller instructions than the print layout. A
   small horizontal inset gives two wider 4:3 viewports enough vertical room
   without changing the Letter page, footer, or two-column arrangement. */
.worksheet-page[data-layout-id="needs-assessment-four"] .worksheet-grid {
  /* Keep both box columns at their established width. The smaller gap moves
     only the second column left, while the new right inset aligns its edge
     with the instructions panel. */
  padding: 4.7% 1.4% 1.5% 5.6%;
  column-gap: 10.37%;
  row-gap: 4%;
}

.worksheet-page[data-layout-id="needs-assessment-four"] .worksheet-grid:not(:has(.worksheet-theme.is-empty)) {
  grid-template-rows: repeat(2, auto);
  align-content: space-between;
}

.worksheet-theme {
  min-width: 0;
  min-height: 0;
  /* The slot is a <figure>, which carries a 1em/40px user-agent margin. */
  margin: 0;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  overflow: hidden;
  border: clamp(1.5px, 0.439cqw, 3px) solid oklch(34% 0.018 245);
  border-radius: clamp(0.35rem, 1.754cqw, 0.7rem);
}

/* Selected cards take their height from the image viewport plus the footer.
   Empty cards continue to stretch across their assigned grid position. */
.worksheet-page[data-template-id="needs-assessment"] .worksheet-theme:not(.is-empty) {
  align-self: start;
  grid-template-rows: auto auto;
}

.worksheet-image-wrap {
  min-height: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: oklch(97% 0.01 220);
}

.worksheet-page[data-layout-id="needs-assessment-four"] .worksheet-image-wrap {
  aspect-ratio: 4 / 3;
}

.worksheet-page[data-layout-id="needs-assessment-six"] .worksheet-image-wrap {
  aspect-ratio: 16 / 9;
}

/* Hide the print-only image on screen. Scoped to @media screen so it never
   overrides print.css (which shows it when printing), and specific enough to
   beat the ".worksheet-image-wrap img" rule below — otherwise both images
   stack and the print copy pokes out as a band. */
@media screen {
  .worksheet-image-wrap img.worksheet-print-image {
    display: none;
  }
}

.worksheet-check {
  position: absolute;
  right: clamp(0.3rem, 1.754cqw, 0.6rem);
  top: 50%;
  width: clamp(1.2rem, 6.14cqw, 2rem);
  aspect-ratio: 1;
  border: clamp(1.5px, 0.439cqw, 3px) solid oklch(34% 0.018 245);
  background: var(--page);
  transform: translateY(-50%);
}

.worksheet-image-wrap img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

.worksheet-label {
  position: relative;
  margin: 0;
  padding: clamp(0.45rem, 2.456cqw, 0.8rem) clamp(2.2rem, 9.474cqw, 3.2rem);
  border-top: clamp(1.5px, 0.439cqw, 3px) solid oklch(34% 0.018 245);
  font-size: clamp(0.8rem, 2.807cqw, 1rem);
  font-weight: 800;
  line-height: 1.1;
  text-align: center;
}

.worksheet-page[data-layout-id="needs-assessment-four"] .worksheet-label {
  height: clamp(3.25rem, 14.035cqw, 4rem);
  padding-inline: clamp(2.2rem, 8.772cqw, 3rem);
  display: flex;
  align-items: center;
  justify-content: center;
}

.worksheet-page[data-layout-id="needs-assessment-six"] .worksheet-grid {
  padding: 2.4% 5.85% 1.35% 11.7%;
  grid-template-rows: repeat(3, minmax(0, 1fr));
  column-gap: 11.75%;
  row-gap: 1.85%;
  transform: translateY(0.25rem);
}

.worksheet-page[data-layout-id="needs-assessment-six"] .worksheet-label {
  height: clamp(2.6rem, 11.228cqw, 3.15rem);
  padding: clamp(0.3rem, 1.579cqw, 0.5rem) clamp(1.8rem, 7.895cqw, 2.6rem);
  display: flex;
  align-items: center;
  justify-content: center;
}

.worksheet-page[data-layout-id="needs-assessment-six"] .worksheet-check {
  width: clamp(1rem, 4.912cqw, 1.55rem);
}

/* ---- Needs Assessment page two ---- */

/* Four more options and nothing else: no header and no instruction panel, so the
   grid takes the whole sheet instead of the third row of the page grid.
   Deliberately over-specific (three classes) rather than relying on source order:
   it has to beat the four-box grid padding in BOTH styles.css and print.css, and
   a plain tie would be decided by which file loaded last. */
/* Page two carries page one's layout id, so its grid insets, gaps, row template,
   label height and picture shape all come from page one's own rules. The only
   thing left to reproduce is WHERE that grid sits: on page one it is the third
   row of the page grid, under the header and the instruction panel, and page two
   has neither.

   These two numbers are that band, measured off page one and expressed in rem so
   they read the same on screen and on paper. Both layouts share them: the header
   and instruction panel come to the same height either way. To re-derive, measure
   #worksheet-grid against #worksheet — margin-top is its top minus the page's
   0.45in padding, and height is its height. Take the rect before any transform;
   the six-box grid's translateY applies to both sheets equally.

   The HEIGHT is the load-bearing half and must stay 42.275rem: row heights and
   the percentage row-gap both resolve against it, so changing it would make page
   two's boxes and gaps disagree with page one. The margin-top is free — page two
   has no header to clear, so the band is lifted to 4rem (about 1.1in of paper
   above the first box, counting the page's own 0.45in padding) and the leftover
   space falls to the bottom of the sheet. */
.worksheet-page.worksheet-extra-page[data-template-id="needs-assessment"] .worksheet-grid {
  height: 42.275rem;
  margin-top: 4rem;
}

/* No empty-box rules here on purpose. Page two now shares page one's row template,
   so an empty box stretches and a filled one sits at content height on BOTH
   sheets — the same behaviour, which is the point. An earlier version sized page
   two's empty boxes to match their finished cards; that made page two disagree
   with page one, which is a worse mismatch than the one it fixed. */

.worksheet-theme.is-empty {
  border-style: dashed;
  border-color: oklch(74% 0.025 235);
  color: oklch(53% 0.025 235);
  background: oklch(97% 0.01 220);
}

.worksheet-empty {
  grid-row: 1 / -1;
  align-self: center;
  padding: 1rem;
  font-size: clamp(0.68rem, 2.632cqw, 0.95rem);
  font-weight: 700;
  text-align: center;
}

/* My CLB Levels: a blank handout, so the page body swaps for the theme grid. */
.clb-body {
  display: none;
}

.worksheet-page[data-template-id="my-clb-levels"] {
  grid-template-rows: auto auto minmax(0, 1fr);
}

.worksheet-page[data-layout-id="my-clb-levels-higher"] {
  --clb-section-gap: clamp(2.1rem, 9.649cqw, 2.9rem);
  --clb-table-left: 5%;
  --clb-table-width: 96.6%;
  --clb-label-column: 16%;
}

.worksheet-page[data-template-id="my-clb-levels"] .worksheet-grid {
  display: none;
}

/* Both families share the instructions panel; only its wording differs. */
.worksheet-instruction-text {
  display: none;
}

.worksheet-page[data-template-id="my-clb-levels"] .worksheet-instructions > ol {
  display: none;
}

.worksheet-page[data-template-id="my-clb-levels"] .worksheet-instruction-text {
  margin: 0;
  padding: clamp(0.55rem, 2.456cqw, 0.8rem) clamp(0.7rem, 3.509cqw, 1.15rem)
    clamp(0.65rem, 2.807cqw, 0.9rem);
  display: block;
  font-size: clamp(0.72rem, 2.807cqw, 1rem);
  line-height: 1.25;
}

.worksheet-page[data-layout-id="my-clb-levels-higher"] .worksheet-instruction-text {
  padding: 0;
}

.worksheet-page[data-template-id="my-clb-levels"] .clb-body {
  min-height: 0;
  padding-top: 4%;
  display: flex;
  flex-direction: column;
  gap: 0;
  /* Align the skill content closer to the instructions and slightly left. */
  transform: translateX(-3%);
}

.worksheet-page[data-layout-id="my-clb-levels-higher"] .clb-body {
  padding-top: var(--clb-section-gap);
  transform: translateX(-3%);
}

.clb-icon {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
}

/* Lower page: an icon and a sentence the learner completes. */
.clb-rows {
  min-height: 0;
  flex: 1;
  display: grid;
  grid-template-rows: repeat(4, minmax(0, 1fr));
}

.clb-row {
  min-height: 0;
  padding: 0 8%;
  display: grid;
  /* Wide enough that the symbol is capped by its height, not this column. */
  grid-template-columns: 15% minmax(0, 1fr);
  align-items: center;
  gap: 8%;
}

.clb-row .clb-icon {
  height: 100%;
  max-height: clamp(3.12rem, 18.947cqw, 5.28rem);
  object-position: left center;
}

.clb-sentence {
  margin: 0;
  display: flex;
  align-items: end;
  gap: 0.4em;
  font-size: 28px;
  line-height: 1.2;
}

/* Only the skill name carries weight. */
.clb-sentence b {
  font-weight: 700;
}

/* The blank the learner writes their benchmark on. */
.clb-sentence i {
  height: 1.2em;
  flex: 1;
  border-bottom: 2px solid oklch(35% 0.012 245);
}

/* Higher page: identify each skill, write the benchmark, then shade the chart. */
.clb-table {
  width: var(--clb-table-width);
  min-height: 0;
  max-height: 62%;
  flex: 1;
  margin-left: var(--clb-table-left);
  align-self: flex-start;
  display: flex;
  flex-direction: column;
}

.clb-skill-headings {
  width: 100%;
  flex: none;
  display: grid;
  grid-template-columns: var(--clb-label-column) repeat(4, minmax(0, 1fr));
}

.clb-skill-corner {
  min-width: 0;
}

.clb-skill-heading {
  min-width: 0;
  padding: clamp(0.35rem, 1.754cqw, 0.55rem) clamp(0.15rem, 0.877cqw, 0.3rem)
    clamp(0.45rem, 2.105cqw, 0.65rem);
  display: grid;
  grid-template-rows: auto auto;
  justify-items: center;
  gap: clamp(0.25rem, 1.404cqw, 0.45rem);
  border-top: 1px solid oklch(78% 0 0);
  border-right: 1px solid oklch(78% 0 0);
  background: oklch(98% 0 0);
  font-size: clamp(0.85rem, 3.158cqw, 1.125rem);
  text-align: center;
}

.clb-skill-heading:first-of-type {
  border-left: 1px solid oklch(78% 0 0);
}

.clb-skill-title {
  display: grid;
  grid-template-rows: auto auto;
  justify-items: center;
  gap: clamp(0.2rem, 1.053cqw, 0.35rem);
  font-weight: 800;
}

.clb-skill-title .clb-icon {
  width: clamp(2.2rem, 10.526cqw, 3.25rem);
  height: clamp(2.2rem, 10.526cqw, 3.25rem);
}

.clb-skill-level {
  margin: 0;
  display: grid;
  grid-template-columns: auto 2.5em;
  align-items: end;
  gap: 0.25rem;
  font-size: clamp(0.72rem, 2.632cqw, 0.9375rem);
  font-weight: 600;
  white-space: nowrap;
}

.clb-skill-level i {
  height: 1.25em;
  border-bottom: 1.5px solid oklch(35% 0.012 245);
}

.clb-chart {
  min-height: 0;
  flex: 1;
  width: 100%;
  display: grid;
  grid-template-columns: var(--clb-label-column) repeat(4, minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  align-items: stretch;
}

.clb-chart-row {
  display: contents;
}

.clb-chart-label {
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translateX(0.27rem);
  font-size: clamp(0.78rem, 3.158cqw, 1.1rem);
  font-weight: 800;
}

.clb-chart-cell {
  border-right: 1.5px solid oklch(28% 0.012 245);
  border-bottom: 1.5px solid oklch(28% 0.012 245);
}

.clb-chart-label + .clb-chart-cell {
  border-left: 1.5px solid oklch(28% 0.012 245);
}

.clb-chart-row:first-child .clb-chart-cell {
  border-top: 1.5px solid oklch(28% 0.012 245);
}

.clb-goal {
  width: 72%;
  margin: var(--clb-section-gap) 0 0;
  padding: clamp(0.6rem, 2.632cqw, 0.85rem) clamp(0.7rem, 3.509cqw, 1.1rem);
  align-self: center;
  display: grid;
  gap: clamp(0.25rem, 1.228cqw, 0.4rem);
  position: relative;
  left: 9%;
  border: 1px solid oklch(84% 0 0);
  border-radius: clamp(0.45rem, 2.456cqw, 0.8rem);
  background: oklch(96.5% 0 0);
}

.worksheet-page[data-layout-id="my-clb-levels-higher"] .clb-goal {
  width: 93%;
  align-self: flex-start;
  left: 8.6%;
}

.clb-goal h3 {
  margin: 0;
  font-size: clamp(0.95rem, 3.684cqw, 1.25rem);
  line-height: 1.15;
}

.clb-improve {
  margin: 0;
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: end;
  gap: 0.45rem;
  color: oklch(42% 0 0);
  font-size: clamp(0.75rem, 2.807cqw, 1rem);
}

.clb-improve b {
  font-weight: 600;
}

.clb-improve i {
  height: 1.1em;
  border-bottom: 1.5px solid oklch(35% 0.012 245);
}

/* MS Teams QR Code: a poster, not a worksheet. Only the TIES logo is kept from
   the shared header — the class name is the page's own heading. */
.worksheet-page[data-template-id="teams-qr-code"] .worksheet-header h2,
.worksheet-page[data-template-id="teams-qr-code"] .worksheet-meta,
.worksheet-page[data-template-id="teams-qr-code"] .worksheet-instructions,
.worksheet-page[data-template-id="teams-qr-code"] .worksheet-grid {
  display: none;
}

/* Only the header and the body are left in flow, so the body takes the rest of
   the page — the phone and code need a definite height to size against. */
.worksheet-page[data-template-id="teams-qr-code"] {
  grid-template-rows: auto minmax(0, 1fr);
}

.worksheet-page[data-template-id="teams-qr-code"] .clb-body {
  min-height: 0;
  display: block;
}

.teams-qr {
  height: 100%;
  /* Even side padding, so the centred logo is centred on the page. */
  padding: 0 4%;
  display: grid;
  grid-template-rows: auto auto auto minmax(0, 1fr);
  gap: clamp(0.5rem, 2.807cqw, 0.95rem);
}

.teams-qr-logo {
  width: clamp(11rem, 52%, 21rem);
  height: auto;
  /* 3.8rem, not the 1.7rem the clamp used to saturate at: 0.35in of extra air
     under the logo, so the class and teacher lines sit clear of it. The main row
     below absorbs the difference, and both things in it are bottom-anchored, so
     nothing else on the page moves. */
  margin: clamp(0.6rem, 5.263cqw, 1.9rem) 0 3.8rem;
  justify-self: center;
  aspect-ratio: 1359 / 444;
  object-fit: contain;
}

.teams-qr-logo[data-method="zoom"] {
  /* Keep the Teams logo's box height (inherited aspect-ratio) so the class name,
     teacher, and everything below sit in the identical position on both sheets.
     object-fit: contain keeps the wider Zoom logo undistorted inside that box,
     and the transform shrinks and drops it without reflowing anything. */
  transform: translateY(1.5rem) scale(0.72);
}

.teams-qr-class {
  margin: 0;
  min-height: 1.2em;
  font-size: clamp(1.15rem, 5.965cqw, 2.15rem);
  font-weight: 400;
  line-height: 1.2;
}

.teams-qr-class b {
  font-weight: 700;
}

/* Spacing before the photo lives here, so the row below stays exactly as tall
   as the space left on the page. */
.teams-qr-teacher {
  margin: 0 0 clamp(0.6rem, 4.386cqw, 1.6rem);
  font-size: clamp(1.15rem, 5.965cqw, 2.15rem);
  font-weight: 400;
  line-height: 1.2;
}

.teams-qr-teacher b {
  font-weight: 700;
}

.teams-qr-main {
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) min(2.6in, 38%);
  /* The phone sits on the bottom edge; the code stays centred beside it. */
  align-items: end;
  gap: clamp(0.8rem, 5.263cqw, 2rem);
}

/* A definite height, not a max. `max-height: 100%` let the photo stretch to fill
   whatever the row left over (6.3in), while print.css capped it at 5.2in — so the
   preview showed a hand 17% taller than the paper, and the QR code's -1.1in lift
   only lined up on print. Inches measure the same on both here, since the page is
   always laid out at true printed size and only scaled by transform. A max also
   cannot enlarge a small source, which is how the Zoom photo used to render at
   its own file size instead of the layout's. */
.teams-qr-phone {
  height: 5.2in;
  width: auto;
  max-width: 100%;
  justify-self: center;
  align-self: end;
  object-fit: contain;
  object-position: center bottom;
}

/* The column holds it at 2.6in so the printed code is a known, scannable size. */
.teams-qr-code {
  width: 100%;
  aspect-ratio: 1;
  /* Anchored to the bottom of the row, like the phone beside it, so anything
     that changes height above (the logo's margin, a longer class name) leaves it
     where it is. Centring made its position a function of the row height, which
     is how it used to drift whenever the sheet above it grew. */
  align-self: end;
  /* Lifted to sit level with the phone's screen rather than the whole photo,
     matching the approved template. Nudged left so the code groups with the
     phone instead of stranding against the right margin. */
  position: relative;
  top: -3.087in;
  right: 0.5in;
  display: grid;
  place-items: center;
}

.teams-qr-code svg {
  width: 100%;
  height: 100%;
  display: block;
}

.teams-qr-placeholder {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0.5rem;
  display: grid;
  place-items: center;
  border: 2px dashed oklch(78% 0 0);
  border-radius: 0.4rem;
  color: oklch(52% 0 0);
  font-size: clamp(0.7rem, 2.632cqw, 0.95rem);
  text-align: center;
}

/* Shares the placeholder's box, but must not read as the ordinary "waiting for
   your link" hint — this one means the code could not be built at all. */
.teams-qr-problem {
  border-color: var(--error);
  color: var(--error);
  background: var(--error-soft);
  font-weight: 700;
}

.image-fallback {
  padding: 1rem;
  color: var(--error);
  font-size: clamp(0.65rem, 2.456cqw, 0.9rem);
  font-weight: 750;
  text-align: center;
}

/* These breakpoints are screen layout only. Scoped to `screen` so they never
   leak into print — print.css (media="print") is the sole authority there; an
   unscoped max-width block matches the small paper/device width and would drag
   mobile screen rules (padding, the view toggle, stacked layout) onto the page. */
@media screen and (max-width: 62rem) {
  .workspace {
    /* The vw cap is what keeps this column honest at large text sizes. A `rem`
       here scales with the root font size but the 62rem breakpoint above does
       not, so at 200% text a flat 20rem claims 640px of a 768px window and the
       preview is left narrower than the page's minimum scale can shrink to —
       clipped on both sides, since .preview-frame hides its overflow. 42vw is
       320px at the narrow end of this range, so normal text size is unaffected. */
    grid-template-columns: min(20rem, 42vw) minmax(0, 1fr);
  }
}

@media screen and (max-width: 48rem) {
  .workspace {
    display: block;
  }

  /* One panel at a time on a phone: the toggle picks which, so the finished page
     is one tap away instead of a long scroll past every step. */
  .view-toggle {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.35rem;
    position: sticky;
    top: 0;
    z-index: 5;
    padding: 0.6rem clamp(1rem, 3vw, 2.5rem);
    background: var(--surface);
    border-bottom: 1px solid var(--line);
  }

  .view-toggle-button {
    min-height: 2.75rem;
    padding: 0.5rem 1rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--page);
    color: var(--ink);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
  }

  .view-toggle-button[aria-pressed="true"] {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--page);
  }

  /* Shown in the preview header, which is only visible in Preview view here. */
  .button.print-button-mobile {
    display: inline-flex;
  }

  /* Help drops its "Good to know" extras on mobile — used less, tighter space;
     the numbered steps carry the essentials. */
  .help-extra {
    display: none;
  }

  .workspace[data-mobile-view="build"] .preview-area {
    display: none;
  }

  .workspace[data-mobile-view="preview"] .controls {
    display: none;
  }

  .controls {
    border-right: 0;
    border-bottom: 1px solid var(--line);
    /* Stacked layout: no sticky panel, let the page scroll normally. */
    position: static;
    max-height: none;
    overflow: visible;
  }

  .preview-area {
    padding-bottom: 2rem;
  }

  .theme-control:nth-last-child(-n + 2) .theme-picker-listbox {
    top: calc(100% + 0.35rem);
    bottom: auto;
  }
}

@media screen and (max-width: 30rem) {
  /* Logo, title, and Help cannot share one line this narrow without squeezing
     the title into a 3-line column, so the title drops to its own row. */
  .app-header {
    min-height: 4.5rem;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
  }

  .app-header > div {
    order: 1;
    flex-basis: 100%;
    margin-right: 0;
  }

  .help-button {
    margin-left: auto;
  }

  .app-header h1 {
    margin-top: 0;
    font-size: 1.08rem;
  }

  .theme-picker-listbox {
    left: calc(-2.7rem - 0.55rem);
  }

  .theme-picker-option {
    min-height: 3.5rem;
  }

  .worksheet-page {
    padding: 0.85rem;
  }

  .confirmation-dialog-content {
    padding: 1.25rem;
  }

  .confirmation-dialog-actions {
    display: grid;
    grid-template-columns: 1fr;
  }
}

/* ---- Life Goals ------------------------------------------------------------
   Two pages: the glue-down sheet, then the cutout sheet. Both share the fixed
   US Letter page box; only the first page carries the standard header and
   instructions panel. */

.worksheet-page[data-template-id="life-goals"] {
  grid-template-rows: auto auto minmax(0, 1fr);
}

.worksheet-page[data-template-id="life-goals"] .worksheet-grid {
  display: none;
}

/* The numbered instruction list belongs to Needs Assessment; Life Goals writes
   one sentence into the shared panel instead. */
.worksheet-page[data-template-id="life-goals"] .worksheet-instructions > ol {
  display: none;
}

.worksheet-page[data-template-id="life-goals"] .worksheet-instruction-text {
  margin: 0;
  padding: clamp(0.55rem, 2.456cqw, 0.8rem) clamp(0.7rem, 3.509cqw, 1.15rem)
    clamp(0.65rem, 2.807cqw, 0.9rem);
  display: block;
  font-size: clamp(0.72rem, 2.807cqw, 1rem);
  line-height: 1.3;
}

.worksheet-page[data-template-id="life-goals"] .clb-body {
  min-height: 0;
  padding-top: 3%;
  display: block;
}

/* Same width and indent as .worksheet-instructions, so the boxes line up under
   the panel rather than running out to the page margin. */
.goal-boxes {
  width: 93%;
  height: 100%;
  margin-left: 5.6%;
  display: grid;
  grid-template-rows: 1fr 1fr;
  /* Widened so the two boxes read as clearly separate at a glance; each box
     gives up half the added gap since both rows are 1fr against a fixed-height
     container. Still well clear of the cutout fit (see life-goals.mjs notes). */
  gap: clamp(1.3rem, 6.02cqw, 2.2rem);
}

/* Deliberately roomy and empty: the learner glues a cutout inside, so the box
   is sized for the paper it receives rather than for its own content. */
.goal-box {
  display: grid;
  grid-template-columns: clamp(1.9rem, 8.772cqw, 2.9rem) minmax(0, 1fr);
  border: clamp(1.5px, 0.439cqw, 3px) solid oklch(34% 0.018 245);
}

.goal-box-number {
  display: grid;
  place-items: center;
  background: oklch(80% 0.062 245);
  font-size: clamp(1.3rem, 5.965cqw, 2.1rem);
  font-weight: 700;
}

.goal-box-content {
  padding: clamp(0.6rem, 2.807cqw, 1rem) clamp(0.7rem, 3.509cqw, 1.2rem);
  display: grid;
  /* The prompt pins to the top and the year row to the bottom, leaving the
     middle clear for the glued cutout. */
  grid-template-rows: auto minmax(0, 1fr) auto;
}

/* Higher tier. Growing this eats into the clear space below, which is what the
   cutout gets glued into, so both sizes are capped by that fit. */
.goal-box-prompt {
  margin: 0;
  font-size: clamp(0.9rem, 4.386cqw, 1.5rem);
  font-weight: 700;
}

/* The lower tier is the literacy stream and has no year row taking up room, so
   its prompt goes considerably larger. */
.worksheet-page[data-layout-id="life-goals-lower"] .goal-box-prompt {
  font-size: clamp(1.1rem, 5.263cqw, 1.9rem);
}

.goal-years {
  grid-row: 3;
  text-align: center;
}

.goal-years-prompt {
  margin: 0 0 clamp(0.15rem, 0.702cqw, 0.25rem);
  font-size: clamp(0.7rem, 2.632cqw, 0.95rem);
}

.goal-years-row {
  margin: 0;
  display: flex;
  justify-content: center;
  gap: clamp(0.55rem, 2.632cqw, 0.95rem);
  font-size: clamp(0.75rem, 2.807cqw, 1rem);
  font-weight: 700;
}

/* ---- Cutout sheet (page two) ---- */

/* A cutout sheet has no header or instructions, so it drops the three-row page
   grid and gives the whole sheet to the cutouts. */
.worksheet-extra-page {
  display: block;
}

.goal-cutouts {
  /* Shared so the scissors offset always tracks the line it sits on. */
  --goal-cut-line: clamp(1.5px, 0.439cqw, 3px);
  /* The cut line's ink and dash rhythm. Derived from the line width so the
     dashes stay proportional at any size; the ratios match what a native
     `dashed` border draws closely enough that the sheet looks unchanged. */
  --goal-cut-ink: oklch(34% 0.018 245);
  --goal-cut-dash: calc(var(--goal-cut-line) * 2);
  --goal-cut-period: calc(var(--goal-cut-line) * 3.4);
  height: 100%;
  display: grid;
  /* White space between boxes, so there is something to cut through without
     clipping the neighbour. */
  row-gap: clamp(0.6rem, 2.807cqw, 1rem);
}

/* Each cutout is capped to fit inside a page-one goal box with room to spare,
   since a learner cutting by hand will not follow the line exactly. The width
   is what binds: a full-width strip is wider than the box it glues into. Both
   tiers run five to a sheet in one column; the higher tier just has two sheets. */
.goal-cutouts {
  grid-template-columns: minmax(0, 80%);
  grid-template-rows: repeat(5, 1fr);
  justify-content: center;
}

/* The dashed rule is the cut line. The last row in each column has nothing
   below it to separate, so it goes without. */
/* A closed box, not a strip: the cut line runs on all four sides, so the learner
   ends up with a piece that fits the page-one box by construction.

   The four sides are drawn as background strips rather than as `border: dashed`,
   because **Safari does not paint the vertical sides of a dashed border under
   the print zoom.** Confirmed 2026-07-29 from a real Safari PDF: both cutout
   sheets carried no vertical geometry whatsoever — top and bottom cut lines
   present, left and right simply absent — while page one's `.goal-box`, the
   same border width and colour but `solid`, printed all four of its edges on
   the same sheet at the same zoom. Fills print reliably there; the goal photos
   in that PDF are fills.

   The border stays, transparent, purely to hold the box model: the scissors
   offset and `print.css` both measure against it, and `background-origin:
   border-box` is what puts the strips exactly where the border used to be. Do
   not "simplify" this by dropping the border and padding to match — that moves
   the scissors off the line it is meant to sit on. */
.goal-cutout {
  position: relative;
  min-height: 0;
  padding: clamp(0.3rem, 1.404cqw, 0.5rem) clamp(0.5rem, 2.456cqw, 0.9rem);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(0.5rem, 2.456cqw, 0.9rem);
  border: var(--goal-cut-line) solid transparent;
  /* top, bottom, left, right */
  background-image:
    repeating-linear-gradient(
      to right,
      var(--goal-cut-ink) 0 var(--goal-cut-dash),
      transparent var(--goal-cut-dash) var(--goal-cut-period)
    ),
    repeating-linear-gradient(
      to right,
      var(--goal-cut-ink) 0 var(--goal-cut-dash),
      transparent var(--goal-cut-dash) var(--goal-cut-period)
    ),
    repeating-linear-gradient(
      to bottom,
      var(--goal-cut-ink) 0 var(--goal-cut-dash),
      transparent var(--goal-cut-dash) var(--goal-cut-period)
    ),
    repeating-linear-gradient(
      to bottom,
      var(--goal-cut-ink) 0 var(--goal-cut-dash),
      transparent var(--goal-cut-dash) var(--goal-cut-period)
    );
  background-size:
    100% var(--goal-cut-line),
    100% var(--goal-cut-line),
    var(--goal-cut-line) 100%,
    var(--goal-cut-line) 100%;
  background-position: 0 0, 0 100%, 0 0, 100% 0;
  background-repeat: no-repeat;
  background-origin: border-box;
  background-clip: border-box;
}

/* Absolute so it does not become a third grid item. Sits on the top-left corner
   of the box to mark where to start cutting, with the page behind it so the
   dashes break around it. The offset is half the line width, because `top: 0`
   resolves to the inner edge of the border rather than its middle. */
.goal-cutout-scissors {
  position: absolute;
  top: calc(var(--goal-cut-line) / -2);
  left: clamp(0.5rem, 2.456cqw, 0.9rem);
  width: clamp(0.75rem, 3.509cqw, 1.2rem);
  height: auto;
  padding: 0 clamp(0.1rem, 0.526cqw, 0.18rem);
  aspect-ratio: 1;
  fill: none;
  stroke: oklch(34% 0.018 245);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  background: var(--page);
  transform: translateY(-50%);
}

/* Both tiers start their text at the same indent from the cut line. */
.goal-cutout-label {
  margin: 0;
  padding-left: clamp(0.6rem, 3.947cqw, 1.5rem);
  font-size: clamp(0.9rem, 3.947cqw, 1.4rem);
  font-weight: 700;
}

/* The lower tier is the literacy stream, so its cutouts carry larger type
   again. The label column has room to spare at both sizes. */
.goal-cutouts[data-tier="lower"] .goal-cutout-label {
  font-size: clamp(1rem, 4.386cqw, 1.6rem);
}

/* Both tiers lay out identically now, so the photo is one size throughout.
   Sized to use the height the box already has: the ceiling is the longest lower
   tier label, which shares this row and must still fit on one line. */
.goal-cutout-image {
  width: clamp(7rem, 28cqw, 11.25rem);
  aspect-ratio: 4 / 3;
  display: block;
  object-fit: cover;
}

/* ---- My Story --------------------------------------------------------------
   Four writing tiers share one calm page system: sentence frames at the
   literacy levels, guided drawing and reflection in the middle, and a compact
   planning-to-paragraph flow at the highest levels. */

.worksheet-page[data-template-id="my-story"] {
  grid-template-rows: auto auto minmax(0, 1fr);
}

.worksheet-page[data-template-id="my-story"] .worksheet-instructions {
  margin-left: 5.6%;
}

.worksheet-page[data-template-id="my-story"] .worksheet-grid,
.worksheet-page[data-template-id="my-story"] .worksheet-instructions > ol {
  display: none;
}

/* The numbered form of the instruction. Two classes deliberately: the panel's own
   `.worksheet-instructions ol` rule is 0,1,1 and would otherwise win, adding a
   second set of padding on top of the text box's. This keeps only the indent the
   numbers need. */
.worksheet-instruction-text .story-instruction-steps {
  padding: 0 0 0 1.6rem;
  gap: 0.3rem;
}

.worksheet-page[data-template-id="my-story"] .worksheet-instruction-text {
  margin: 0;
  padding: clamp(0.55rem, 2.456cqw, 0.8rem) clamp(0.7rem, 3.509cqw, 1.15rem)
    clamp(0.65rem, 2.807cqw, 0.9rem);
  display: block;
  font-size: clamp(0.72rem, 2.807cqw, 1rem);
  line-height: 1.3;
}

.worksheet-page[data-template-id="my-story"] .clb-body {
  min-height: 0;
  width: 93%;
  height: 100%;
  margin-left: 5.6%;
  padding-top: 3%;
  display: block;
}

.story-foundation,
.story-stems,
.story-high {
  height: 100%;
  min-height: 0;
}

.story-foundation,
.story-stems {
  display: grid;
}

/* Both rows are content height and the stack packs from the top, so the air
   above and below the picture is stated here rather than being whatever a
   flexible row had left over. That leftover used to be 55px above and 74px
   below, which pinned the writing rules to the very bottom of the page; the
   space they give back now falls to the foot of the sheet instead. */
.story-foundation {
  grid-template-rows: auto auto;
  align-content: start;
  justify-items: center;
  padding-top: 1.2rem;
  gap: 4rem;
}

/* Height-bound like the Low and Mid frames: the height is fixed and the 4/3
   ratio derives the width, which is what centres it. It began as 74% of a
   flexible row (312.9px); the number is stated outright now because there is no
   flexible row left to take a share of, and it has since come down to 17rem. */
.story-foundation > .story-foundation-visual {
  width: auto;
  height: 17rem;
  max-width: 100%;
}

.story-foundation-facts {
  width: 100%;
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: end;
  column-gap: 1.1rem;
  row-gap: 3.75rem;
}

.story-foundation-facts p {
  margin: 0;
  font-size: 1.85rem;
  font-weight: 700;
  line-height: 1.2;
}

/* The three-line guide literacy learners are taught to write between: a thick
   baseline, a thin top line, and a dotted midline for x-height.

   The colours are deliberately not pale. These sheets are usually printed in
   black and white, where colour converts by luminance, and a true "light blue"
   lands near white. Both of these convert to roughly 60% grey — light on screen,
   solid on paper. Anything lighter drops out of the print. */
.story-rule {
  position: relative;
  height: 3rem;
  border-top: 1.5px solid oklch(72% 0.11 235);
  border-bottom: 3px solid oklch(72% 0.11 235);
}

.story-rule::before {
  content: "";
  position: absolute;
  inset: 50% 0 auto;
  border-top: 2px dotted oklch(75% 0.11 10);
}

.story-foundation-visual {
  width: 100%;
  max-height: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: clamp(1.5px, 0.439cqw, 3px) solid oklch(34% 0.018 245);
  border-radius: clamp(0.35rem, 1.754cqw, 0.7rem);
  background: oklch(98% 0.008 225);
}

/* ---- The stem sheet, shared by the Low and Mid tiers ---- */

/* The drawing box and the name gaps share the top row; the numbered stems take
   the rest of the sheet. */
.story-stems {
  grid-template-rows: auto minmax(0, 1fr);
  gap: 1.2rem;
}

.story-stems-intro {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1.6rem;
  /* Extra air under the instruction panel. It comes out of the stems' row below,
     and their centring absorbs half of that, so the foot of the sheet only loses
     half of whatever is added here. */
  margin-top: 1.8rem;
}

/* Square, and sized by height alone — the ratio is what derives the width, which
   in turn sizes the column the box sits in. Both tiers were 4/3 at first and the
   landscape frame crowded the name gaps beside it. Low's height is 70% of the
   Foundation box, which measures 417.2 x 312.9px. */
.story-stems-intro > .story-foundation-visual {
  width: auto;
  height: 13.7rem;
  aspect-ratio: 1 / 1;
  max-width: 100%;
}

/* Labelled gaps rather than sentences, spread against the height of the box —
   which is why the gap is a function of how many there are. Two of them (Low)
   need 3.5rem to fill the box; four (Mid) need 2.2rem to fit inside it. */
.story-stems-names {
  min-width: 0;
  display: grid;
  align-content: center;
  gap: 3.5rem;
}

/* Mid's box is taller than Low's; the shape and the sizing rule are shared. The
   stems are centred in the row below, so this height is also the lever on the
   air at both ends of the sheet, and it moves both at once: every pixel added
   here costs half a pixel from the gap above the first stem and half from the
   foot. It survived the drop to four stems unchanged, which is what pays for
   the 75px and 56px those gaps now hold. The stems' grid rows are `auto` and
   will compress silently if the sheet runs out of room. */
.worksheet-page[data-layout-id="my-story-mid"] .story-stems-intro > .story-foundation-visual {
  height: 14.5rem;
}

.worksheet-page[data-layout-id="my-story-mid"] .story-stems-intro {
  margin-top: 1.2rem;
}

/* Deliberately less than the gap that would span the box exactly: three names set
   that far apart read as unrelated to each other. They sit centred against the
   picture instead of aligning to its top and bottom edges. This number is a
   function of the box's height, so it has to be re-derived whenever that
   changes — and the failure is silent. A brief 12.5rem box left this at 3.6rem
   spanning it exactly, so the names aligned to its edges and stopped reading as
   a group. Beside the 14.5rem box they sit 16px inside it. */
.worksheet-page[data-layout-id="my-story-mid"] .story-stems-names {
  gap: 3.6rem;
}

/* Mid fits five stems where Low has three, so it packs them tighter. */
.worksheet-page[data-layout-id="my-story-mid"] .story-stems-facts {
  row-gap: 1.75rem;
  padding-top: 0;
}

/* Numbered with a counter rather than a list marker: these list items are grid
   items, and a grid item's ::marker is not painted by every engine.

   Rows are content height and the spacing is the `row-gap`, so the white band
   between any two stems is identical. Sizing the rows instead (`1fr`, a fixed
   height, or a minmax floor) spaces them by whatever slack is left inside each
   row, so a two-line stem — which fills its row exactly — sits visibly tighter
   against its neighbours than the one-line stems do against each other. */
.story-stems-facts {
  min-height: 0;
  width: 100%;
  margin: 0;
  padding: 0;
  display: grid;
  grid-auto-rows: auto;
  row-gap: 3.5rem;
  align-content: center;
  /* Biases the centred block down the page by half of this, so more of the
     leftover falls under the picture than at the foot. */
  padding-top: 1.2rem;
  counter-reset: story-fact;
  list-style: none;
}

.story-stems-facts li {
  min-height: 0;
  display: grid;
  align-content: center;
  grid-template-columns: 2.2rem minmax(0, 1fr);
  gap: 0.35rem 0;
  counter-increment: story-fact;
}

/* `start`, not `end`: on a stem that runs to two lines the number belongs beside
   the first line. Both share a font size and line height, so their baselines
   still meet — and on a one-line stem the two are identical anyway. */
.story-stems-facts li::before {
  content: counter(story-fact) ".";
  align-self: start;
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.3;
}

/* Only the first line sits beside the number; a second one lines up under the
   text, not under the digit. */
.story-stems-facts p {
  grid-column: 2;
}

/* Each line lays its label, its blank and any trailing note on one baseline; the
   blank takes whatever width is left over. */
.story-stems-facts p,
.story-stems-names p {
  margin: 0;
  display: flex;
  align-items: end;
  gap: 0.4rem;
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.3;
}

.story-stems-facts i,
.story-stems-names i {
  flex: 1;
  height: 1.3em;
  border-bottom: 1.5px solid oklch(42% 0.012 245);
}

/* The wrapper stands in for the rule when there is a hint, so it takes the rule's
   width rules with it. */
.story-stems-facts .story-blank {
  flex: 1;
}

/* "I speak ___ languages." — a count, not an answer, so it does not grow. */
.story-stems-facts i.is-short,
.story-stems-facts .story-blank.is-short {
  flex: 0 0 3.5rem;
}

/* A full stop that ends the blank rather than a word: it cancels the line's
   word-space so it sits against the end of the rule, not adrift after it. */
.story-stems-facts .is-stop {
  margin-left: -0.4rem;
}

/* The bracketed hints are an instruction to the learner, not part of the sentence
   they read, so they drop the sentence's weight. Same size, though — `small`
   would otherwise shrink them as well. */
.story-stems-facts small {
  font-size: 1em;
  font-weight: 400;
}

/* A hinted rule and its hint share a wrapper, and the wrapper — not the rule —
   is the line's flex item. The hint is then taken out of flow and hung under the
   rule, centred on it, which is why it works the same whether the rule is a short
   count with a word after it or a full-width answer. The line reserves the room
   it needs with padding, since an absolute box contributes no height. */
.story-blank {
  position: relative;
}

.story-blank > i {
  display: block;
}

.story-blank > small {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  padding-top: 0.4rem;
  text-align: center;
}

/* Exactly the hint's own height plus its 0.4rem lead, so a hinted line reserves
   what the hint needs and not a pixel more — any surplus would show up as extra
   space after that stem only, and the gaps between stems would stop matching. */
.story-stems-facts p:has(.story-blank) {
  padding-bottom: 2.16rem;
}

.story-sentence {
  min-width: 0;
}

.story-sentence p {
  margin: 0;
  font-size: clamp(0.78rem, 3.158cqw, 1.1rem);
  line-height: 1.2;
}

.story-lines {
  min-height: 0;
  display: grid;
  grid-auto-rows: minmax(1.15rem, 1fr);
  gap: clamp(0.25rem, 1.404cqw, 0.45rem);
}

.story-lines span {
  min-height: 0.9rem;
  border-bottom: clamp(1px, 0.263cqw, 1.5px) solid oklch(42% 0.012 245);
}

.story-quick-facts .story-sentence p {
  min-width: 0;
  margin: 0;
  display: grid;
  grid-template-columns: max-content minmax(2rem, 1fr);
  align-items: end;
  gap: 0.35rem;
  font-size: clamp(0.78rem, 3.158cqw, 1.1rem);
}

.story-quick-facts .story-sentence i {
  height: 1.2em;
  border-bottom: clamp(1px, 0.263cqw, 1.5px) solid oklch(42% 0.012 245);
}

.story-high-introduction {
  min-height: 0;
  display: grid;
  grid-template-columns: 42% minmax(0, 1fr);
  gap: clamp(0.7rem, 3.158cqw, 1rem);
}

.story-sketch,
.story-quick-facts,
.story-plan,
.story-writing {
  min-height: 0;
  padding: clamp(0.55rem, 2.632cqw, 0.9rem);
  border: clamp(1.5px, 0.351cqw, 2px) solid oklch(72% 0.018 235);
  border-radius: clamp(0.4rem, 1.754cqw, 0.6rem);
}

.story-sketch,
.story-quick-facts {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}

/* Clearly above the 1.1rem prompt text below them: at 1.15rem the two read as
   the same size and the boxes lost their headings. */
.story-sketch h3,
.story-quick-facts h3,
.story-plan h3,
.story-writing h3 {
  margin: 0;
  color: oklch(28% 0.045 245);
  font-size: clamp(0.95rem, 4.211cqw, 1.4rem);
  line-height: 1.2;
}

.story-sketch-space {
  min-height: 0;
  margin-top: clamp(0.4rem, 1.754cqw, 0.6rem);
  border: clamp(1px, 0.263cqw, 1.5px) dashed oklch(62% 0.025 235);
  background: oklch(99% 0.005 225);
}

.story-quick-facts {
  grid-template-rows: auto repeat(3, minmax(0, 1fr));
  gap: clamp(0.25rem, 1.228cqw, 0.4rem);
}

/* No lines block to stack: the whole answer sits on the inline blank. */
.story-quick-facts .story-sentence {
  min-height: 0;
  display: grid;
  align-content: center;
}

/* Numbered by counter for the same reason the stem sheet is: these list items
   are grid items, and a grid item's ::marker is not painted by every engine.
   Rows are content-sized and spread apart, so a two-line question keeps its
   second line rather than being squeezed to match the one-line ones. */
.story-questions {
  min-height: 0;
  width: 100%;
  margin: 0;
  /* space-between pins the first question to the box's top edge and the last
     to its bottom, so the top padding is the air under the intro boxes and the
     bottom padding keeps the final writing line off the page's bottom margin.
     Both come out of the between-question gaps, which absorb them evenly. */
  padding: 0.25in 0 0.5in;
  display: grid;
  grid-auto-rows: auto;
  align-content: space-between;
  counter-reset: story-question;
  list-style: none;
}

.story-questions li {
  min-height: 0;
  display: grid;
  grid-template-columns: 1.9rem minmax(0, 1fr);
  counter-increment: story-question;
}

/* 1rem is 12pt on the printed page, which is laid out at true size. Stated in rem
   rather than pt so it beats both the shared `.story-sentence p` clamp here and
   its 17.6px restatement in print.css on specificity, in one place. */
.story-questions li::before,
.story-questions .story-sentence p {
  font-size: 1rem;
}

.story-questions li::before {
  content: counter(story-question) ".";
  font-weight: 700;
  line-height: 1.2;
}

.story-questions .story-sentence {
  grid-column: 2;
  display: grid;
  gap: clamp(0.2rem, 1.053cqw, 0.35rem);
}

/* A normal handwriting pitch: these are full written answers, not a word in a
   gap. */
.story-questions .story-lines {
  grid-auto-rows: 1.7rem;
}

/* The top row is a share of the body column's height, so this is the only number
   that sets how tall the sketch and "About me" boxes are. Their width comes from
   `.story-high-introduction`'s columns and is unaffected. Whatever is left goes
   to the question list, which spreads it between the questions. */
.story-high {
  display: grid;
  grid-template-rows: 33% minmax(0, 1fr);
  gap: clamp(0.7rem, 3.158cqw, 1rem);
}

.story-plan,
.story-writing {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: clamp(0.4rem, 1.754cqw, 0.6rem);
}

.story-plan-prompts {
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr));
  gap: clamp(0.45rem, 2.105cqw, 0.7rem);
}

.story-plan-prompt {
  min-height: 0;
  padding: clamp(0.4rem, 1.754cqw, 0.6rem);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: clamp(0.2rem, 1.053cqw, 0.35rem);
  border: 1px solid oklch(82% 0.014 235);
  background: oklch(98% 0.008 225);
}

.story-plan-prompt h4 {
  margin: 0;
  font-size: clamp(0.68rem, 2.807cqw, 0.95rem);
  line-height: 1.2;
}

.story-writing .story-lines {
  grid-template-rows: repeat(10, minmax(0, 1fr));
}

/* ---- My Story, High tier, page two ----
   The optional paragraph sheet. Sized entirely in rem, which measures the same
   on screen and on paper, so it needs no block in print.css. */

/* Content height and packed from the top: the prompt sits a little below the
   page's own padding rather than at the very top, and whatever the ten lines do
   not use falls to the foot of the sheet. */
.story-page-two {
  margin: 2.5rem 5.6% 0;
  display: grid;
  align-content: start;
  gap: 2rem;
}

.story-paragraph-prompt {
  margin: 0;
  font-size: 1.1rem;
  line-height: 1.35;
}

/* Roomier than the question lines on page one (1.7rem), since a whole paragraph
   is written here rather than a one-line answer. Ten lines at any sane
   handwriting pitch cannot fill a Letter page, so the leftover falls to the foot
   rather than being spread — stretching the pitch to reach the bottom would put
   0.8in between rules, which is not a writing line any more. */
.story-page-two .story-lines {
  grid-auto-rows: 3.2rem;
}

/* ---- Assessment Level Agreement --------------------------------------------
   One signed form, the same page at every class level. Every size below is a
   rem or a percentage of the fixed 8.5in page, and both measure identically on
   screen and on paper, so this sheet deliberately has no block in print.css.
   Change the numbers here. */

.worksheet-page[data-template-id="assessment-level-agreement"] .worksheet-instructions {
  /* The same inset every other family uses, so the panel lines up sheet to
     sheet. print.css leaves margin-left alone, so this one copy covers both. */
  margin-left: 5.6%;
}

/* No themes and no numbered steps: the instruction is prose. The header's
   Name/Date row goes too — both parties sign at the foot instead, so every
   write-on field on this sheet lives in one block down there. */
.worksheet-page[data-template-id="assessment-level-agreement"] .worksheet-grid,
.worksheet-page[data-template-id="assessment-level-agreement"] .worksheet-header .worksheet-meta,
.worksheet-page[data-template-id="assessment-level-agreement"] .worksheet-instructions > ol {
  display: none;
}

.worksheet-page[data-template-id="assessment-level-agreement"] .worksheet-instruction-text {
  margin: 0;
  padding: 0.8rem 1.15rem 0.9rem;
  display: block;
  font-size: 1rem;
  line-height: 1.3;
}

/* Two paragraphs share the panel, so the space between them is stated here
   rather than left to the browser's default paragraph margins. */
.worksheet-page[data-template-id="assessment-level-agreement"] .worksheet-instruction-text p {
  margin: 0;
}

/* A blank line between paragraphs — 1.3rem is exactly the panel's line height
   (1rem x 1.3), so the gap is a skipped line rather than an approximation of one.

   `p + p` rather than a class so every paragraph after the first is spaced the
   same way, and it has to out-specify the reset directly above (0,3,1). */
.worksheet-page[data-template-id="assessment-level-agreement"] .worksheet-instruction-text p + p {
  margin-top: 1.3rem;
}

/* The air under the instruction panel. A rem, not the 3% the other families use:
   a percentage resolves against the column's width, which is the wrong axis.
   The air below the table is set separately on `.ala-sheet` and is deliberately
   larger — the two were briefly one number, but the signature block needs more
   separation from the table than the table needs from the panel above it. */
.worksheet-page[data-template-id="assessment-level-agreement"] .clb-body {
  min-height: 0;
  width: 93%;
  margin-left: 5.6%;
  padding-top: 3rem;
  display: block;
}

/* Table at the top and the sign-off a fixed distance under it, packed from the
   top: whatever is left over falls to the foot of the sheet, which is the same
   blank band the source form leaves below the signature. */
.ala-sheet {
  display: grid;
  grid-template-rows: auto auto;
  align-content: start;
  gap: 3.5rem;
}

/* Cells carry the right and bottom rules; the table closes the grid with the
   top and left ones, so no rule is ever drawn twice. */
.ala-table {
  display: grid;
  border-top: 1.5px solid oklch(28% 0.012 245);
  border-left: 1.5px solid oklch(28% 0.012 245);
}

/* Five equal columns, as on the source: the row-label column is exactly as wide
   as each of the four skill columns. */
.ala-row {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

.ala-row > * {
  min-width: 0;
  padding: 0.5rem 0.6rem;
  border-right: 1.5px solid oklch(28% 0.012 245);
  border-bottom: 1.5px solid oklch(28% 0.012 245);
}

/* Tall enough to hand-write a benchmark in, and equal across the three rows.
   This is the sheet's largest single block of white space, so it is the first
   place to look when the instruction panel grows and the foot stops fitting. */
.ala-table > .ala-row:not(.ala-head) > * {
  min-height: 4rem;
}

.ala-skill {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.3rem;
  font-size: 1.125rem;
  text-align: center;
}

.ala-icon {
  width: 2.75rem;
  height: 2.75rem;
  object-fit: contain;
}

.ala-row-label {
  display: flex;
  align-items: center;
  font-size: 1rem;
  font-weight: 650;
}

/* Yes and No sit apart for the student to circle one. */
.ala-choice {
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  gap: 0.6rem;
  font-size: 1rem;
  font-weight: 650;
}

/* Three write-on rows, closer together than the table gap above them, since they
   are one block rather than separate sections. */
.ala-sign {
  display: grid;
  gap: 1.6rem;
}

/* Borrows the header Name/Date row's markup and rules; only the placement
   differs, since it is already inside the inset body column.

   Scoped to the page, not left as a bare `.ala-fields`: print.css restates
   `.worksheet-meta`'s own width and margins, and a plain class ties it on
   specificity and loses on source order. That printed these rows 27px right of
   where the preview showed them. */
.worksheet-page[data-template-id="assessment-level-agreement"] .ala-fields {
  width: 100%;
  margin: 0;
}


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