/* ==========================================================================
   platform.css · ADDITIONS ONLY, on top of styles.css.

   styles.css is the Instrument implementation from the prototype and it is
   not edited. Anything that has to look different because the platform is
   authenticated (forms, alerts, the account page) is added here, using the
   same tokens and the same rules.

   THE ACCENT RULE STILL APPLIES. --ember marks exactly three things: where
   you are now, the single primary action in the view, and the one
   emphasised element in a diagram. A form is not an emphasis. Field borders
   are --rule, labels are --ink, hints are --muted, and the only ember on a
   login page is the focus ring and the submit button, which IS the single
   primary action.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Layout for the two shell widths
   -------------------------------------------------------------------------- */

.shell--wide { max-width: min(78rem, 100% - 2.5rem); }

/* The auth pages are one column, narrow, centred. Nothing else on the page
   competes, because there is nothing else the visitor can usefully do. */
.authpage {
  max-width: 26rem;
  margin: clamp(2rem, 1rem + 5vw, 4.5rem) auto;
}
.authpage h1 { margin-bottom: .35rem; }
.authpage .lede { margin-bottom: 2rem; }

/* .prose · the reading column                     SHARED: EXAM + CERTIFICATE

   EIGHT PAGES HAVE BEEN EMITTING THIS CLASS AND NOTHING HAS EVER DECLARED IT,
   so all eight have been running body text at the full 70rem of .shell. Nothing
   said so: the pages return 200, the markup is valid, and a stylesheet does not
   log a selector it was never asked for. It is the same silence as the
   seventeen inline attributes in section 7.

   It is the same JOB as .reading in styles.css, which LIVE uses on its two
   pages, so it takes the same measure. Two names for one thing is not ideal and
   the honest repair is one name, but renaming eight pages' markup is a bigger
   change than declaring the name they already print, and .reading is not
   available to be edited: styles.css is the prototype implementation and is not
   touched. A module that wants this from here on should reach for .reading.

   WHY THE WIDE OVERRIDE EXISTS AND IS NOT A HEDGE. Two of the eight pages ask
   the shell for .shell--wide: /exam/mark, because the marking screen puts the
   rubric bands and the script side by side, and /exam/queue, because it is a
   table with five columns. Clamping those to 34rem would take back the width
   the handler explicitly asked for, so asking for the wide shell wins. The
   other six take the measure.

   WHICH PAGES THIS DECLARATION NEWLY AFFECTS, and it is every page that already
   prints the class:
     narrowed to --measure   /exam, /exam/paper, /exam/result/{id},
                             /certificate, /verify, /verify/{number} (the name
                             answer is rendered by verify-name.php)
     unchanged, stays wide   /exam/mark/{id}, /exam/queue
   ADMIN is NOT affected: it dropped .prose from its own pages rather than
   declare it here, which was the right instinct and is what left this. */
.prose { max-width: var(--measure); }
.shell--wide .prose { max-width: none; }

/* Headings inside a reading column need air above them, the same air
   styles.css gives .reading h3, or a run of h2 sections reads as one block. */
.prose h2 { margin-top: 2.2rem; }
.prose h2:first-child { margin-top: 0; }

/* .small · the quieter second sentence                            SHARED

   A guidance line under a control, a caveat under a heading, the sentence that
   qualifies the one above it. .field__hint is the same treatment tied to a
   field and .stop__note is the same treatment tied to a stop; this is the one
   that is tied to nothing, which is why it has the plainest name.

   Only EXAM prints it today, so declaring it newly affects exactly three pages:
   /exam/paper (the clock caveat, the save warning, "handing in is final"),
   /exam/mark/{id} (the word-count guidance, the gate note, the ceilings note,
   the notes help) and /exam/result/{id} (the appeal note and the pass rule).
   Nothing else in the tree emits it, so nothing else moves. */
.small {
  font-size: .86rem;
  line-height: 1.55;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   2. Forms
   -------------------------------------------------------------------------- */

.field { margin: 0 0 1.35rem; }

.field > label {
  display: block;
  font-family: var(--sans);
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: .4rem;
}

.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field textarea {
  width: 100%;
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: .7rem .8rem;
}

/* A password field is read character by character when someone checks a
   typo, so it gets the mono stack. */
.field input[type="password"] { font-family: var(--mono); letter-spacing: .02em; }

.field input:focus-visible,
.field textarea:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
  border-color: var(--ember);
}

.field__hint {
  margin: .4rem 0 0;
  font-size: .84rem;
  line-height: 1.5;
  color: var(--muted);
}

/* An invalid field is marked on the field, next to the field, in words.
   Never colour alone: about one man in twelve cannot rely on it. */
.field--bad input { border-color: var(--ember); }
.field__error {
  margin: .4rem 0 0;
  font-size: .86rem;
  color: var(--ember);
}
.field__error::before { content: "\2192\00a0"; }

