/* Step C12: brand stylesheet, palette, typography, and dark scheme for staff.
 * Client decisions D-044 (palette) and D-045 (fonts), amended by D-054.
 * Page ground, both sides: #203950. Sage #8CBDB9 is now an accent.
 * Button background: #E89F45. Hover: #E1983E. Button label: #1C1C1C.
 * Headings and body: Raleway 500. Button labels: Montserrat 400. */

:root {
  /* Brand palette: docs/decisions.md D-044, amended by D-054.
   *
   * The names are roles, not sides. `--ground-page` is the navy both the
   * respondent and the staff side now sit on; `--accent-sage` is the old
   * respondent ground, kept because it is a brand value and it is what links
   * on the navy are painted with. */
  --ground-page: #203950;

  /* The footer band, and the only place in the brand that is pure black. It is
   * darker than the page ground on purpose: the footer is the end of the page
   * and the one band that has to read as not-content, which a second navy
   * cannot do against a navy page. Client asked for black. */
  --ground-footer: #000000;

  --accent-sage: #8cbdb9;
  --card-surface: #ffffff;
  --button-surface: #e89f45;
  --button-surface-hover: #e1983e;
  --button-text: #1c1c1c;
  --text-on-light: #1c1c1c;

  /* On the navy ground. Not pure white: 10.5:1 is far past AA and a hair of
   * warmth off the maximum is easier on a long page of body copy. The muted
   * tone is 7.6:1 and is for supporting text only, never for a control. */
  --text-on-dark: #eef1f4;
  --text-on-dark-muted: #c5cfd9;

  /* Surfaces for things sitting directly on the navy: a text input, the
   * progress track. Alpha over the ground rather than opaque hexes, so each
   * one composites against whatever is behind it and none has to be re-picked
   * if the ground ever moves again. See D-055.
   *
   * There were four. `--selected-on-dark`, the amber wash behind a chosen
   * option row, went with the row's box in D-069, and the raised tones are
   * down to one reader each for the same reason: the respondent screens hold
   * text on the ground, with rules between things, and a surface now has to
   * earn its place rather than being the default way to group anything.
   *
   * The staff dark scheme below keeps its own opaque hexes. Those are settled
   * and eyeballed across the admin screens, and re-deriving them was no part of
   * moving the respondent flow off its card. */
  --raise-on-dark: rgba(255, 255, 255, 0.05);
  --raise-on-dark-hover: rgba(255, 255, 255, 0.09);
  --recess-on-dark: rgba(0, 0, 0, 0.22);

  /* Two borders, because they answer to different rules. The decorative one
   * divides a fieldset off from the page and only has to be visible. The
   * control one is the edge of something you click, which WCAG 1.4.11 asks to
   * be 3:1 against what surrounds it: #6E8AA6 on the navy is 3.3:1, where the
   * staff side's #3E5A75 is 1.7:1 and base.css's neutral default is invisible. */
  --border-on-dark: rgba(255, 255, 255, 0.16);
  --control-border-on-dark: #6e8aa6;

  --field-error-color: #c52727;

  /* One radius for every surface the brand draws: buttons, cards, panels,
   * fieldsets, option rows, the testimonial bubble. D-062.
   *
   * It is its own token rather than `--ui-border-radius` at :root, because
   * that one is base.css's per-element knob and base.css gives each element a
   * sensible default through it: 0.25rem on a checkbox, 0.375rem on a text
   * input, 0.5rem on an article. Setting it globally to a surface radius turns
   * the checkbox into a circle, which is the one shape it may not be, since
   * round means radio. */
  --radius-surface: 1.25rem;

  /* Typography: docs/decisions.md D-045 */
  --font-family-headings: "Raleway", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-family-body: "Raleway", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-family-buttons: "Montserrat", -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* Reading size on phones. The audience is church leadership, read on a phone,
 * and skews well past 40, so small-screen text is 30% larger rather than the
 * usual step down from the desktop size.
 *
 * base.css derives every font-size from the root font-size, so this one
 * declaration moves body copy, headings, buttons, labels, form fields and the
 * nav together and in proportion. The alternative, a list of per-element
 * overrides, is the same change spread across thirty rules that then drift
 * apart. 130% of the 16px default is 20.8px.
 *
 * It is a max-width query because what it says is "phones are bigger", which
 * is a max-width statement. It was originally written that way to beat two of
 * Pico's own steps; D-057 removed those and the direction still stands.
 *
 * The consequence to know about: anything that must keep a tuned pixel size
 * through this has to be expressed in px, not rem, or it grows 30% too. The
 * header is the only such place, see .wordmark-mark. Conversely .hero-heading
 * needs no bump of its own, its clamp floor is in rem and already scales. */
@media (max-width: 767px) {
  :root {
    --ui-font-size: 130%;
  }
}

/* Public scheme (default).
 *
 * The light scheme, and that is a statement about one screen rather than about
 * the respondent side: the contact page is a white card, so the surface tokens
 * left unset here fall through to base.css's neutral defaults, which are tuned
 * for white, and `.assessment-card` rebinds the text colours back over for it.
 *
 * Everything else went the other way, the form screens in D-055 and the
 * results page in D-066. `.assessment-panel` carries them straight on the navy
 * and has to rebind every *surface* token instead, because those are the ones
 * the light scheme leaves white.
 *
 * The text direction is deliberate and is what makes the panel cheap: the
 * ground is the default context, so a screen on the navy inherits readable text
 * for free and the exception lives in one place on the card. The other way
 * round, every section added to the landing page would start out near-black on
 * navy. */
:root:not([data-theme="dark"]),
[data-theme="light"] {
  --ui-background: var(--ground-page);
  --ui-color: var(--text-on-dark);
  --ui-card-background: var(--card-surface);
  --ui-primary-background: var(--button-surface);
  --ui-primary-border: var(--button-surface);
  --ui-primary-hover-background: var(--button-surface-hover);
  --ui-primary-hover-border: var(--button-surface-hover);
  --ui-primary-focus: rgba(232, 159, 69, 0.5);
  --ui-primary-inverse: var(--button-text);
  --ui-h1-color: var(--text-on-dark);
  --ui-h2-color: var(--text-on-dark);
  --ui-h3-color: var(--text-on-dark);
  --ui-h4-color: var(--text-on-dark);
  --ui-h5-color: var(--text-on-dark);
  --ui-h6-color: var(--text-on-dark);
  --ui-font-family: var(--font-family-body);
  --ui-font-weight: 500;

  /* Sage on the navy ground: 5.7:1, and a brand value rather than an invented
   * one. The same pair the staff side uses, for the same reason, and for the
   * same reason it is not amber: amber is the button colour. Inside a card this
   * is rebound to the navy, where sage would be 1.9:1 on white. */
  --link-color: var(--accent-sage);
  --link-color-hover: #b4d6d3;

  /* Error surface: a pale tint, not a red block. #c52727 on it is 5.05:1. */
  --error-surface: #fdeeee;

  /* Destructive actions. White on #c52727 is 5.69:1, and 7.29:1 on the hover
   * shade. Per theme because the only .danger button today sits on the staff
   * dark side, and a red tuned for the white card is wrong there. */
  --danger-surface: #c52727;
  --danger-surface-hover: #a81f1f;
  --danger-text: #ffffff;
  --danger-focus: rgba(197, 39, 39, 0.5);
}

/* Staff dark scheme: /app, /app/admin, and /login */
[data-theme="dark"] {
  --ui-background: var(--ground-page);

  /* Body text and every heading. These were missing until D-061, so the whole
   * staff side and the login card ran on base.css's `#111827` fallback: near
   * black on the navy ground, 1.4:1. The light scheme above set them and the
   * dark one did not, which is the kind of gap a per-token test catches and
   * reading two long blocks side by side does not. */
  --ui-color: var(--text-on-dark);
  --ui-h1-color: var(--text-on-dark);
  --ui-h2-color: var(--text-on-dark);
  --ui-h3-color: var(--text-on-dark);
  --ui-h4-color: var(--text-on-dark);
  --ui-h5-color: var(--text-on-dark);
  --ui-h6-color: var(--text-on-dark);

  --ui-card-background: #192d40;
  --ui-card-border: #2e4964;
  --ui-primary-background: var(--button-surface);
  --ui-primary-border: var(--button-surface);
  --ui-primary-hover-background: var(--button-surface-hover);
  --ui-primary-hover-border: var(--button-surface-hover);
  --ui-primary-focus: rgba(232, 159, 69, 0.5);
  --ui-primary-inverse: var(--button-text);
  --ui-font-family: var(--font-family-body);
  --ui-font-weight: 500;
  --ui-table-border: #2e4964;
  --ui-field-background: #132230;
  --ui-field-border: #3e5a75;
  --ui-field-color: #e0e3e7;
  --field-error-color: #ff7875;

  /* The sage, as the link colour on navy: 5.7:1, and a brand value rather than
   * an invented one. Amber would also pass at 5.4:1, but amber is the button
   * colour and a link that looks like a button is worse than one that does not.
   * Identical to the public scheme above, since D-054 put both sides on the
   * same ground, but kept per scheme because the two blocks are independent
   * and a future divergence should not have to untangle a shared value. */
  --link-color: var(--accent-sage);
  --link-color-hover: #b4d6d3;

  /* A tint of the navy ground rather than a light panel, so the alert still
   * reads as part of the dark surface: #ff7875 on it is 5.85:1 and the staff
   * body grey is 11.6:1. */
  --error-surface: #32222c;

  /* The light red inverts: a bright surface with the near-black label, the
   * same shape as the amber button, because a dark red on navy barely reads as
   * a control. #1c1c1c on #ff7875 is 6.65:1, and the surface is 4.65:1 against
   * the navy ground, past the 3:1 a UI component needs. The light-theme red
   * would have been 2.10:1 there. */
  --danger-surface: #ff7875;
  --danger-surface-hover: #ff9b99;
  --danger-text: #1c1c1c;
  --danger-focus: rgba(255, 120, 117, 0.5);
}

/* Typography elements */
body {
  font-family: var(--font-family-body);
  font-weight: 500;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--font-family-headings);
  font-weight: 500;
}