/* .field--check · a box or a radio, with its sentence beside it   SHARED
                                                     EXAM + CERTIFICATE

   THIS ONE IS NOT A MISSING TREATMENT, IT IS A WRONG ONE, and that is worth
   separating. `.field > label` above is written for a label that SITS ABOVE its
   control: block, .82rem, 600, uppercase, letter-spaced, --muted. On a checkbox
   the label WRAPS the control and carries the whole sentence, so every page
   printing .field--check has been setting a full sentence in muted uppercase
   small caps and will go on doing it until this rule exists. The modifier was
   written to stop that and was never declared, so it stopped nothing.

   So this is a reset before it is a style. text-transform, letter-spacing,
   size, weight and colour all go back to body text, and the label becomes a row
   with the control at the start of it.

   align-items: baseline and not center: these sentences wrap to two and three
   lines, and a centred box drifts to the middle of the block instead of sitting
   against the first line where it is read.

   WHICH PAGES THIS NEWLY AFFECTS
     /certificate           the two consent radios, "show my name" and "do not".
                            The sentence a holder makes a privacy decision from
                            is currently uppercase and muted, which reads as a
                            label rather than as a choice.
     /exam/mark/{id}        the four gate checkboxes, each carrying the gate's
                            own sentence out of the answer key, and the
                            "I am not sure" box. */
.field--check > label {
  display: flex;
  align-items: baseline;
  gap: .55rem;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
  margin-bottom: 0;
}

/* The control keeps its own size and does not stretch with the row. */
.field--check > label > input { flex: 0 0 auto; }

/* A checkbox row is one line of a list, not a section of a form, so it does not
   want .field's 1.35rem. Four gates in a column need to read as four gates. */
.field--check { margin-bottom: .55rem; }

/* .field--num · one figure                                        SHARED

   A full-width input for a four-character number tells the eye to expect a
   sentence. tabular-nums so a column of answers lines up digit under digit,
   which is how a candidate proof-reads their own paper before handing it in.

   The input stays type="text" with inputmode: modules/exam/_view.php explains
   why, and it is the comma in "0,879" on an English-locale browser.

   WHICH PAGES THIS NEWLY AFFECTS
     /exam/paper            every part A entry, drawn by cnp_exam_input()
     /exam/mark/{id}        the four dimension scores
   Nothing outside EXAM prints it today. It is here rather than in section 11
   because it is a modifier of .field, and a modifier belongs with the thing it
   modifies: the next module with a figure to collect should find it here. */
.field--num input[type="text"] {
  width: auto;
  max-width: 11rem;
  font-variant-numeric: tabular-nums;
}

.form-actions {
  display: flex;
  align-items: center;
  gap: 1rem 1.25rem;
  flex-wrap: wrap;
  margin-top: 1.75rem;
}
.form-actions a { font-size: .9rem; }

/* --------------------------------------------------------------------------
   3. Alerts
   Two kinds and no more. Something went wrong, and something worked.
   -------------------------------------------------------------------------- */

.alert {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--stamp);
  border-radius: var(--radius);
  background: var(--stamp-soft);
  padding: .9rem 1.1rem;
  margin: 0 0 1.75rem;
  font-size: .94rem;
}
.alert p:last-child { margin-bottom: 0; }
.alert--bad { border-left-color: var(--ember); background: var(--ember-soft); }

/* --- the two tones of .note ---------------------  SHARED: EXAM + CERTIFICATE

   styles.css declares .note and its comment says what it is: "important, not
   urgent. It used to be ember-on-ember-soft, which meant four accents down a
   page and none of them meaning anything." Two modules have been printing
   .note--good and .note--bad on top of it since they were built, from
   cnp_exam_note() and cnp_cert_note(), and neither modifier existed. Both tones
   have therefore been rendering as a plain .note: "You passed" and "You did not
   pass" have looked identical on /exam/result/{id}.

   NEITHER TONE IS ALLOWED TO BE EMBER, and this is the constraint that decided
   the whole treatment rather than a note added afterwards. .alert--bad above IS
   ember, and that is right for a form error on a page whose only other content
   is the form. It is wrong here. /exam/result/{id} on a failure prints
   .note--bad and, three lines below it, the retake link as .btn, which styles.css
   already draws in --ember. An ember note would be the second accent on that
   page and would take the emphasis off the one control the candidate has.
   /exam and /exam/paper are the same shape. So the tones are told apart by a
   mark and by the weight of the left edge, in --ink, --muted and --stamp, and
   the accent budget is untouched.

   AND NOT BY COLOUR EITHER WAY. Section 2 above states the rule for an invalid
   field: "Never colour alone: about one man in twelve cannot rely on it." The
   same rule applies to an outcome, and it applies harder, because the outcome
   here is whether somebody passed an exam. The sentence inside the note already
   says which it is; the mark reinforces it and carries none of it.

   WHICH PAGES THESE NEWLY AFFECT
     /exam                  "you passed" (good), and the locked, awaiting and
                            retake-wait notices, which are plain .note and do
                            not move
     /exam/paper            "saved" (good), "the brief is unavailable" (bad)
     /exam/mark/{id}        "settled as a pass" (good) / "as a fail" (bad), and
                            every validation error above the form (bad)
     /exam/result/{id}      "you passed" (good) / which condition failed (bad)
     /certificate           "your choice was saved" (good)
     /verify/{number}       the name answer: MATCH (good). NO MATCH is
                            deliberately plain .note and stays plain, because
                            telling a verifier that a name did not match is
                            information and not a fault. */