/* Buttons: the brand typeface, and the pill.
 *
 * The amber ground and near-black label are NOT set here. They come from the
 * --ui-primary-* variables in the scheme blocks above, which base.css's button
 * rule reads into its own locals.
 *
 * This file used to ALSO declare those colours at element level, across three
 * blocks of five selectors each, every one carrying
 * :not(.secondary):not(.contrast):not(.outline):not(.danger):not(.nav-toggle)
 * to stay off the framework's variants. That was configuring the same button
 * two contradictory ways at once and paying for both: the exclusions existed
 * only to settle a conflict the element-level route created, which is why
 * styling .nav-toggle cost twenty selector edits. Via the variables there is
 * no conflict, because every variant rebinds the same variables. D-053, and
 * the rule base.css was written to keep.
 *
 * Keep it that way. If a button needs a different colour, rebind
 * --ui-primary-* on it as .danger does below. Do not reach for a
 * background-color declaration and a :not() chain.
 *
 * The radius is the one thing no variable expresses on its own, so it sets
 * --ui-border-radius for the base rule to pick up, rather than declaring
 * border-radius and starting another specificity argument. That route still
 * stands; only the value changed. It was 8rem, which is past any button height
 * and therefore clamped to a pill: a number that never rendered as itself and
 * told you nothing about what you would see. D-062 made it the one surface
 * radius the rest of the brand already wanted. */
button,
[type="button"],
[type="submit"],
[type="reset"],
[role="button"] {
  --ui-border-radius: var(--radius-surface);
  font-family: var(--font-family-buttons);
  font-weight: 400;
}

/* Destructive actions. Scoped to buttons rather than left as a bare .danger,
 * so it cannot re-theme the primary colour of a subtree if the class is ever
 * put on a container.
 *
 * This class was referenced by all twenty of the exclusions above and defined
 * nowhere, so the exclusion did nothing: Pico's own rule still read the amber
 * --ui-primary-background, and "Permanently delete" rendered identical to
 * every ordinary button. Rebinding the variables is both the fix and the
 * pattern, and it covers hover and the focus ring without another rule,
 * because base.css's hover and :focus-visible rules read the -hover- and
 * -focus variables from the element they are on. */
button.danger,
[type="button"].danger,
[type="submit"].danger,
[role="button"].danger {
  --ui-primary-background: var(--danger-surface);
  --ui-primary-border: var(--danger-surface);
  --ui-primary-inverse: var(--danger-text);
  --ui-primary-hover-background: var(--danger-surface-hover);
  --ui-primary-hover-border: var(--danger-surface-hover);
  --ui-primary-focus: var(--danger-focus);
}

/* Outline buttons on the card and on the panel. One amber edge, and only the
 * resting label colour differs, because that is the only thing the surface
 * underneath changes. Filled on hover, where both land on the same pair. */
.assessment-card [role="button"].outline,
.assessment-card button.outline {
  background-color: transparent;
  border-color: var(--button-surface);
  color: var(--text-on-light);
}

.assessment-panel [role="button"].outline,
.assessment-panel button.outline {
  background-color: transparent;
  border-color: var(--button-surface);
  color: var(--text-on-dark);
}

.assessment-card [role="button"].outline:hover,
.assessment-card [role="button"].outline:active,
.assessment-card button.outline:hover,
.assessment-card button.outline:active,
.assessment-panel [role="button"].outline:hover,
.assessment-panel [role="button"].outline:active,
.assessment-panel button.outline:hover,
.assessment-panel button.outline:active {
  background-color: var(--button-surface);
  border-color: var(--button-surface);
  color: var(--button-text);
}

/* Links take their colour from the theme, because --ui-primary-background is
 * amber and amber text is 2.0:1 on the white card. The theme lives on <html>,
 * never on <body>, so these have to be scoped from :root: a
 * `body:not([data-theme=...])` selector matches on every page, dark included,
 * and painted every staff link navy on the navy ground.
 *
 * base.css gives `a` a floor colour and an underline; this is more specific
 * and wins. The explicit currentColor on the decoration is kept rather than
 * inherited, because a decoration colour that does not follow the label is the
 * kind of thing nobody notices until it is on a screenshot. */
a:not([role="button"]) {
  color: var(--link-color);
  text-decoration: underline;
  text-decoration-color: currentColor;
}

a:not([role="button"]):hover {
  color: var(--link-color-hover);
}

/* Site header: logo left, navigation right. C13, D-046.
 *
 * The bar spans the full width; `.site-header-inner` carries the container
 * and the flex row. On a hero page the bar is what gets the blur, so it has to
 * reach both edges of the viewport. */
/* The z-index belongs here and not only on the hero variant below. The overlay
 * is z-index 10, so a header without one paints underneath it and the cross
 * disappears the moment the menu opens, which is what shipped on every page
 * except the landing page. `.site-header-wrap` is unpositioned, so the two
 * compare in the root stacking context. */
.site-header {
  position: relative;
  z-index: 20;
}

.site-header-inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 0.55rem;
}

/* ---- Hero, and the header that sits on top of it ----------------------- */

/* A page with a hero lifts the header out of the flow so the image runs up
 * under it, edge to edge and behind the logo. Only `.has-hero` pages do this:
 * everywhere else the header stays in the flow above <main>, which is why this
 * is scoped to the body class rather than applied to .site-header outright.
 *
 * backdrop-filter blurs what is painted *behind* an element and never its own
 * children, so the logo and the hamburger stay sharp while the image under the
 * bar softens. It goes straight on the bar: a ::before with a negative z-index
 * would do the same job while adding a stacking context to reason about.
 *
 * No background colour at all, deliberately: the brief is a blur and not a
 * scrim, so the image keeps its own brightness under the bar. -webkit- prefix
 * for Safari, which still wants it. */
.has-hero .site-header {
  position: absolute;
  inset-inline: 0;
  top: 0;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.hero {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 100vh;
  background-color: var(--ground-page);
  background-image: 
    linear-gradient(to right, rgba(0, 0, 0, 0.5) 20%, rgba(0, 0, 0, 0) 50%),
    url("/static/img/envision-hero-bg-globe.webp");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}


/* The padding is symmetric on purpose. The hero centres its content with
 * `align-items: center`, which centres the padding box, so uneven padding would
 * push the copy off centre by half the difference. The 6rem has to stay larger
 * than the absolutely positioned header, which is tallest on mobile at about
 * 82px against this rule's 125px, so the copy can never slide up under the
 * logo on a short viewport. Both numbers move with the phone root font-size,
 * which is why the margin is this wide. */
/* No `width` here. This element is `.hero-inner container`, and app.css loads
 * after base.css at equal specificity, so anything width-ish declared here
 * silently beats the container's own 90%/80%. It used to say `width: 100%`,
 * which was a no-op against the old flat 1100px container and became a bug the
 * moment the container went percentage based. Same trap for .site-header-inner
 * and anything else paired with .container; the last test in
 * test_landing_hero.py checks all of them. */
.hero-inner {
  padding-block: 6rem;
}

/* The copy sits in a column on the left, which is where the hero's own gradient
 * darkens the image: the scrim runs from 50% black at the left edge to nothing
 * by 50% across, so text that strays past the halfway mark loses its backing
 * and lands on whatever the globe happens to be doing there. Widen this column
 * and the gradient has to widen with it. */
.hero-copy {
  max-width: 34rem;
  margin-inline: auto;
  color: #ffffff;
  text-align: center;
}

.hero-eyebrow {
  margin-bottom: 0.75rem;
  font-family: var(--font-family-buttons);
  font-size: 0.8rem;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #ffffff;
}

/* --ui-h1-color is the near-black used on the white card, so the heading has
 * to be set white explicitly here rather than inherited from .hero-copy. */
.hero-heading {
  margin-bottom: 1rem;
  font-size: clamp(2rem, 6vw, 3.25rem);
  line-height: 1.1;
  color: #ffffff;
}

.hero-lede {
  margin-bottom: 2rem;
  font-size: 1.05rem;
  line-height: 1.6;
  color: #ffffff;
}

/* Two buttons, so this paragraph is the row that holds them, and it is the one
 * place in the hero that cannot ride on the column's inherited text-align: a
 * flex container ignores text-align entirely. It needs to be flex anyway,
 * because the pair does not fit side by side at the phone reading size and has
 * to wrap to a stack with a gap in both axes.
 *
 * So the alignment is restated as justify-content, centred here and flush left
 * in the 768px block below, next to the text-align it has to agree with. Those
 * two declarations are a pair. Changing one without the other puts the buttons
 * somewhere the text above them is not. */
.hero-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
  margin-bottom: 0;
}

/* The second action is a white outline, not a filled button. There is no
 * approved secondary button colour to use: docs/brand.md puts every button on
 * amber, and the framework default secondary is a slate that belongs to no
 * palette in this project. White is already the hero's text colour, and an
 * outline beside the solid amber reads as the lesser action without inventing
 * a brand value. The hover fills white and takes the near-black label, which
 * is the same move the amber button makes.
 *
 * Rebinding the secondary variables rather than declaring colours, per
 * D-053, and scoped to the hero so the `outline secondary` buttons in the
 * staff admin keep the neutral grey. */
.hero-cta .secondary {
  --ui-secondary-background: transparent;
  --ui-secondary-border: #ffffff;
  --ui-secondary-inverse: #ffffff;
  --ui-secondary-hover-background: #ffffff;
  --ui-secondary-hover-border: #ffffff;
  --ui-secondary-focus: rgba(255, 255, 255, 0.5);
}