.note--good,
.note--bad { border-left-color: var(--ink); }

.note--bad { border-left-width: 3px; }

.note--good::before,
.note--bad::before {
  font-family: var(--mono);
  color: var(--muted);
  margin-right: .55rem;
}
.note--good::before { content: "\2713"; }   /* check mark */
.note--bad::before  { content: "\2717"; }   /* ballot X   */

/* --------------------------------------------------------------------------
   4. Masthead additions
   Sign out is a POST, so it is a <button> that has to read as a nav link.
   -------------------------------------------------------------------------- */

.navform { display: inline; margin: 0; }

.navlink--button {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
}
.navlink--button:hover { text-decoration-thickness: 2px; }

/* --------------------------------------------------------------------------
   5. The rail's third pip state
   styles.css draws "stamped" and "current". "ahead" is the default empty
   pip, and it is named here so the markup can be explicit rather than
   relying on the absence of an attribute.
   -------------------------------------------------------------------------- */

.ticket__pip[data-state="ahead"] { background: transparent; border-color: var(--rule); }

/* --------------------------------------------------------------------------
   6. Development warning
   Shown only when the session cookie has lost its Secure flag, which is the
   one configuration that must never reach production.
   -------------------------------------------------------------------------- */

.insecure-warning {
  background: var(--ember);
  color: var(--ink-invert);
  font-family: var(--mono);
  font-size: .78rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-align: center;
  padding: .5rem 1rem;
}

/* --------------------------------------------------------------------------
   7. Spacing that used to be inline, and was never applied

   Seventeen style="" attributes lived in shell.php, ticket.php, result.php
   and _itinerary.php. The CSP in lib/bootstrap.php sends style-src 'self'
   plus a per-request nonce, and no 'unsafe-inline'. Under CSP Level 2
   style-src governs inline style ATTRIBUTES as well as <style> blocks, and
   THE NONCE DOES NOT RESCUE AN ATTRIBUTE: a nonce applies to an element. So
   every one of those pages was returning 200 and rendering with the wrong
   spacing, with nothing in any log. They are declared here instead, and they
   now actually apply.

   The nonce on style-src is there for three pages that emit no stylesheet
   link at all, because they have to render when the application is broken:
   the uncaught-exception 500, the dev stack trace and the expired-form 403.
   Those carry a five-declaration <style nonce> block of their own. It is not
   an opening for anything on a normal page: a normal page links this file.

   Note on the cascade: these rules are single-class where the inline they
   replace was on an element that already carries a class of the same weight
   (.eyebrow, .stop__note). They win on source order, because shell.php links
   styles.css first and platform.css second. That ordering is load-bearing
   for this section; do not swap the two <link> tags.
   -------------------------------------------------------------------------- */

/* styles.css has --gap (1.25rem), the gap INSIDE a component. This is the gap
   BETWEEN sections: the space that opens a page under the sticky masthead,
   and the space that sets a closing section apart from the road above it.
   Three call sites carried it inline at 2.5rem. */
:root { --gap-section: 2.5rem; }

.gap-section { margin-top: var(--gap-section); }

/* --- the ticket ---------------------------------------------------------- */

/* An eyebrow sitting directly above a big count is tight against it, not on
   the .eyebrow default of .7rem. Two places, one pattern: the rail in the
   masthead-side journey block, and the ticket stub. */
.journey > .eyebrow,
.ticket__stub > .eyebrow { margin-bottom: .2rem; }

/* The tier line runs straight under the holder's name, which already carries
   its own .35rem. .stop__note's .15rem on top of that is a gap the ticket
   does not want. */
.ticket__body > .stop__note { margin-top: 0; }

/* The stub's footnote under the count. Quieter and smaller than .stop__note,
   which is why it is not one. */
.ticket__note {
  margin: .5rem 0 0;
  font-size: .85rem;
  color: var(--muted);
}

/* --- the itinerary ------------------------------------------------------- */

.itinerary__intro {
  color: var(--muted);
  font-size: .94rem;
}

/* The clock note. Set apart below, because the first module head follows it
   and a module head is a rule across the page. */
.itinerary__clock { margin: 1.5rem 0 var(--gap-section); }

/* --- cards --------------------------------------------------------------- */

/* A card leads with an eyebrow and then its heading, and the pair reads as
   one lockup rather than as two paragraphs. Both live tighter than their
   defaults (.7rem for .eyebrow, .5em for h3). */
.card > .eyebrow { margin-bottom: .4rem; }
.card > h3 { margin-bottom: .4rem; }

/* --- the stamp result page ----------------------------------------------- */

/* One card per question, and inside it one line per option. The option lines
   are .stop__note for their ink and size and need more air between them than
   .stop__note's .15rem, because each is a separate sentence about a separate
   option rather than a note under a title. */
.stamp-q { margin-top: 1.5rem; }
.stamp-q__option { margin-top: .6rem; }

/* --------------------------------------------------------------------------
   8. The help desk, given prominence where it is the only check there is

   Decided by Miguel, 8 August 2026. The channel, the number, the address and
   the 24 hour promise do not change and do not move: SUPPORT-AND-HELP
   section 1 is the authority for all four and they appear everywhere they
   already appeared. This is the treatment for the sessions whose practice has
   no worked answer, because the learner is working on their own company and
   there is nothing to check themselves against.

   WHICH SESSIONS IS NOT DECIDED HERE AND NOT DECIDED IN A LIST. shell.php
   receives the compiled session's "open_practice" flag, which the compiler
   sets from the guidance construct in the markdown. A fourth such session
   gets this on the day it is written.

   THE ACCENT RULE. --ember marks where you are, the single primary action in
   the view, and one emphasis in a diagram. On a session page the primary
   action is the stamp button, so nothing here is ember. The vocabulary is
   --stamp and --stamp-soft, which is exactly what .help[open] already uses
   for an aid the learner has opened, so the card reads as part of the
   helpline rather than as a second, louder ask.

   No style attribute anywhere: the CSP is style-src 'self' 'nonce-...', which
   drops style="" as well as an unnonced <style>, and a nonce cannot rescue an
   attribute.
   -------------------------------------------------------------------------- */

.helpdesk--here { border-color: var(--stamp); }

/* The left edge of the panel, not a filled block. A tint across the whole
   card would compete with the stamp box directly above it on the same page. */
.helpdesk--here .helpdesk__say {
  border-left: 2px solid var(--stamp);
  background: var(--stamp-soft);
}

/* The one sentence that says why this session is different. It sits under the
   paragraph it qualifies, separated by the same dashed rule the rest of this
   card uses, and it is not a shout: same size as the body, ink rather than
   muted, so it reads as a sentence Miguel wrote and not as a banner. */
.helpdesk__here {
  margin: .9rem 0 0;
  padding-top: .8rem;
  border-top: 1px dashed var(--stamp);
  color: var(--ink);
}

/* Above 52em the panel and the stub are two columns and the panel already
   carries the left edge. Below it they stack, so the edge would run down the
   side of a full-width block and read as a quote. */
@media (max-width: 51.99em) {
  .helpdesk--here .helpdesk__say {
    border-left: 0;
    border-top: 2px solid var(--stamp);
  }
}

/* "There is no solution to open on this one" sits INSIDE .helpline, so that
   the fetch reply from /exercise/{code}/{key}/aid is a single root element and
   exercise.js's holder.firstElementChild does not drop it. .helpline is a
   flex row, so without this the sentence would sit beside the control instead
   of under it. The same rule catches the refusal note exercise.js inserts
   next to the form it just posted.

   A direct child only: .help--none is also used inside .help__body, for
   "the solution opens once there is an answer above", and that one is not a
   flex item and must not become one. */
.helpline > .help--none { flex: 1 0 100%; }

/* --------------------------------------------------------------------------
   9. The live stops                                            owner: LIVE

   Two clocks and a list of cities, plus two admin tables. Nothing here
   invents a token, a colour or a font: everything is --ink, --muted, --rule
   and the two type stacks that styles.css already declares.

   NO --ember IN THIS SECTION, and that is the accent rule rather than an
   omission. --ember marks where you are, the single primary action in the
   view, and one emphasis inside a diagram. On a live page the primary action
   is the booking button, which is .btn and already carries it. A date is
   information, not an accent; a clock that shouted would make the one control
   on the page shout less.

   NO style="" ANYWHERE THAT USES THESE. The CSP is style-src 'self'
   'nonce-...' with no 'unsafe-inline', which drops inline style ATTRIBUTES as
   well as unnonced <style> blocks, and a nonce applies to an element and never
   to an attribute. Seventeen attributes in four other files were being
   silently discarded before that was noticed. Everything visual this module
   needs is declared here.
   -------------------------------------------------------------------------- */

.live-clock { margin: 1.1rem 0 1.5rem; }

/* The announcement. Larger than the body because it is the one fact on the
   page somebody came to read, and serif because it is a date and not a label. */
.live-clock__utc {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.25rem;
  line-height: 1.3;
  font-variant-numeric: tabular-nums;
}

.live-clock__mins {
  font-family: var(--sans);
  font-size: .85rem;
  color: var(--muted);
  white-space: nowrap;
}