.hero-cta .secondary:hover,
.hero-cta .secondary:active,
.hero-cta .secondary:focus {
  --ui-secondary-inverse: var(--button-text);
}

/* Desktop drops the centring. The column is pinned to the left because that is
 * where the gradient is, and centred text inside a left-hand column reads as a
 * mistake, with the button in particular floating away from the text above it.
 * Mobile keeps the centring: there is no gradient story at that width, the
 * column fills the screen, and centred is what the design asks for. */
@media (min-width: 768px) {
  .hero-copy {
    margin-inline: 0;
    text-align: left;
  }

  /* The flex equivalent of the text-align above. Both or neither. */
  .hero-cta {
    justify-content: flex-start;
  }
}

/* ---- Landing: the intro section under the hero ------------------------- */

/* Second section of the landing page: a centred head, then an image beside a
 * short list of what the assessment does. It sits directly on the navy ground
 * with no card, which is only possible since D-054. On the old sage ground the
 * amber CTA at the bottom of the right column was 1.07:1 against it and failed
 * WCAG 1.4.11 on the boundary of a control, so a section like this had to be
 * boxed in white; on navy the same button is 5.4:1.
 *
 * Text colours here come from the page scheme rather than being declared, which
 * is the point of the direction D-054 chose: nothing in this section has to
 * know what colour the ground is. */
/* Only the top of the gap is this section's business. The bottom belongs to
 * base.css's `main`, which now carries the page gutter for every screen; a
 * bottom padding here would stack on it and leave a dead screenful under the
 * CTA. */
.intro {
  padding-block: 1rem 0;
}

@media (min-width: 768px) {
  .intro {
    padding-block: 2rem 0;
  }
}

/* The head is centred at every width, unlike the hero, which goes flush left on
 * desktop to follow its gradient. There is no image behind this one. */
.intro-head {
  max-width: 44rem;
  margin-inline: auto;
  margin-bottom: 2.5rem;
  text-align: center;
}

/* Same treatment as .hero-eyebrow, in amber rather than white because there is
 * no image to sit on and the navy ground wants an accent. 5.4:1 on the navy,
 * per D-044. Deliberately not shared with the hero rule: the two differ in the
 * one declaration that matters and may yet differ in more. */
.intro-eyebrow {
  margin-bottom: 0.75rem;
  font-family: var(--font-family-buttons);
  font-size: 0.8rem;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--button-surface);
}

/* A step down from the h1 in the hero, and it stays a step down at every width:
 * both are clamped, with this one's ceiling below the hero's. */
.intro-heading {
  margin-bottom: 1rem;
  font-size: clamp(1.6rem, 4vw, 2.25rem);
  line-height: 1.2;
}

.intro-sub {
  margin-bottom: 0;
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--text-on-dark-muted);
}

/* One column on a phone, two from tablet up, image first in both. `align-items`
 * is start rather than center: the two columns are rarely the same height and a
 * centred image floats against a left-aligned list. */
.intro-split {
  display: grid;
  gap: 2rem;
}

@media (min-width: 768px) {
  .intro-split {
    grid-template-columns: 1fr 1fr;
    gap: 3rem;
    align-items: start;
  }
}

/* Ross Frisbee and his quote. This column was a flat grey placeholder until
 * the review video arrived.
 *
 * The portrait is a cut-out with a real alpha channel, so it sits straight on
 * the navy with no box behind it, and the bubble overlaps it by a negative
 * margin rather than by absolute positioning. That matters: the quote is 40
 * words and grows if it is ever re-cut, and an absolutely positioned bubble
 * would run off the bottom of the image the first time it did. In flow it can
 * only push the attribution down. */
.intro-media {
  display: flex;
  flex-direction: column;
  margin: 0;
}

.intro-portrait {
  display: block;
  width: 100%;
  max-width: 32rem;
  height: auto;
  margin-inline: auto;
}

/* White, and solid rather than one of the alpha surfaces, for a reason the
 * tail makes unavoidable: the bubble straddles the portrait and the navy, and
 * an alpha fill would composite differently over each, putting a visible seam
 * down a shape that is supposed to be one piece. */
.intro-quote {
  position: relative;
  margin: -2.5rem 0 0;
  padding: 1.5rem 1.5rem 1.25rem;
  border-radius: var(--radius-surface);
  background-color: var(--card-surface);

  /* Restated, not only rebound. base.css puts `color: var(--ui-color)` on
   * <body>, where it resolves to the page's light text and is inherited as a
   * computed colour, so rebinding the token here would change nothing. Same
   * trap as .assessment-card. */
  --ui-color: var(--text-on-light);
  color: var(--ui-color);
}

/* The tail, pointing up into his shoulder. Borders rather than a rotated
 * square, so it inherits nothing that could round its tip. */
.intro-quote::before {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 4rem;
  border-inline: 0.75rem solid transparent;
  border-bottom: 0.75rem solid var(--card-surface);
}

.intro-quote p {
  margin: 0;
  font-size: 1.05rem;
  line-height: 1.5;
}

.intro-attribution {
  margin-top: 1rem;
  padding-left: 0.25rem;
}

.intro-attribution-name,
.intro-attribution-role {
  display: block;
}

.intro-attribution-name {
  font-weight: 600;
}

.intro-attribution-role {
  font-size: 0.95rem;
  color: var(--text-on-dark-muted);
}

.intro-points {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
}

/* h3 and a paragraph per point. h3 rather than h4 because .intro-heading is the
 * h2 directly above them, so this is the next level down and the document
 * outline stays unbroken. */
.intro-point h3 {
  margin-bottom: 0.4rem;
  font-size: 1.2rem;
  line-height: 1.3;
}

.intro-point p {
  margin-bottom: 0;
  line-height: 1.55;
  color: var(--text-on-dark-muted);
}

.intro-cta {
  margin-bottom: 0;
}

/* The logo. Scoped from .site-header so it outranks the generic link rule
 * above without relying on source order. Both files are white on transparent,
 * so whatever sits behind the header has to be dark or an image. */
.site-header .wordmark {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  line-height: 0;
}

/* The "T" mark on phones, the full lockup from tablet up. Both are in the
 * markup and CSS hides one: the full lockup is a 3.4:1 letterbox and a phone
 * header has no width to give it.
 *
 * In px, not rem, and it is the one place in this file that has to be. The
 * phone root font-size is 130% (top of this file), so 3.7rem here would make
 * the mark 77px while .nav-toggle stayed at its 57px, and the two sides of the
 * header would no longer balance. The header size is tuned, not derived from
 * the text size, so it is pinned. Browser zoom still scales px. */
.wordmark-mark {
  display: block;
  height: 59px;
  width: auto;
}

.wordmark-full {
  display: none;
  height: 3rem;
  width: auto;
}

@media (min-width: 768px) {
  .wordmark-mark {
    display: none;
  }

  .wordmark-full {
    display: block;
  }
}

.site-nav {
  position: relative;
  display: flex;
  align-items: center;
}

/* ---- Navigation ------------------------------------------------------- */

/* One link list, two presentations. Inline in the bar from 768px up, a full
 * screen overlay below it. The markup renders the list twice from a single
 * Jinja array, because the overlay cannot be a child of the blurred bar: a
 * backdrop-filter makes an element a containing block for fixed descendants,
 * so a nested overlay would be trapped in the header's box. */

.nav-links {
  display: none;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Belt and braces, and kept deliberately after D-057. list-style applies to
 * the item rather than to the list, so `list-style: none` on the <ul> above
 * does not reach it if anything ever styles `li` again. Pico did exactly that
 * with `ul li { list-style: square }`; base.css does not, so today this is
 * redundant and costs one declaration. Same for the overlay list below. */
.nav-links li {
  margin: 0;
  list-style: none;
}

/* Two bars, becoming a cross. 57px is well past the 44px minimum tap target,
 * which matters: this is a phone at a conference.
 *
 * The amber ground is reset here rather than excluded upstream. base.css
 * paints it from a bare `button` selector, which this plain class outranks, so
 * the resets below are enough. They were not enough while this file also
 * declared the amber at element level behind four `:not()` classes, which
 * outweighed them and painted the toggle amber whatever it asked for. The
 * box-shadow reset is a leftover from Pico's focus ring and is kept because a
 * bare `box-shadow: none` costs nothing and the white outline below is what
 * this wants on keyboard focus. */
.nav-toggle {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 57px;
  height: 57px;
  padding: 0;
  margin: 0;
  background: none;
  border: 0;
  box-shadow: none;
  color: #ffffff;
}

.nav-toggle:hover,
.nav-toggle:active,
.nav-toggle:focus {
  background: none;
  border: 0;
  box-shadow: none;
  color: #ffffff;
}

.nav-toggle:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

.nav-toggle-bar {
  display: block;
  width: 34px;
  height: 2px;
  border-radius: 1px;
  background-color: currentColor;
  transition: transform 0.2s ease;
}

/* The cross is this same button, so it sits exactly where the bars were. The
 * open state is read from aria-expanded, which Alpine already binds, rather
 * than from a bound class: the CSP build evaluates property access and method
 * calls only, and an object literal in x-bind:class is neither. The bars are
 * 2px with a 9px gap, so their centres are 11px apart and each travels 5.5px
 * to meet in the middle. Change the gap or the bar height and both offsets have
 * to move with it, or the cross comes out as two parallel diagonals. */
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:first-child {
  transform: translateY(5.5px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bar:last-child {
  transform: translateY(-5.5px) rotate(-45deg);
}

/* The overlay covers the viewport and sits *below* the bar, so the cross stays
 * visible and tappable in the spot the bars occupied. */
.nav-overlay {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--ground-page);
}

/* Scroll lock. The overlay is fixed, so the page scrolls on underneath it
 * without this. `siteNav.toggle()` mirrors the open state onto <html> as
 * `.nav-open`: Alpine's CSP build evaluates property access and method calls
 * only, so the class cannot be bound from the markup and is set in app.js
 * instead. Both elements are locked because iOS Safari has long ignored
 * overflow on <body> on its own. The class name is shared with
 * static/js/app.js and pinned by tests/test_navigation.py. */
html.nav-open,
html.nav-open body {
  overflow: hidden;
}

.nav-overlay-links {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: center;
}

.nav-overlay-links li {
  margin: 0;
  list-style: none;
}

.nav-overlay-links a {
  font-size: 1.6rem;
}

/* White nav text everywhere the header appears, with the current page
 * underlined and nothing else. Scoped from .site-header and .nav-overlay so it
 * outranks the generic themed link rule near the top of this file. */
.site-header .nav-links a,
.nav-overlay-links a {
  color: #ffffff;
  text-decoration: none;
}

.site-header .nav-links a:hover,
.nav-overlay-links a:hover,
.site-header .nav-links a[aria-current="page"],
.nav-overlay-links a[aria-current="page"] {
  color: #ffffff;
  text-decoration: underline;
  text-underline-offset: 0.35em;
}

/* From tablet up the links are simply in the bar, and there is nothing to
 * open: both the toggle and the overlay go away. Hiding the toggle is what
 * guarantees the overlay can never be left open behind a resize. */
@media (min-width: 768px) {
  .nav-links {
    display: flex;
    align-items: center;
    gap: 2rem;
  }

  .nav-toggle,
  .nav-overlay {
    display: none;
  }

  /* Releasing the lock here matters as much as hiding the overlay: a phone
   * turned to landscape past the breakpoint with the menu open would otherwise
   * leave the page frozen with nothing on screen to close. Same specificity as
   * the lock, later in the file, so this wins while it matches. */
  html.nav-open,
  html.nav-open body {
    overflow: visible;
  }
}

[x-cloak] {
  display: none !important;
}

#toasts {
  position: fixed;
  inset-inline: 1rem;
  bottom: 1rem;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  pointer-events: none;
}

#toasts > * {
  pointer-events: auto;
}