/* The device's own reading. Deliberately quieter than the line above it: it is
   a conversion and the line above is the announcement, and if the two ever
   disagree the reader should already be looking at the right one. It is
   rendered with the hidden attribute and live.js removes it, so with no
   JavaScript there is no empty line here at all. */
.live-clock__local {
  margin: .35rem 0 0;
  font-size: .94rem;
  color: var(--muted);
}

.live-clock__lead {
  margin: 1.1rem 0 .4rem;
  font-family: var(--sans);
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.live-clock__foot {
  margin: .6rem 0 0;
  font-size: .84rem;
  line-height: 1.5;
  color: var(--muted);
}

/* Six rows, one per edition. auto-fit rather than a fixed six, because at 390
   the six become two columns of three and at 1200 they are one row, without a
   breakpoint of their own. minmax(0, 1fr) and not 1fr: a plain 1fr keeps a
   min-content floor, which is what silently clipped the calculators on the
   main site. */
.live-cities {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  gap: .55rem 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.live-cities li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  border-top: 1px solid var(--rule);
  padding-top: .4rem;
  font-size: .92rem;
}

.live-cities__city { color: var(--muted); }
.live-cities__time { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* One sentence saying where this stop stands, on the hub card. */
.live-state { margin: .8rem 0 .6rem; }

/* --- .roster, the list-of-people table -----------------------------------
   Miguel's screens, and they are lists rather than dashboards. A table is the
   right element: it is a roster, the columns mean the same thing down the
   page, and a screen reader announces the header with each cell. It scrolls
   inside its own box rather than pushing the page sideways.

   THREE MODULES NOW, NOT ONE. Written here for LIVE's two admin tables, taken
   by ADMIN's attendance and mentor screens, and taken by EXAM's marking queue
   on 9 August 2026. EXAM had been printing class="queue" on the same shape of
   table, which no stylesheet declared, so both of its lists rendered bare. The
   repair was the name and not a second declaration: two names for one table is
   how a design system stops being one. */

.roster {
  width: 100%;
  border-collapse: collapse;
  margin: 1.5rem 0;
  font-size: .92rem;
}

.roster th,
.roster td {
  text-align: left;
  vertical-align: top;
  padding: .5rem .6rem;
  border-bottom: 1px solid var(--rule);
}

.roster thead th {
  font-family: var(--sans);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The learner's own sentence, under their row rather than squeezed into a
   sixth column. It is prose and it needs a line length. */
.roster__note td {
  border-bottom: 1px solid var(--rule);
  color: var(--ink);
  padding-top: 0;
}

.roster__who {
  display: block;
  font-size: .8rem;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   10. The console                                             owner: ADMIN

   Seven declarations' worth of layout for the pages that publish a room link,
   mark attendance, schedule the individual half hour and revoke a certificate.
   Nothing here invents a token, a colour or a font: everything is --ink,
   --muted, --rule, --card and the two type stacks styles.css already declares.

   NO --ember IN THIS SECTION, AND THAT IS THE ACCENT RULE RATHER THAN AN
   OMISSION. --ember marks where you are, the single primary action in the view,
   and one emphasis inside a diagram. A console is a list of equal small acts
   and has no primary one, so cnp_admin_button() draws .btn--quiet by default
   and the ember is opt-in. Exactly one view in the module takes it: the revoke
   button on /admin/certificate with a record on screen, which is .btn and gets
   the ember styles.css already gives it. Nothing below adds a second.

   NO style="" ANYWHERE THAT USES THESE. The CSP is style-src 'self'
   'nonce-...' with no 'unsafe-inline', which drops inline style ATTRIBUTES as
   well as unnonced <style> blocks, and a nonce applies to an element and never
   to an attribute. Seventeen attributes in four files were being silently
   discarded before that was noticed.
   -------------------------------------------------------------------------- */

/* One stop, or one individual session: a card carrying a form. The rule above
   it separates it from the card before it, which matters on a page that is a
   run of near-identical blocks. */
.admin-set { margin-top: 1.5rem; }
.admin-set > h4 { margin: 1.5rem 0 .4rem; }

/* What a link is stored as right now, printed so it can be read against what
   was pasted. break-all and not break-word: a room link is one unbroken token
   with no spaces in it, and without this a 200-character URL sets the width of
   the page and the whole document scrolls sideways at 390. */
.admin-was {
  margin: .25rem 0 .9rem;
  font-size: .84rem;
  color: var(--muted);
  overflow-wrap: break-word;
  word-break: break-all;
}

/* The learner's own sentence about when they can meet. It is prose and it is
   what the time is decided from, so it reads as body text and not as metadata. */
.admin-said { margin: .4rem 0 1.1rem; }

/* A form inside a table cell. .field's 1.35rem bottom margin is right in a
   column of fields and wrong in a row of twenty-five, so this form uses none of
   it: the control and its button sit on one line and wrap together when there
   is no room for both. */
.admin-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin: 0;
}

/* styles.css styles no <select> at all, because until this module there was
   none in the application. Same border, radius, ground and ink as the text
   inputs in section 2, so the two read as one control set. */
.admin-inline select {
  font-family: var(--sans);
  font-size: .88rem;
  line-height: 1.4;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: .35rem .45rem;
}
.admin-inline select:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
  border-color: var(--ember);
}

/* The attendance table scrolls inside its own box rather than pushing the page
   sideways. Five columns, a name, a ticket code and a select is exactly the
   width that overflows below 480. */
.admin-scroll { overflow-x: auto; }

/* .facts is styles.css's grid and it is written for <li><b><span>. The
   certificate record is a <dl> of <dt>/<dd> pairs, which is the right element
   for a label and a value, so this gives those two the treatment the <b> and
   the <span> get. SCOPED TO .admin-facts on purpose: modules/certificate/
   prints the same shape and this module does not restyle another module's
   page from a shared stylesheet. */
.admin-facts > div { border-top: 1px solid var(--rule); padding-top: .6rem; }
.admin-facts > div > dt {
  font-family: var(--sans);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}
.admin-facts > div > dd {
  margin: .2rem 0 0;
  overflow-wrap: break-word;
}

/* --------------------------------------------------------------------------
   11. The exam                                                  owner: EXAM

   The desk, the paper, the result, the marking screen and the marking queue.
   Everything below is EXAM's own vocabulary: no other module prints any of
   these names, so nothing outside EXAM's five pages changes because this
   section exists. The classes that turned out to be SHARED went to the
   sections that own the thing they modify instead, and are listed at the end
   of this comment so that the split is readable from one place.

   WHY THIS SECTION EXISTS AT ALL. Every name here was already being printed by
   a handler and declared by nothing. The pages returned 200, the markup was
   valid, and a stylesheet does not log a selector nobody wrote: the marking
   screen simply rendered as a wall of unstyled paragraphs. It is the same
   failure as the seventeen inline attributes in section 7 and the same failure
   as the .queue table in the paragraph above. A page that works and looks
   broken tells you nothing, which is why it survives.

   NO --ember IN THIS SECTION, AND IT IS NOT AN OMISSION. --ember marks where
   you are, the single primary action in the view, and one emphasis inside a
   diagram. Every page here already spends its one accent on a control that
   styles.css draws: the Start button on /exam, Hand it in on /exam/paper,
   Record on /exam/mark/{id}, the retake link on /exam/result/{id}. The
   temptation was .panel--act and .item--wrong, and both would have put a
   second ember on a page that already has one. .panel--act lifts by ground,
   and a lost item is told by the marks printed inside it.

   AND NOT BY COLOUR ALONE ANYWHERE. Section 2: "about one man in twelve cannot
   rely on it." Every item on /exam/result/{id} prints its own "3 / 5" and, when
   it was carried forward, says so in words. The left edge is reinforcement.

   THE SHARED ONES, DECLARED ELSEWHERE ON PURPOSE
     .prose, .small                section 1  (also CERTIFICATE / anyone)
     .field--check, .field--num    section 2  (modifiers of .field)
     .note--good, .note--bad       section 3  (modifiers of styles.css's .note)
     .roster                       section 9  (the queue table was renamed to it)
   -------------------------------------------------------------------------- */

/* --- .panel · a bounded block inside a reading column ---------------------

   NOT A SECOND .card, and the difference is the reason it has its own name. A
   .card is a standalone section with a ground, a border and generous padding,
   and CERTIFICATE stacks two or three of them down a page where each is the
   whole point of its part of the page. An exam page is a column of prose that
   is INTERRUPTED three or four times: the rules, then what you may do next,
   then the attempts you have already sat. Four card boxes down that column read
   as four competing panes. Two hairlines and a change of rhythm read as an
   aside, which is what these are.

   Rules across the column rather than a box is the Instrument's own vocabulary:
   a module head in the itinerary is a rule across the page for the same
   reason. */
.panel {
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding: 1.25rem 0;
  margin: var(--gap-section) 0;
}
.panel > :first-child { margin-top: 0; }
.panel > :last-child  { margin-bottom: 0; }
.panel > h2, .panel > h3 { margin-top: 0; }

/* The one panel carrying the single thing to do: start the paper, resume it,
   go back to it. It is lifted by GROUND and by padding and never by the accent,
   because the accent on this page is already spent on the button inside it. */
.panel--act {
  background: var(--paper-deep);
  border-radius: var(--radius);
  border-top-color: transparent;
  border-bottom-color: transparent;
  padding: 1.25rem clamp(1rem, .7rem + 1vw, 1.5rem);
}

/* The deadline block at the head of the paper. Tabular numerals because the
   only thing on it a candidate re-reads is a time, and because the minutes
   remaining is a snapshot they will compare against the clock in their hand. */
.panel--clock { font-variant-numeric: tabular-nums; }
.panel--clock p { margin-bottom: .35rem; }

/* The reconciliation diagnostic on a result. It is NOT a mark and must not read
   as one, so it takes the --stamp left edge that styles.css gives .note: the
   same treatment as everything else on this site that is important and not
   urgent. */
.panel--diagnostic {
  border-left: 2px solid var(--stamp);
  border-top: 0;
  border-bottom: 0;
  padding: .9rem 1.1rem;
  background: var(--paper-deep);
  border-radius: 0 var(--radius) var(--radius) 0;
}

/* The confirmation after a script has been recorded. --stamp and not --ember:
   the marker has finished, this is a receipt, and the only control left on the
   page is a link back to the queue. */
.panel--good { border-top: 2px solid var(--stamp); }

/* --- .scoreline · "Part A            48 / 60" -----------------------------

   A label and a figure, pushed apart, on one row. It is the shape a reader
   scans down: four of them stack on a marking confirmation and the figures have
   to line up under each other or the eye has to hunt for each one. */
.scoreline {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin: 0 0 .45rem;
  border-bottom: 1px solid var(--rule);
  padding-bottom: .45rem;
}
.scoreline__what  { color: var(--muted); }
.scoreline__value { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* --- .items / .item · one question, on three different pages --------------

   .item is a <fieldset> on /exam/paper and on /exam/mark/{id}, and an <li> on
   /exam/result/{id}. The same name because it is the same thing, and the reset
   below has to cover both: a fieldset carries a browser border and a list item
   carries a marker, and neither is wanted. */
.items { list-style: none; margin: 0; padding: 0; }

.item {
  border: 0;
  border-left: 2px solid var(--rule);
  margin: 0 0 1.5rem;
  padding: 0 0 0 1rem;
  min-width: 0;
}
.item > legend {
  padding: 0;
  font-family: var(--sans);
  font-size: .92rem;
  font-weight: 600;
  color: var(--ink);
}
.item > legend + * { margin-top: .6rem; }

/* The item id in the legend of the paper: A3a, B2. It is a coordinate, and a
   candidate reads it against the printed brief, so it is mono and it is the
   first thing on the line. */
.item__id { margin-right: .4rem; }

/* "(5 marks)". A weight, not a heading, so it steps back from the label it
   follows on both the paper and the marking screen. */
.marks {
  font-family: var(--sans);
  font-size: .82rem;
  font-weight: 400;
  color: var(--muted);
  white-space: nowrap;
}

/* --- the result item, and the five bases it can have ----------------------

   modules/exam/marking.php rolls every field up into one of five bases and
   result.php prints it as .item--{basis}: band, carry, partial, blank, wrong.
   All five are declared, because a modifier that exists in PHP and not in CSS
   is exactly the silence this section is repairing.

   The distinction is carried by the marks printed inside the item and, for a
   carried-forward answer, by a sentence. The left edge only reinforces it, and
   it does so with --rule and --stamp: no ember, and nothing a reader has to be
   able to see a colour to follow. */
.item__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem;
  margin: 0 0 .4rem;
  font-weight: 600;
}
.item__head > :last-child { margin-left: auto; white-space: nowrap; }

/* What the candidate typed, printed back at them. The figures are the point of
   the line, so they sit in mono and the label in front of them does not. */
.item__yours { margin: 0 0 .4rem; }

/* "Marked from your own earlier answer." The single most reassuring sentence on
   the page and the one a candidate needs to find, so it is not muted. */
.item__carry {
  margin: .4rem 0 0;
  font-size: .9rem;
  color: var(--ink);
}

/* The worked line, shown only where marks were lost. It is the reason this page
   exists at all, so it reads as body text and not as a footnote, and it keeps
   the --stamp edge the rest of the site gives an explanation. */
.item__worked {
  margin: .5rem 0 0;
  padding: .6rem .9rem;
  border-left: 2px solid var(--stamp);
  background: var(--paper-deep);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: .93rem;
}

.item--band,
.item--carry   { border-left-color: var(--rule); }
.item--partial { border-left-color: var(--stamp); }
.item--wrong,
.item--blank   { border-left-color: var(--stamp); border-left-width: 3px; }

/* --- .bands and .dims · two definition lists of the same shape ------------

   .bands is the rubric on the marking screen, one band per row: a range, a
   name, and the sentence that decides it. .dims is the four dimension scores on
   a result. Both are <dl> wrapping <div><dt>/<dd>, which is the right element
   for a label and its value, and both get the treatment styles.css gives .facts
   for <li><b><span>. Scoped to the two names rather than written once for every
   <dl> on the site, for the reason ADMIN scoped .admin-facts: CERTIFICATE
   prints .facts on the same shape and this section does not restyle another
   module's page. */
.bands,
.dims { margin: .6rem 0 1rem; }

.bands > div,
.dims > div {
  border-top: 1px solid var(--rule);
  padding-top: .5rem;
  margin-top: .5rem;
}
.bands > div > dt,
.dims > div > dt {
  font-family: var(--sans);
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--ink);
}
.bands > div > dd,
.dims > div > dd {
  margin: .25rem 0 0;
  font-size: .93rem;
  color: var(--muted);
}

/* The dimension score is a figure and not a description, so it sits with the
   label rather than under it and lines up down the column. */
.dims > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}
.dims > div > dd { margin-top: 0; white-space: nowrap; color: var(--ink); }

/* --- the two blockquotes: the candidate's, and the assessor's -------------

   .script is what the candidate wrote, shown to the marker. .grader-note is
   what the marker wrote, shown to the candidate. They are deliberately NOT the
   same treatment: one is the thing being judged and one is the judgement, and a
   marker who is about to score 40 marks of prose should be able to tell at a
   glance that they are looking at the script.

   pre-wrap on neither: both go through nl2br() in the handler, so the line
   breaks are already <br> and white-space would double them. */
.script {
  margin: 1rem 0 0;
  padding: 1rem 1.2rem;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  font-size: 1rem;
  line-height: 1.65;
  overflow-wrap: break-word;
}

.grader-note {
  margin: 1rem 0;
  padding: .8rem 1.1rem;
  border-left: 2px solid var(--stamp);
  background: var(--paper-deep);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: .95rem;
}
.grader-note p:last-child { margin-bottom: 0; }

/* --- .published-rule · the pass rule, printed where it is promised --------

   The one sentence on the desk that the cohort's grading regime decides, and
   the reason it has a name of its own is that it is a PROMISE: /exam and
   /exam/result/{id} print the same sentence from the same function so that the
   page cannot accidentally offer personal marking as a standing arrangement.
   It is set apart from the rules list above it, and it is --ink rather than
   --muted, because a promise printed in grey reads as boilerplate. */