@media (min-width: 576px) {
  #toasts {
    inset-inline: auto 1rem;
    width: min(24rem, calc(100vw - 2rem));
  }
}

.skip-link {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link:focus,
.skip-link:focus-visible {
  position: static;
  display: inline-block;
  width: auto;
  height: auto;
  padding: 0.5rem 1rem;
  margin: 0.5rem 1rem 0;
  overflow: visible;
  clip-path: none;
  white-space: normal;
}

/* Public assessment card: white surface sitting over the navy ground.
 *
 * This is the one place the page's text colours are turned back over. D-055
 * took the form screens off it and D-066 took the results page off it too, so
 * what is left is the contact page. The page scheme describes the navy ground,
 * so without this that page would be near-white text on white.
 *
 * It is one screen away from being dead. Keep it until the contact page is
 * designed rather than deleting it and then needing it back: it is the only
 * worked example of inverting the text half of the scheme, and the comment
 * below is the part that took two goes to get right.
 *
 * Rebinding the variables rather than declaring colours, per D-053. A custom
 * property declared on an element beats an inherited one whatever the
 * specificity, so this reaches every descendant, including base.css's
 * heading rules, which read the h*-color tokens.
 *
 * `color` has to be restated, and that is the part that is easy to miss:
 * base.css puts `color: var(--ui-color)` on <body>, which resolves there
 * against the page value and is then inherited as a *computed colour*.
 * Rebinding the token alone changes nothing a descendant already inherited. */
.assessment-card {
  --ui-color: var(--text-on-light);
  --ui-h1-color: var(--text-on-light);
  --ui-h2-color: var(--text-on-light);
  --ui-h3-color: var(--text-on-light);
  --ui-h4-color: var(--text-on-light);
  --ui-h5-color: var(--text-on-light);
  --ui-h6-color: var(--text-on-light);

  /* Navy on the white card: 11.9:1. The sage the page uses is 1.9:1 here. */
  --link-color: var(--ground-page);
  --link-color-hover: #122130;

  max-width: 44rem;
  margin-inline: auto;
  color: var(--ui-color);
  background-color: var(--card-surface);
  border-radius: var(--radius-surface);
  padding: 1.5rem;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}

@media (min-width: 576px) {
  .assessment-card {
    padding: 2.25rem;
  }
}

/* Public assessment panel: the three form screens, straight on the navy.
 *
 * The entry form, the seven question screens and the closing question. D-055
 * took them off the white card, so this is the card's opposite number and the
 * two are worth reading side by side, because they rebind two disjoint sets of
 * tokens and the reason is the whole point:
 *
 *   - the card rebinds the TEXT tokens, because it changes the surface under
 *     text that the page scheme already coloured light
 *   - the panel rebinds the SURFACE tokens, because it keeps the page's light
 *     text and inherits it for free, but the form controls and muted greys
 *     fall through to base.css's neutral defaults, which are tuned for white
 *
 * So there is deliberately no `color` here, and no background either. A panel
 * that declared its own text colour would be duplicating the page default, and
 * the duplicate is what drifts. If you find yourself adding one, the thing that
 * is actually wrong is a surface token that is still white.
 *
 * Every value comes from the alpha tokens at the top of this file, so a nested
 * surface composites instead of needing its own hex: an input inside a fieldset
 * inside the panel still reads recessed. */
/* Two screens, one problem: a form sitting straight on the navy ground rather
 * than on a white card. The token list is shared because it is the same list,
 * not because the two screens are related; splitting it into a copy per screen
 * is how one of them quietly keeps a white input. */
.assessment-panel,
.login-card {
  /* Fieldsets and the .placeholder note, which both read the card token. */
  --ui-card-background: var(--raise-on-dark);
  --ui-card-border: var(--border-on-dark);

  /* Supporting text: the step counter, the option descriptions, the closing
   * help and skip note. The light-scheme grey is 2.1:1 on the navy. */
  --ui-muted-color: var(--text-on-dark-muted);
  --ui-muted-border: var(--border-on-dark);

  /* Controls. The active background matters as much as the resting one: it
   * falls through to white on the light scheme, so without this every input
   * turns white the moment it takes focus, which is the worst possible
   * moment. */
  --ui-field-background: var(--recess-on-dark);
  --ui-field-active-background: var(--recess-on-dark);
  --ui-field-border: var(--control-border-on-dark);
  --ui-field-active-border: var(--button-surface);
  --ui-field-color: var(--text-on-dark);
  --ui-field-placeholder: var(--text-on-dark-muted);

  /* Both reds retune, for the same reason they do on the staff side: #c52727
   * is 2.2:1 on the navy, and the pale pink error surface is a light panel in
   * the middle of a dark screen. */
  --field-error-color: #ff7875;
  --error-surface: #32222c;
}

.assessment-panel {
  max-width: 44rem;
  margin-inline: auto;
}

/* ---- Login ------------------------------------------------------------- */

/* The hero image behind the sign-in card, with a flat scrim over it rather
 * than the hero's left-to-right gradient: the card is centred, so there is no
 * side for a directional scrim to favour. The image is the landing page's, by
 * reference, so it is one file in the cache and one file to replace. */
.login-page {
  background-color: var(--ground-page);
  background-image: linear-gradient(rgba(32, 57, 80, 0.72), rgba(32, 57, 80, 0.72)),
    url("/static/img/envision-hero-bg-globe.webp");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* Centring happens on <main> rather than on <body>, which also holds the skip
 * link and the toast container; making body the flex parent moves those too. */
.login-page main {
  display: flex;
  min-height: 100vh;
  align-items: center;
  justify-content: center;
}

.login-card {
  width: 100%;
  max-width: 540px;
  padding: 2.5rem 2rem;
  border: 1px solid var(--border-on-dark);
  border-radius: var(--radius-surface);
  background-color: var(--ground-page);
}

.login-card .wordmark {
  display: block;
  width: max-content;
  max-width: 100%;
  margin-inline: auto;
  line-height: 0;
}

.login-card .wordmark-full {
  height: 2.9rem;
}

/* .wordmark-full is display:none below the tablet breakpoint, where the header
 * shows the square mark instead. Two classes beat that one. */
.wordmark-full-only .wordmark-full {
  display: block;
}

.login-card h1 {
  margin-block: 1.75rem 1.5rem;
  text-align: center;
  font-size: 1.5rem;
}

/* Submit on the right, the way out on the left. `width: auto` is not cosmetic:
 * base.css gives every submit `width: 100%`, which here would put the two
 * buttons on separate rows. */
.login-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.login-actions [role="button"] {
  flex: 0 0 auto;
}

.login-actions button[type="submit"] {
  flex: 1 1 auto;
  width: auto;
}

/* Copy that has not arrived yet, marked as such. The dashed outline stays
 * when D-069 took every other surface off these screens, and deliberately:
 * this is not design, it is scaffolding, and it should keep looking like
 * scaffolding until the client's words replace it. What it loses is the fill,
 * so it is an outline on the page rather than one more raised box. */
.placeholder {
  border: 2px dashed var(--ui-muted-border);
  border-radius: var(--radius-surface);
  padding: 1rem;
  margin-block: 1.5rem;
  color: var(--ui-muted-color);
}

.placeholder p:last-child {
  margin-bottom: 0;
}

/* Visually hidden, but read aloud. Not `display: none`, which takes it out of
 * the accessibility tree along with everything else. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The progress grid: seven clusters of three marks, one per dimension.
 *
 * Deliberately not a bar. The shape is the message, D-056: seven short
 * sections rather than one long list, and never more than three marks from
 * the end of the section you are in. The gap *between* clusters is doing that
 * work, so it has to stay clearly larger than the gap inside one.
 *
 * `flex: 1` on the cluster rather than a fixed width: seven equal clusters
 * fill the panel at any size, and the marks inside stretch with them, so the
 * whole thing scales down to a phone without a second set of numbers. */
/* The per-dimension mark. The grey placeholder box is gone: `dimension-icon-N`
 * now carries the drawing for dimension N.
 *
 * **Masked, not an <img>, and not inline SVG.** These line drawings appear
 * beside a muted header label and beside a body weight results heading, so
 * they have to take the colour of whatever text they sit next to. An <img>
 * renders the file in its own document, where `stroke="currentColor"` resolves
 * to black and the icon disappears into the navy; inline SVG would tint
 * correctly but puts 400 bytes of path data into the markup seven times on one
 * page and takes the drawings out of the browser cache. A mask painted with
 * `currentColor` gets both: one cached file per dimension, and a colour that
 * follows the text. D-083.
 *
 * `-webkit-mask` first for Safari before 15.4, which is also why the shorthand
 * is written out rather than relying on `mask`.
 *
 * Two sizes, set by the context: larger beside the section heading on the
 * results page, smaller in the header bar.
 */
.dimension-icon {
  display: inline-block;
  flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.dimension-icon-1 {
  -webkit-mask-image: url("/static/img/icons/dim-1.svg");
  mask-image: url("/static/img/icons/dim-1.svg");
}

.dimension-icon-2 {
  -webkit-mask-image: url("/static/img/icons/dim-2.svg");
  mask-image: url("/static/img/icons/dim-2.svg");
}

.dimension-icon-3 {
  -webkit-mask-image: url("/static/img/icons/dim-3.svg");
  mask-image: url("/static/img/icons/dim-3.svg");
}

.dimension-icon-4 {
  -webkit-mask-image: url("/static/img/icons/dim-4.svg");
  mask-image: url("/static/img/icons/dim-4.svg");
}

.dimension-icon-5 {
  -webkit-mask-image: url("/static/img/icons/dim-5.svg");
  mask-image: url("/static/img/icons/dim-5.svg");
}

.dimension-icon-6 {
  -webkit-mask-image: url("/static/img/icons/dim-6.svg");
  mask-image: url("/static/img/icons/dim-6.svg");
}

.dimension-icon-7 {
  -webkit-mask-image: url("/static/img/icons/dim-7.svg");
  mask-image: url("/static/img/icons/dim-7.svg");
}

.result-dimension-heading .dimension-icon {
  width: 2.25rem;
  height: 2.25rem;
}

.progress-grid {
  margin-bottom: 1.5rem;
}

.progress-groups {
  display: flex;
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.progress-group {
  display: flex;
  flex: 1;
  gap: 0.2rem;
  margin: 0;
  padding: 0;
}

.progress-dot {
  flex: 1;
  height: 0.35rem;
  border-radius: 0.35rem;
  background-color: var(--raise-on-dark-hover);
}

.progress-dot-done {
  background-color: var(--accent-sage);
}

/* Done dots are links; they keep the same pill shape but lighten on hover to
 * signal they are tappable. `display: block` is explicit because flex
 * blockification is implementation-defined. */
a.progress-dot-done {
  display: block;
}

a.progress-dot-done:hover {
  opacity: 0.7;
}

a.progress-dot-done:focus-visible {
  outline: 2px solid var(--button-surface);
  outline-offset: 2px;
  border-radius: 0.35rem;
}

/* The one you are on, in the button colour, because it is the only mark on the
 * page that answers "where am I". 5.4:1 on the navy. */
.progress-dot-current {
  background-color: var(--button-surface);
}

/* Subtle text link above the question, shown when a previous question exists.
 * Muted so it does not compete with the question itself. */
.question-back {
  display: inline-block;
  margin-bottom: 0.75rem;
  font-size: 0.875rem;
  color: var(--text-on-dark-muted);
  text-decoration: none;
}

.question-back:hover {
  color: var(--ui-color);
}

/* The section being answered, in the right half of the minimal header bar.
 *
 * It was `.step-dimension`, a line of small caps between the progress grid and
 * the question. D-063 moved it up here: the bar already existed, held nothing
 * but the logo on these screens, and every line above the question costs a
 * phone screen's worth of scroll before the thing the screen is for.
 *
 * `.site-header-inner` is already `space-between`, so there is nothing to do
 * about the position. The width cap is what keeps a long dimension name off
 * the logo; it wraps to two short lines instead. */
.site-header-step {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  max-width: 55%;
  margin-bottom: 0;
  font-family: var(--font-family-buttons);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  line-height: 1.3;
  text-align: right;
  text-transform: uppercase;
  color: var(--text-on-dark-muted);
}

.site-header-step .dimension-icon {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
}

/* On desktop the dimension name moves below the progress grid, so the header
 * slot is freed up. Mobile keeps it in the bar because vertical space is tight. */
@media (min-width: 768px) {
  .site-header-step {
    display: none;
  }
}

/* Dimension name + icon below the progress grid, desktop only. Hidden on mobile
 * where the same information lives in the header bar. */
.question-dimension {
  display: none;
}

@media (min-width: 768px) {
  .question-dimension {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
  }

  .question-dimension-name {
    margin-bottom: 0;
    font-family: var(--font-family-buttons);
    font-size: 1.1rem;
    font-weight: 400;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-on-dark-muted);
  }

  .question-dimension .dimension-icon {
    width: 1.5rem;
    height: 1.5rem;
  }
}

.assessment-progress {
  margin-bottom: 1rem;
}

.step-counter {
  margin-bottom: 0.5rem;
  font-weight: 600;
  color: var(--ui-muted-color);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* The consent block at the end of the entry form. A rule above it instead of a
 * box around it: D-069 took the raised surfaces off the respondent side, and a
 * separator does the one thing the box was doing, which is marking where the
 * form stops asking about the church and starts asking for permission.
 *
 * The `.gating-*` rules that shared this box are gone with it. They had been
 * dead since D-041 removed the gating question.
 *
 * `margin-block` is the shorthand on purpose. Written as `1.5rem 0` it zeroed
 * the bottom margin every other fieldset gets from base.css, and the Begin
 * button sat against the consent wording with nothing between them. */
.consent-fieldset {
  border: 0;
  border-top: 1px solid var(--ui-muted-border);
  padding: 1.5rem 0 0;
  margin-block: 1.5rem;
}

.consent-label {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 0.75rem;
  cursor: pointer;
  line-height: 1.4;
  margin-bottom: 0;
}

.consent-label input[type="checkbox"] {
  margin-top: 0.2rem;
}

/* The bot check and the button under it, as one centred column.
 *
 * Nothing here styles the widget. Cloudflare replaces `.cf-turnstile` with a
 * fixed 300px iframe of its own, so the only thing we can do from outside is
 * place it, and the only thing that decides how it looks is `data-theme` on
 * that div. The dark value is set in the template and belongs there: it is a
 * Turnstile parameter, not a CSS one, and a stylesheet cannot reach inside a
 * cross origin frame to repaint a white card.
 *
 * The button is capped rather than spanning the form. base.css gives a submit
 * `width: 100%`, which is right for the staff screens it was written for and
 * wrong on a 44rem entry form where it would sit as a full width bar under a
 * 300px widget. 20rem is 320px, so the two read as one column. The cap is on
 * the button and the centring on the wrapper, because the button is
 * inline-flex to centre its own label and auto margins do not centre an
 * inline-flex box. */
.start-turnstile,
.start-actions {
  display: flex;
  justify-content: center;
}

.start-turnstile {
  margin-bottom: 1.5rem;
}

.start-actions button[type="submit"] {
  max-width: 20rem;
}

/* The question and its options.
 *
 * No box, since D-069. It was a raised panel holding five more raised panels,
 * two surfaces deep on a screen with one question on it, and neither depth
 * said anything: there is nothing else on the screen for the question to be
 * distinguished *from*. What is left is the thing itself, a question and a
 * list, with a rule under the question where the top of the box used to be.
 *
 * The fieldset's own border has to be turned off explicitly. A `<fieldset>`
 * carries a 2px groove by UA default, and base.css resets padding and margin
 * on it but not that.
 *
 * On the question text being a <p> and not a <legend>: D-058, and read it
 * before putting a legend back. A legend is lifted out of flow and painted
 * across the border, where neither the padding nor the fieldset's `display`
 * reaches it. */
.question-fieldset {
  border: 0;
  padding: 0;
  margin-bottom: 2rem;
}

.question-text {
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--ui-muted-border);
  margin-bottom: 0;
  font-weight: 600;
  font-size: 1.05rem;
  line-height: 1.35;
}

.options-group {
  display: flex;
  flex-direction: column;
}

/* One option, and the Next button that belongs to it.
 *
 * The button is a sibling of the label rather than a child, because a label
 * may not contain another interactive control and nesting one makes a tap
 * ambiguous between choosing the option and submitting. The row is what pairs
 * them, and it is what `:has()` is asked about below. */
/* A hairline between rows, and none above the first: the rule under the
 * question text is already that edge. D-069 replaced five bordered, filled,
 * rounded boxes with this. A list of five things reads as a list. */
.option-row + .option-row {
  border-top: 1px solid var(--ui-muted-border);
}

.option-target {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 0.75rem;
  padding-block: 0.85rem;
  cursor: pointer;
  min-height: 48px;
}

/* Hover and selected are the same amber in two strengths, on the label rather
 * than behind it. The row itself keeps no surface at all: the radio already
 * says which one is chosen, and the Next button appearing in the row says it
 * twice. */
.option-target:hover .option-label {
  color: var(--button-surface);
}

.option-row:has(input[type="radio"]:checked) .option-label {
  color: var(--button-surface);
}

/* The Next button that appears under the option you just chose.
 *
 * D-056, and the reason the whole screen was rebuilt: this is taken one
 * handed on a phone, and the thumb should not have to travel to the bottom of
 * the page twenty-one times. The control appears where the thumb already is.
 *
 * Pure CSS off the radio's own `:checked` state. No Alpine and no script: the
 * state already exists in the DOM, and a JavaScript copy of it would be the
 * duplication the stack rules warn against. The cost is `:has()`, and the
 * `.question-next` button at the bottom of the form is the answer to that
 * cost rather than an oversight. Do not delete it.
 *
 * `margin-bottom` matches `.option-target`'s `padding-block`, so the row keeps
 * the same inset at the bottom whether the button is in it or not. Without it
 * the button sat flat against the hairline under the row. */
.option-next {
  display: none;
  width: auto;
  margin-top: 0.5rem;
  margin-bottom: 0.85rem;
  margin-left: auto;
  padding: 0.5rem 1.75rem;
}

/* **Phones only, and the reveal is what the breakpoint gates.** D-072. The
 * argument for this button is thumb travel on a one handed phone. On a desktop
 * there is no thumb, the pointer is already wherever it was clicked, and the
 * bottom Next is a few hundred pixels away rather than a scroll away, so a
 * second button sliding in under the chosen row is one more thing on screen
 * doing a job that was not a problem.
 *
 * `max-width: 767px`, which is the one other place this sheet expresses the
 * phone band, at the root font-size bump at the top of the file. It is the
 * complement of the `min-width: 768px` queries everywhere else and the two
 * numbers have to stay one apart.
 *
 * The base rule above stays outside the query, so the button is hidden at
 * every width unless this one reveals it, and `display: none` keeps it out of
 * the tab order and the accessibility tree rather than merely out of sight. */
@media (max-width: 767px) {
  .option-row:has(input[type="radio"]:checked) .option-next {
    display: block;
  }
}

/* The second Next, at the bottom of the form, and what it looks like before
 * anything is answered.
 *
 * It reads "Next" because it does what the other Next does. Two words for one
 * action is a difference the respondent will go looking for and not find.
 * D-068.
 *
 * The values are base.css's own `:disabled` treatment, repeated rather than
 * referenced because CSS cannot inherit a rule, and `opacity` is the part that
 * matters: it fades the amber against the navy, so the button reads inactive
 * without a second grey entering the palette.
 *
 * `pointer-events` is deliberately NOT in that list, and the button is
 * deliberately not `disabled`. The radios are `required`, so the browser
 * already refuses this submit; leaving the button live means that refusal
 * arrives as "please select one of these options" pointing at the group, which
 * is the one thing a truly disabled button cannot do. It looks unavailable and
 * explains itself when pressed anyway. */
.question-form:not(:has(input[type="radio"]:checked)) .question-next {
  opacity: 0.5;
  cursor: not-allowed;
}

.option-target input[type="radio"] {
  margin-top: 0.2rem;
  cursor: pointer;
}

.option-content {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.option-label {
  font-weight: 600;
  line-height: 1.3;
}

.option-desc {
  color: var(--ui-muted-color);
  font-size: 0.875rem;
  line-height: 1.35;
}

/* The break before "Not sure", which is the N/A answer and not part of the
 * scale. It is the same hairline the rows are separated by, so what makes it
 * read as a break rather than as one more row is the gap around it. That gap
 * is the whole rule now that the rows are a list: with the boxes gone, a
 * divider at row spacing would be invisible. */
.option-divider {
  margin-block: 1.25rem;
  border-top: 1px solid var(--ui-muted-border);
}

/* The note screen at the end of each dimension. D-056 gave the per dimension
 * comment its own page, so the collapsed toggle that used to live under the
 * third question is gone along with its Alpine component. */
/* The heading is a question four words long, so it is sized as a label rather
 * than as a page title. It was a clamp up to 1.8rem, which on a phone filled
 * two lines above a box nobody is obliged to type in. D-064. */
.notes-heading {
  margin-bottom: 0.75rem;
  font-size: 1.15rem;
  line-height: 1.3;
}

/* Under the box, not above it: it answers "where does this go", which is a
 * question a respondent has after reading the prompt, not before. */
.notes-help {
  display: block;
  margin-top: 0.5rem;
  line-height: 1.5;
  color: var(--text-on-dark-muted);
}

/* Hard right, at every width. Most people are right handed and this is taken
 * one handed on a phone, so the right edge is where the thumb already is;
 * the same reasoning put the Next button on the right of its option row. */
.notes-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 1.5rem;
}

.notes-actions button {
  width: auto;
}

/* One button, two labels, and the textarea decides which one shows.
 *
 * The same move as the Next button under a selected option, D-056: the state
 * is already in the DOM, so CSS reads it and nothing holds a second copy of
 * it. `:placeholder-shown` is true exactly while the box is empty, which is
 * what makes the placeholder attribute on that textarea load bearing: remove
 * it and the label freezes. tests/test_notes_screen.py pins the pair.
 *
 * Written so the skip wording is the default and `:has()` turns it over,
 * rather than the other way round. A browser without `:has()` drops both rules
 * below as invalid, and what is left is one button reading "Nothing to add,
 * continue", which still submits and still saves whatever was typed. The
 * reverse would leave two labels in one button. */
.notes-submit-save {
  display: none;
}

.notes-form:not(:has(textarea:placeholder-shown)) .notes-submit-save {
  display: inline;
}

.notes-form:not(:has(textarea:placeholder-shown)) .notes-submit-skip {
  display: none;
}

.closing-label {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.closing-question {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
}

.closing-help {
  color: var(--ui-muted-color);
  margin-bottom: 0.5rem;
}

.closing-skip-note {
  display: block;
  margin-top: 0.5rem;
  color: var(--ui-muted-color);
}

/* The eyebrow over the church name. Small caps in the muted tone, the same
 * treatment the header bar's section name gets, because it is doing the same
 * job: labelling the big thing next to it. */
.results-eyebrow {
  margin-bottom: 0.35rem;
  font-family: var(--font-family-buttons);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-on-dark-muted);
}

/* The four stages, and the seven findings under them.
 *
 * Two passes over the same rules. D-066 took them off the white card, so every
 * `#f8fafb` became a panel token. D-069 then took the surfaces away
 * altogether: the raised boxes were a second depth saying nothing the spacing
 * did not already say, and on a results page that is seven of them in a
 * column. What separates one finding from the next is a rule, which is what a
 * list of findings wants.
 *
 * `--ui-muted-border` is the only token left in here. It is rebound on
 * `.assessment-panel`, so these hairlines are the right weight on the navy. */
.band-path-legend {
  padding-block: 1.25rem;
  border-block: 1px solid var(--ui-muted-border);
  margin-block: 1.5rem 2rem;
}

.band-path-caption {
  margin-bottom: 0.75rem;
  color: var(--ui-muted-color);
  font-size: 0.9rem;
}

/* The path. A row that wraps, not a four column grid: at 130px minimum the
 * grid broke into two rows of two on a phone, which reads as a square of four
 * options rather than as a sequence. Wrapped text keeps the order.
 *
 * `list-style: none` and no padding, because each stage carries its own
 * coloured dot, and the numbers a browser would put here are the band numbers
 * the respondent must never see. D-004. */
.band-path-track {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.band-path-step {
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.25;
}

/* No chevron between the stages any more. It was a sage arrow before every
 * stage but the first, which on a desktop row read as a path and on a phone
 * did not: the track wraps to one stage per line there, so the first line sat
 * flush left and the other three were indented behind an arrow pointing at
 * nothing. The band dots replace it (D-096). Four coloured dots down the left
 * edge are a key, which is what this block is for, and they line up at every
 * width. */

/* One dimension, as a disclosure. D-065, de-boxed by D-069.
 *
 * base.css styles `details` for the one plain use on the staff side: a bottom
 * rule, a list marker, no box. That is close to what is wanted here, and the
 * rule is the separator, so what this overrides is the marker and the spacing
 * rather than the whole treatment. The last one keeps its rule: it is the
 * bottom edge of the list, not a divider between two things. */
.result-dimension {
  margin-block: 0;
  padding-bottom: 0;
  border-bottom: 1px solid var(--ui-muted-border);
}

/* The whole bar is the hit target, which on a phone is the difference between
 * a disclosure and a dare. 48px to match the option rows. */
.result-dimension-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 48px;
  padding-block: 1rem;
  list-style: none;
}

/* Both halves of removing the default triangle: the pseudo-element is Safari
 * and older WebKit, `list-style` above is everything else. */
.result-dimension-summary::-webkit-details-marker {
  display: none;
}

/* Icon + dimension name grouped on the left; the chevron (via ::after) on the
 * right. min-width: 0 lets a long dimension name truncate rather than overflow
 * past the chevron. */
.result-dimension-heading {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}

.result-dimension-summary h2 {
  margin-bottom: 0;
  font-size: 1.15rem;
  line-height: 1.3;
}

/* The chevron, built from two borders rather than a glyph so it does not
 * depend on a font that may not have one. Pointing down when closed, up when
 * open, and it is `flex: none` because a long section name must not squeeze
 * it to nothing. */
.result-dimension-summary::after {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: -0.25rem;
  border-right: 2px solid var(--button-surface);
  border-bottom: 2px solid var(--button-surface);
  transform: rotate(45deg);
}

.result-dimension[open] > .result-dimension-summary::after {
  margin-top: 0.25rem;
  transform: rotate(-135deg);
}

/* No second rule under an open summary. The section's own bottom rule is
 * right there, and two hairlines a paragraph apart is the stripey look the
 * boxes were removed to get away from. The gap below the body does the
 * separating, and the section rule closes it off. */
.result-dimension[open] > .result-dimension-summary {
  margin-bottom: 0;
}

.result-dimension-body {
  padding: 0 0 1.5rem;
}

/* The band this section reached. It was `.band-tag`: amber fill, amber border,
 * button text colour and the same radius as a button, sitting directly under a
 * heading. It looked exactly like a control and did nothing when tapped.
 *
 * It is not arbitrary and it is not removed, because it is the finding for
 * this section in two words. It is now dressed as what it is, a label: text on
 * the dark surface with no box around it, in the colour of the band it names
 * (below). D-065, coloured by D-096. */
.band-label {
  display: block;
  margin-top: 0.25rem;
  font-family: var(--font-family-buttons);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* The four bands, in colour. D-096.
 *
 * Seven sections collapsed into seven rows is a page you read rather than a
 * page you scan: the finding for each one is two words of small caps in the
 * same amber, so nothing tells you which sections are fine and which are not
 * until you have read all seven. The colour is the scan.
 *
 * Red to green across the four stages, which is the scale everybody already
 * knows, and each one is set once here as `--band-color` so the label, its
 * dot and the next step's rule all take it from the same place. The label text
 * stays, because colour is never the only carrier of meaning: a reader who
 * cannot separate these four still has the words. WCAG 1.4.1.
 *
 * All four are AA on the navy as small text: 4.8, 5.4, 8.0 and 6.0 to one. */
.band-step-0 {
  --band-color: #ef8778;
}

.band-step-1 {
  --band-color: #e89f45;
}

.band-step-2 {
  --band-color: #ead45f;
}

.band-step-3 {
  --band-color: #7fc98c;
}

/* The dot, on both the label and the legend. It is what carries the colour at
 * a glance: two words of 0.75rem small caps is not much coloured area, and the
 * dot is a solid block of it at the start of the line. */
.band-label,
.band-path-step {
  color: var(--band-color, var(--button-surface));
}

.band-label::before,
.band-path-step::before {
  content: "";
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-right: 0.4rem;
  margin-bottom: 0.05rem;
  border-radius: 50%;
  background: var(--band-color, var(--button-surface));
}

/* "Insufficient data" is a state, not an achievement, so it does not get the
 * accent, and it is not one of the four stages, so it gets no dot either: a
 * dot in the fallback amber would read as a fifth band. The muted tone is the
 * panel's, which is 4.6:1 on the surface. */
.band-label-quiet {
  color: var(--ui-muted-color);
}

.band-label-quiet::before {
  display: none;
}

.result-commentary {
  margin-bottom: 0;
  line-height: 1.55;
}

/* The one thing on the page that is an instruction rather than a description,
 * so it keeps a marker: an amber rule down its left side, which is the same
 * shape the error message uses and the only accent left in the body of a
 * finding. No fill behind it. */
.result-next-step {
  margin-top: 1rem;
  margin-bottom: 0;
  padding-inline-start: 1rem;
  border-inline-start: 4px solid var(--band-color, var(--button-surface));
}

.result-resource {
  margin-top: 1rem;
  margin-bottom: 0;
}

/* The team guide, above the findings. The caption under the button is what
 * stops it reading as a second start button: on its own, an outline button at
 * the top of the page is the most prominent thing on it and says nothing about
 * what it opens. */
.results-team-guide {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
  margin-block: 0 2rem;
}

.results-team-guide small {
  color: var(--text-on-dark-muted);
}

/* Call to action at the foot of the results page. No rule above it: the last
 * disclosure already draws one, and two hairlines that close together read as
 * a mistake rather than as a boundary. The space does the separating. */
.results-cta {
  margin-top: 2.5rem;
  padding-top: 2rem;
  text-align: center;
}

.results-cta-heading {
  margin-bottom: 0.5rem;
  font-size: 1.25rem;
  font-weight: 600;
}

.results-cta-body {
  margin-bottom: 1.5rem;
  margin-inline: auto;
  max-width: 36em;
  color: var(--text-on-dark-muted);
}

/* Validation. C14, D-049.
 *
 * `.field-error` is a <small>, which base.css renders smaller and muted, as
 * Pico did before it: the exact opposite of an error, and most of why a
 * missing answer looked like the page had frozen. So the size is pulled back to near body copy and it is given its
 * own line, rather than trailing a label as fine print. */
.field-error {
  display: block;
  margin-top: 0.5rem;
  color: var(--field-error-color);
  font-weight: 600;
  font-size: 0.95rem;
}

/* The form level sentence, at the top of the form. The field markers say which
 * question; this says what happened, which is the part a respondent who has
 * scrolled past a marker actually needs. */
.form-message {
  margin-block: 0 1.5rem;
  padding: 0.8rem 1rem;
  border-inline-start: 4px solid var(--field-error-color);
  border-radius: var(--radius-surface);
  background-color: var(--error-surface);
  color: var(--field-error-color);
  font-weight: 600;
}

/* And the offending question itself, so the eye lands on it rather than
 * hunting. Only the group that failed carries this.
 *
 * A red rule down the side, which is what `.form-message` above does, rather
 * than the red wash and red border it used to have: D-069 left the fieldset
 * with no border to recolour and no surface to tint. The marker is the same
 * shape as the one on the message that sent the respondent here. */
.question-fieldset-invalid {
  padding-inline-start: 1rem;
  border-inline-start: 4px solid var(--field-error-color);
}


/* The site footer. D-071. templates/partials/_footer.html, which is opt in
 * and says why there. */
.site-footer {
  background-color: var(--ground-footer);
  color: var(--text-on-dark);
}

/* A centred column, not a row that collapses: three items, each of which wants
 * the full width to centre inside, and no width at which putting them side by
 * side would read better.
 *
 * No `width` declared here. This element is `.site-footer-inner container` and
 * app.css loads after base.css at equal specificity, so a width on this rule
 * would silently beat the container's own 90%/80%. Same trap as .hero-inner
 * and .site-header-inner; see the comment on .hero-inner. */
.site-footer-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  padding-block: 3rem;
  text-align: center;
}

/* Full sized, which is what the header's lockup is not: there it is a 2.4rem
 * strip in a bar, here it is the thing the footer is for. Capped rather than
 * left to the container, which is 1280px wide on a desktop and would stretch
 * a 750px image past its own resolution. */
.site-footer-logo {
  display: block;
  width: 100%;
  max-width: 19rem;
  height: auto;
}

/* The two accounts, inline under the logo. A list, because that is what two
 * links are, with the marker and the indent taken off. */
.site-footer-social {
  display: flex;
  align-items: center;
  gap: 1.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* On the item as well as the list, which is what the nav lists do and for the
 * reason given in test_navigation.py: `list-style` only reaches the marker by
 * inheritance, so any rule naming the <li> beats it. base.css has none today.
 * The margin does come from base.css, and is the gap this row does not want. */
.site-footer-social li {
  margin: 0;
  list-style: none;
}

/* The underline the generic link rule draws has nothing to underline but the
 * icon's box, where it reads as a stray rule rather than a link. The hover
 * state is opacity instead: the files are flat white, so there is no second
 * colour to move to without shipping a second pair of them. */
.site-footer-social a {
  display: block;
  line-height: 0;
  text-decoration: none;
  opacity: 0.8;
  transition: opacity 0.15s ease-in-out;
}

.site-footer-social a:hover,
.site-footer-social a:focus-visible {
  opacity: 1;
}

.site-footer-social img {
  display: block;
  height: 1.6rem;
  width: auto;
}

/* Last, and smaller than body copy: it is the one thing here that is a legal
 * obligation rather than something anybody came for. Sage on black is 10.3:1,
 * so the size is the only thing holding it back. */
.site-footer-legal {
  margin: 0;
  font-size: 0.9rem;
}

/* ---- The staff shell --------------------------------------------------- */

/* A left sidebar and a content area, which is what /app has needed since there
 * was more than one staff screen. D-078, templates/staff/_shell.html.
 *
 * What was there before: every staff page extended base.html and got the
 * *public* header, so a signed in user on a submission detail screen had Home,
 * Contact and Envision training in the bar and no link to any other staff
 * screen. The only way back was the browser's back button.
 *
 * The sidebar is `position: fixed` rather than a grid column, for the reason
 * every dashboard does it: the staff screens are long tables, and a column in
 * the flow scrolls its own navigation off the top of a 300 row submissions
 * list. Fixed also means the two presentations, a column at desktop and a full
 * screen overlay on a phone, are the same element at two widths rather than
 * two copies of one link list.
 *
 * Mobile first, so the phone case is the base rule and the desktop sidebar is
 * the media query. The open state is `html.staff-nav-open`, set by
 * `staffNav.toggle()` in app.js: `x-show` cannot be used here because the
 * inline `display: none` it writes when closed would outrank the media query
 * below, and a sidebar closed once on a phone would stay closed on a desktop. */
:root {
  /* One number, used by the sidebar's own width and by the offset on <main>
   * that keeps the content clear of it. Two literals here is how the content
   * ends up either overlapped or floating. */
  --staff-sidebar-width: 15rem;
}

/* The bar that holds the hamburger on a phone. Sticky rather than fixed: it is
 * in the flow, so <main> starts below it without an offset, and it stays put
 * while a long table scrolls under it. */
.staff-topbar {
  position: sticky;
  top: 0;
  z-index: 11;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 1rem;
  background-color: var(--ground-page);
  border-bottom: 1px solid var(--border-on-dark);
}

/* Above the overlay, so the cross stays visible and tappable in the spot the
 * bars were just tapped in. Same arrangement as the public header. */
.staff-topbar .wordmark {
  line-height: 0;
}

.staff-topbar .wordmark-mark {
  height: 2.4rem;
  width: auto;
}

/* The phone overlay. `inset: 0` with the topbar's z-index above it leaves the
 * cross reachable; `padding-top` clears the bar so the first link is not under
 * it. A column with its account controls at the bottom at every width, which is
 * the one piece of the layout that is not breakpoint dependent. */
.staff-sidebar {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: none;
  flex-direction: column;
  gap: 2rem;
  /* Inline padding is zero and every child owns the gutter, so a nav row can
     run the full width of the column. See .staff-nav-links a. */
  --staff-sidebar-gutter: 1.5rem;
  padding: 5rem 0 2rem;
  overflow-y: auto;
  background-color: var(--ground-page);
}

html.staff-nav-open .staff-sidebar {
  display: flex;
}

/* The overlay is fixed, so the page scrolls on underneath it without this.
 * Both elements, because iOS Safari has long ignored `overflow` on <body>
 * alone. Released in the desktop query below, or a phone turned to landscape
 * with the menu open is frozen with nothing on screen to close. */
html.staff-nav-open,
html.staff-nav-open body {
  overflow: hidden;
}

/* The lockup at the top of the column. Hidden on the phone overlay, because
 * the topbar two rows up is already showing it and the padding above the first
 * link is there to clear that bar. */
.staff-sidebar-brand {
  display: none;
  padding-inline: var(--staff-sidebar-gutter);
  line-height: 0;
}

.staff-sidebar-brand .wordmark-full {
  display: block;
  width: 100%;
  max-width: 11rem;
  height: auto;
}

.staff-nav-links {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* On the item as well as the list, for the reason given in test_navigation.py:
 * `list-style` only reaches the marker by inheritance, so any rule naming the
 * <li> beats it. The margin is base.css's `li { margin-block: 0.25rem }`, which
 * this row spacing replaces. */
.staff-nav-links li {
  margin: 0;
  list-style: none;
}

/* A row, not a link: the whole width is the target, which is what makes this
 * read as navigation rather than as prose with links in it. The sage link
 * colour is overridden to white here for the same reason the public header does
 * it, and the current row is the one that keeps a surface. */
.staff-nav-links a {
  display: block;
  /* Full bleed and square. A rounded row with a rounded rule down its left
     edge was the first draft and it read as decoration: the corner radius
     pulls the bar away from the edge of the column, so the marker floats in
     the middle of nothing. Flush to the edge, the bar is a tab marker, which
     is a thing the eye already knows how to read.

     The left border is on every row, transparent until it is the current one,
     so marking a row does not move its text. 3px comes off the padding rather
     than being added to it, which is what keeps the label on the same vertical
     line as the wordmark above and the username below. */
  border-left: 3px solid transparent;
  padding: 0.6rem var(--staff-sidebar-gutter);
  padding-left: calc(var(--staff-sidebar-gutter) - 3px);
  color: var(--text-on-dark);
  font-size: 1.05rem;
  text-decoration: none;
}

.staff-nav-links a:hover,
.staff-nav-links a:focus-visible {
  background-color: var(--raise-on-dark);
  color: var(--text-on-dark);
  text-decoration: none;
}

/* The current section, marked with a surface and the amber bar rather than an
 * underline: an underline on a full width row reads as a divider. */
.staff-nav-links a[aria-current="page"] {
  background-color: var(--raise-on-dark);
  border-left-color: var(--button-surface);
  color: var(--text-on-dark);
  font-weight: 600;
}

/* `margin-top: auto` is what puts the account controls at the bottom of the
 * column, which is where the user asked for them and where a dashboard puts
 * the things used once a session. In the phone overlay it does the same thing,
 * because the overlay is the full viewport height. */
.staff-sidebar-foot {
  margin-top: auto;
  padding-top: 1.5rem;
  padding-inline: var(--staff-sidebar-gutter);
  border-top: 1px solid var(--border-on-dark);
}

.staff-whoami {
  margin: 0 0 0.75rem;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text-on-dark);
}

/* The role on its own line under the username, quieter than it. Not a badge:
 * two of them exist, they never change, and a pill would make the one piece of
 * chrome nobody reads twice the loudest thing in the column. */
.staff-role {
  display: block;
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--text-on-dark-muted);
}

/* Full width, matching the nav rows above it rather than the buttons on the
 * content screens. base.css gives a submit `width: 100%` and this is not one,
 * so the width is declared. */
.staff-signout {
  width: 100%;
  margin-top: 0.75rem;
}

/* The one form on this surface, and the reason it needs a cap: three password
 * fields across a 1280px container read as a mistake, because a field's width
 * is a hint about how much to type. 540px is the login card's width, so the
 * two password forms in this application are the same shape. */
.staff-form-narrow {
  width: 100%;
  max-width: 540px;
}

/* From tablet up the column is permanent and there is nothing to open: both the
 * topbar and the toggle go away, which is also what guarantees the overlay can
 * never be left open behind a resize. */
@media (min-width: 768px) {
  .staff-topbar {
    display: none;
  }

  .staff-sidebar {
    inset: 0 auto 0 0;
    display: flex;
    width: var(--staff-sidebar-width);
    --staff-sidebar-gutter: 1rem;
    padding: 2rem 0;
    border-right: 1px solid var(--border-on-dark);
  }

  .staff-sidebar-brand {
    display: block;
  }

  /* The offset that keeps the content clear of the fixed column. On <body>
   * rather than on <main>, because <main> is also `.container`: a width-ish
   * declaration on it would silently beat the container's own 80%, which is
   * the trap .hero-inner, .site-header-inner and .site-footer-inner all had to
   * be written around. Padding on the parent leaves the container measuring
   * itself against the space that is left. */
  body.staff-shell {
    padding-left: var(--staff-sidebar-width);
  }

  /* 80% of the remaining width leaves a 16 column table in a third of the
   * screen, so the staff content area takes the room it has. This is the
   * deliberate version of the trap described above: it names <main> inside the
   * staff body class, so it cannot reach the public container. */
  body.staff-shell > main.container {
    width: auto;
    max-width: none;
    margin-inline: 0;
    padding-inline: 2rem;
  }

  html.staff-nav-open,
  html.staff-nav-open body {
    overflow: visible;
  }
}

/* ---- Staff content: headings, records, tables --------------------------- */

/* Centred headings and a bigger gap between sections, both asked for after
 * the shell landed: the admin screens are long stacks of panels and a left
 * aligned h2 four lines below the previous table does not read as the start of
 * anything. Scoped to body.staff-shell, so the respondent flow, where a left
 * edge runs all the way down the page, is untouched. */
body.staff-shell h1,
body.staff-shell h2 {
  text-align: center;
}

body.staff-shell h2 {
  margin-bottom: 1.25rem;
}

body.staff-shell section + section {
  margin-top: 3rem;
}

/* A record of short fields: a two column grid with the label in the left
 * column, not a <dl> at browser defaults, which puts the term and its value in
 * the same type at the same weight one line apart and leaves you reading a
 * list of twelve identical looking lines to find one value.
 *
 * The label is the quieter of the two. It is the part you already know you are
 * looking for, so it is small, spaced and muted, and the value keeps the body
 * size and colour. The row rule is what lets the eye track across a wide
 * value column back to the right label. */
.record-list {
  display: grid;
  grid-template-columns: minmax(6rem, max-content) 1fr;
  margin: 0;
}

.record-list dt {
  padding: 0.6rem 1.5rem 0.6rem 0;
  border-top: 1px solid var(--ui-card-border, #e5e7eb);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  line-height: 1.6;
  text-transform: uppercase;
  color: var(--text-on-dark-muted);
}

.record-list dd {
  margin: 0;
  padding: 0.6rem 0;
  border-top: 1px solid var(--ui-card-border, #e5e7eb);
}

/* No rule above the first row: it would sit directly under the section
 * heading's own space and read as the heading being underlined. */
.record-list dt:first-of-type,
.record-list dd:first-of-type {
  border-top: 0;
}

/* One column on a phone, with the label above its value. The row rule moves to
 * the label only, because in one column a rule on both halves draws a line
 * between a label and the value it belongs to. */
@media (max-width: 36rem) {
  .record-list {
    grid-template-columns: 1fr;
  }

  .record-list dt {
    padding: 0.75rem 0 0;
  }

  .record-list dd {
    padding: 0.15rem 0 0;
    border-top: 0;
  }
}

/* The other list on the submission screen: a dimension comment is a paragraph
 * under a label, so it stays stacked and gets the label treatment without the
 * grid. */
.record-notes {
  margin: 0;
}

.record-notes dt {
  margin-top: 1.25rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-on-dark-muted);
}

.record-notes dt:first-of-type {
  margin-top: 0;
}

.record-notes dd {
  margin: 0.35rem 0 0;
}

/* Every staff table is inside one of these. base.css deliberately sets no
 * overflow on `table` and left the decision here, and the decision is that a
 * table scrolls in its own box: a sixteen column table on a phone was pushing
 * the viewport wider than the layout, so the header, the footer and every
 * paragraph on the page scrolled sideways with it and the whole screen read as
 * broken rather than as one wide table.
 *
 * `overscroll-behavior-x: contain` stops a swipe that reaches the end of the
 * table from turning into the browser's back gesture. */
.table-scroll {
  margin-block: 0 1rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

.table-scroll > table {
  margin-block: 0;
}

/* The pair that makes the scroll mean something. Without the nowrap a table
 * with `width: 100%` simply squeezes its columns into whatever space there is,
 * which is the unreadable version of the same problem and never scrolls; with
 * it the table grows to the width its content actually needs and the box
 * scrolls. A narrow table still fits, so nothing scrolls that does not have
 * to. */
.table-scroll th,
.table-scroll td {
  white-space: nowrap;
}

/* The exception, on the handful of cells holding a sentence rather than a
 * label: a question text or a URL at nowrap is a single line a metre wide. The
 * min-width is what stops auto table layout from giving a wrapping column two
 * words per line because every other column refused to shrink. */
.table-scroll .cell-wrap {
  min-width: 16rem;
  white-space: normal;
}