.published-rule {
  margin: 1rem 0 0;
  padding-top: .8rem;
  border-top: 1px dashed var(--rule);
  color: var(--ink);
}

/* --- .attempts · the papers already sat -----------------------------------

   One line each, and at most two. A bordered list would be a table for two
   rows; the rule between them is enough to separate a link from a link. */
.attempts {
  list-style: none;
  margin: .6rem 0 0;
  padding: 0;
}
.attempts li {
  border-top: 1px solid var(--rule);
  padding: .5rem 0;
  font-size: .94rem;
}

/* --------------------------------------------------------------------------
   12. Forward and back inside a session                      owner: CONTENT

   Decided by Miguel, 9 August 2026: a session is four screens, one per block,
   not one long scrolling page. This is the control that walks them.

   WHY IT IS A NEW NAME AND NOT .form-actions. That class is one row of a form
   with a submit and a quiet link beside it, and it carries no rule and no
   section spacing because a form ends where its fieldset ends. This ends a
   BLOCK. It needs the same hairline that .block__label uses to open one, so
   that the reading closes the way it opened, and it is not inside a form: it
   is two links.

   WHY dev/ DOES NOT HAVE ONE, said here rather than hidden. The prototype is a
   single scrolling session, so it has no within-session navigation of any
   kind, and no step indicator either. Nothing here invents a token, a colour,
   a radius or a type size: the rule is --rule, the spacing is --gap-section
   from section 7, the primary is .btn and the quiet link is the same .9rem
   .form-actions gives a link beside a button. The one thing a designer should
   still be asked for is a proper LABELLED step strip, "question / essential /
   practice / stamp" with the current one marked, which would be the honest
   version of the position line this ships as an .eyebrow. That is a request
   for dev/, not a thing to draw here first.

   THE ACCENT RULE, screen by screen. --ember marks where you are, the single
   primary action in the view, and one emphasis in a diagram. Where you are is
   the rail's current pip, unchanged. The primary action is exactly one .btn
   per screen: "next" on the question, the essential and the practice, and the
   stamp button on the stamp, which is why the stamp screen deliberately draws
   no next control at all. The back link is not an action of equal weight and
   must never become one.

   No style attribute anywhere: the CSP is style-src 'self' 'nonce-...', which
   drops style="" as well as an unnonced <style>, and a nonce cannot rescue an
   attribute.
   -------------------------------------------------------------------------- */

.blocknav {
  display: flex;
  align-items: center;
  gap: 1rem 1.25rem;
  flex-wrap: wrap;
  max-width: 44rem;
  margin-top: var(--gap-section);
  padding-top: 1.35rem;
  border-top: 1px solid var(--rule);
}

/* Back reads as a footnote to the block above it, not as a second offer. */
.blocknav__back {
  font-size: .9rem;
  color: var(--muted);
}
.blocknav__back:hover { color: var(--ink); }

/* Pushed to the trailing edge so the two controls are not a pair. On a narrow
   screen the row wraps and the auto margin collapses, which puts next on its
   own line under back, which is the order a thumb wants. */
.blocknav__next { margin-inline-start: auto; }
