/* Verigrant web: one stylesheet, no framework.
 *
 * # It is one product, and this is half of one system
 *
 * The other half is `web/marketing/styles.css`. The two sheets are deliberately
 * built the same way and out of the same token names, because a reader crosses
 * between them without being told: the site root is this application's own
 * document, the deep pages are the marketing site's, and a person who follows
 * "Security" out of the workspace and comes back has been in both in ten
 * seconds. Where a value appears in both, it is the same value here.
 *
 * # Both themes, and neither is an afterthought
 *
 * Light is the default and dark is a `prefers-color-scheme` override, which is
 * the arrangement the marketing site already had and the reason it is worth
 * matching: an application that was dark on a light site read as a different
 * product wearing the same wordmark. Every colour below is a token and the two
 * themes differ in one block and nowhere else, so a component cannot be styled
 * for one theme and broken in the other without that block being edited.
 *
 * The dark values are the palette this application has always had. The light
 * ones are chosen rather than inverted: the accent darkens to #0b6e58 because
 * the dark theme's #4cc2a5 is a mid tone picked against a near black ground and
 * carries no text on white, and the page sits a step off white so that a card
 * has an edge without needing a shadow to find one.
 *
 * # The scales
 *
 * Type, space, radius and control height are each set once at the top and
 * referenced everywhere below. That is what keeps ten screens from drifting
 * into ten scales, and it is why there are almost no bare pixel values further
 * down: a number written inline is a decision nobody can find again.
 *
 * Mobile first. The layout is a single column that gains columns as there is
 * room for them, and the workspace stays a workspace on a phone rather than
 * becoming a desktop layout with the middle cut out. */

:root {
  /* Form controls, scrollbars and the rest of the browser's own furniture
     follow the theme rather than staying light under a dark page. */
  color-scheme: light dark;

  /* Colour -----------------------------------------------------------
     --bg is the ground the workspace sits on and --surface is a card on it.
     They are deliberately different in both themes: a card that is the same
     colour as the page needs a shadow to be a card, and a workspace full of
     shadows is a workspace that is shouting. */

  --bg: #f4f8fa;
  --bg-soft: #eaf1f5;
  --surface: #ffffff;
  --surface-2: #eaf0f4;
  --field: #ffffff;
  --line: #dbe4ea;
  --line-strong: #c2d0da;
  --text: #0f1720;
  --muted: #55697c;
  --accent: #0b6e58;
  --accent-strong: #085546;
  --accent-soft: rgba(11, 110, 88, 0.08);
  --accent-line: rgba(11, 110, 88, 0.28);
  --accent-ink: #ffffff;
  --danger: #a3301f;
  --danger-soft: rgba(163, 48, 31, 0.08);
  --danger-line: rgba(163, 48, 31, 0.3);
  --danger-ink: #ffffff;
  /* Amber, and it is a third colour rather than a shade of the danger one on
     purpose. A pending check, a suspended account and a key taken on trust are
     all things to look at; none of them has gone wrong, and colouring them red
     teaches a person to dismiss red. */
  --warn: #8a5a12;
  --warn-soft: rgba(138, 90, 18, 0.1);
  --warn-line: rgba(138, 90, 18, 0.34);

  /* Three states, three colours -------------------------------------------
   *
   * The accent above is the brand and it is what a person can press. It was
   * also, until now, what said a check had come back verified, what filled a
   * strong password meter, what marked an unread notice and what tinted the one
   * match on the home screen. Five different kinds of news in one green, on a
   * page whose primary control is that same green, is a screen on which nothing
   * is distinguishable from anything: the founder's word for it was blobs, and
   * the word is right. Colour that is spent everywhere is colour that says
   * nothing anywhere.
   *
   * So the accent keeps the one job it is actually for, which is an action, and
   * two states are given a hue of their own.
   *
   * --ok is good news that has already happened: a check that came back
   * verified, a key that is unlocked, a bundle that opened, a password that is
   * strong. It is a green because that is what a verdict of "this is fine"
   * looks like in every interface anybody has used, and it is deliberately not
   * the brand green: the leaf is warmer and a shade lighter than the accent's
   * teal, which is what lets a verified badge sit beside a primary button
   * without the two reading as one thing.
   *
   * --info is the market answering, rather than the person acting. A match and
   * its score, a notice nobody has read, a key this browser has just minted:
   * none of them is a success and none of them is a warning, and all of them
   * were wearing the colour of the button beside them. Blue is what the rest of
   * the world reads as a note, and it is far enough from both greens that a
   * column of cards is scannable again.
   *
   * Both are held to 4.5:1 against every ground this sheet puts text on, in
   * both themes, by `stylesheet::every_hue_this_application_colours_with_is_legible`. */

  --ok: #1f7a3d;
  --ok-soft: rgba(31, 122, 61, 0.1);
  --ok-line: rgba(31, 122, 61, 0.32);
  --info: #15539c;
  --info-soft: rgba(21, 83, 156, 0.09);
  --info-line: rgba(21, 83, 156, 0.3);

  /* The four audiences, one hue each ---------------------------------------
   *
   * The front door asks who is reading it and offers four answers, and each
   * answer swaps the screens underneath for a different product: a person's own
   * record, an institution's queue, an admissions record, and what an assistant
   * acting for somebody is handed. Four positions of one control, filled in one
   * green, said none of that. Which position was chosen was carried by the word
   * inside it, and the screen it swapped to looked exactly like the screen
   * before it.
   *
   * So each audience owns a colour, and the colour follows the choice all the
   * way down: the position in the selector is filled with it, the heading that
   * answers the choice is set in it, and the record standing on the platform is
   * edged in it. A reader who presses "School" now sees the screen change
   * rather than reads that it did.
   *
   * Two of the four are hues this sheet already has, referenced rather than
   * respelled. The person applying wears the brand colour because this product
   * is theirs and every other audience here is somebody they apply to; the
   * institution wears the informational blue, because an institution on this
   * side of the market is exactly what --info is for. The other two are new.
   *
   * They are ordered so that no two neighbours in the control are neighbours on
   * the wheel: green, blue, plum, teal. Adjacent positions in one hue family
   * would have put the differentiation back where it started for the two
   * readers most likely to confuse them.
   *
   * --audience, --audience-soft and --audience-line are the chosen one,
   * resolved by `.stage-<slug>` further down this file. The default is the
   * seeker's, which is what the application opens on and what the still frame
   * in `index.html` is a picture of. */

  --audience-seeker: var(--accent);
  --audience-seeker-soft: var(--accent-soft);
  --audience-seeker-line: var(--accent-line);
  --audience-employer: var(--info);
  --audience-employer-soft: var(--info-soft);
  --audience-employer-line: var(--info-line);
  --audience-school: #8a2f6a;
  --audience-school-soft: rgba(138, 47, 106, 0.1);
  --audience-school-line: rgba(138, 47, 106, 0.32);
  --audience-assistant: #10657a;
  --audience-assistant-soft: rgba(16, 101, 122, 0.1);
  --audience-assistant-line: rgba(16, 101, 122, 0.32);
  /* One ink for all four, because all four are dark enough in this theme to
     carry white and light enough in the other to carry the near black below.
     Four inks would be four decisions nobody could check; this one is checked. */
  --audience-ink: #ffffff;
  --audience: var(--audience-seeker);
  --audience-soft: var(--audience-seeker-soft);
  --audience-line: var(--audience-seeker-line);

  /* The ground the platform layer sits on, behind the record.
     Chosen per theme rather than reused from --bg-soft, because "behind" is a
     different direction in the two of them: on a light page a recessed plane is
     a step darker, and on a dark one it is a step darker still. --bg-soft is
     lighter than --bg in the dark theme, which is the right value for a raised
     field and exactly the wrong one here. Both of these hold --muted at better
     than 4.5:1, because the explanation on this layer is quiet and still has to
     be readable. */
  --stage: #e6edf2;
  /* The ground behind the one panel in this application that covers the page.
     Chosen per theme like --stage and for the same reason: a scrim has to read
     as the page being set back rather than as a grey rectangle, and the amount
     of ink that does that on a near white page is not the amount that does it
     on a near black one. */
  --scrim: rgba(15, 23, 32, 0.45);
  --shadow-sm: 0 1px 2px rgba(15, 23, 32, 0.06);
  --shadow: 0 1px 2px rgba(15, 23, 32, 0.05), 0 10px 26px -18px rgba(15, 23, 32, 0.3);
  /* One step above --shadow, and the only thing in this application that gets
     it: the record, lifted off the layer that explains it. A second elevation
     used anywhere else would make this one mean nothing. */
  --shadow-lift: 0 2px 4px rgba(15, 23, 32, 0.06), 0 26px 50px -30px rgba(15, 23, 32, 0.5);

  /* Type -------------------------------------------------------------
     One scale, and a shallow one. This is a workspace rather than a landing
     page: the largest thing on a screen is a heading that says which screen it
     is, and a display size here would be a column of shouting. The one
     exception is the served document's hero, which is a page and sets its own
     size from --size-display. */

  --text-xs: 0.78rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lede: 1.0625rem;
  --size-display: clamp(1.9rem, 1.3rem + 2.6vw, 2.75rem);
  --size-h1: clamp(1.4rem, 1.2rem + 0.7vw, 1.75rem);
  --size-h2: 1.1875rem;
  --size-h3: 1.0625rem;
  --size-h4: 0.95rem;
  --leading: 1.55;
  --leading-tight: 1.22;

  /* Space ------------------------------------------------------------
     --space-4 is the gap between two cards and the padding inside one, which
     is what makes a column of cards read as an even rhythm rather than as a
     stack of boxes with their own opinions. */

  --space-1: 0.25rem;
  --space-2: 0.4rem;
  --space-3: 0.65rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2.25rem;
  /* The marketing site's own container and gutter, to the pixel. That is the
     point of the number rather than a coincidence: the site root is this
     application and /security.html is a marketing page, so a reader who follows
     one and comes back has watched the header and the footer either stay put or
     jump. It is also wider than the 46rem this column used to be, which is what
     lets the workspace put the record and a match beside each other. Prose is
     held to --measure inside a card rather than to the column, so widening the
     column does not widen a paragraph. */
  --container: 1120px;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --measure: 68ch;
  --header-h: 3.5rem;

  /* Shape ------------------------------------------------------------ */

  --radius-xs: 6px;
  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 16px;
  --radius-pill: 999px;
  --control-h: 2.5rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f1720;
    --bg-soft: #131e28;
    --surface: #16212c;
    --surface-2: #1e2c3a;
    --field: #0f1720;
    --line: #2a3b4c;
    --line-strong: #3a4f63;
    --text: #e8eef4;
    --muted: #9db0c1;
    --accent: #4cc2a5;
    --accent-strong: #6ad3b9;
    --accent-soft: rgba(76, 194, 165, 0.12);
    --accent-line: rgba(76, 194, 165, 0.34);
    --accent-ink: #04241c;
    --danger: #ff8f8f;
    --danger-soft: rgba(255, 143, 143, 0.12);
    --danger-line: rgba(255, 143, 143, 0.34);
    --danger-ink: #1a0e0e;
    --warn: #e6b566;
    --warn-soft: rgba(230, 181, 102, 0.12);
    --warn-line: rgba(230, 181, 102, 0.34);
    /* The two states, lifted off the near black ground the way every other hue
       in this block is: the light theme's values carry white and would be mud
       here, so these are chosen against #0f1720 rather than lightened from
       above. */
    --ok: #6fd08c;
    --ok-soft: rgba(111, 208, 140, 0.12);
    --ok-line: rgba(111, 208, 140, 0.34);
    --info: #7fb8f2;
    --info-soft: rgba(127, 184, 242, 0.12);
    --info-line: rgba(127, 184, 242, 0.34);
    /* And the two audiences that are not one of the hues above. The seeker
       follows --accent and the institution follows --info, through the
       references in the light block, so neither is restated here: a hue written
       down twice is a hue that is the same colour until somebody edits one of
       them. */
    --audience-school: #f09cc6;
    --audience-school-soft: rgba(240, 156, 198, 0.12);
    --audience-school-line: rgba(240, 156, 198, 0.34);
    --audience-assistant: #5cc5da;
    --audience-assistant-soft: rgba(92, 197, 218, 0.12);
    --audience-assistant-line: rgba(92, 197, 218, 0.34);
    --audience-ink: #04141c;
    --stage: #0a1119;
    --scrim: rgba(3, 7, 12, 0.66);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 14px 34px -22px rgba(0, 0, 0, 0.9);
    --shadow-lift: 0 2px 6px rgba(0, 0, 0, 0.5), 0 30px 60px -32px rgba(0, 0, 0, 1);
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font: var(--text-base) / var(--leading) system-ui, -apple-system, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* Room for the iOS home indicator when installed. */
  padding-bottom: env(safe-area-inset-bottom);
}

h1,
h2,
h3,
h4 {
  line-height: var(--leading-tight);
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-3);
  text-wrap: balance;
}

h1 {
  font-size: var(--size-h1);
  letter-spacing: -0.022em;
}
h2 {
  font-size: var(--size-h2);
}
h3 {
  font-size: var(--size-h3);
}

/* Prose is held to a measure inside the card rather than the card being held
   to one. The column is wide because two cards stand side by side in it; a
   sentence that ran the whole width would be scanned instead of read. */
.card p,
.card li,
.banner p {
  max-width: var(--measure);
}

/* Focus, everywhere and visibly -------------------------------------------
 *
 * One rule rather than a ring per control. Every tab, every button, every link
 * and every field in this application is reached from a keyboard by somebody,
 * and the browser's own outline disappears against a dark surface at the exact
 * moment it is the only thing telling a person where they are. `:focus-visible`
 * rather than `:focus` so a mouse press does not leave a ring behind it. */

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* Read aloud, not drawn. For the words a screen reader needs and a sighted
 * reader already has from the layout: the title of a screen whose tab is
 * already marked current, or what the number on a badge counts. Kept in the
 * accessibility tree, which `display: none` would not do. */

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

/* Header ------------------------------------------------------------------
 *
 * The same bar as the marketing site's: the wordmark at one end, one cluster of
 * controls at the other, the same height, the same hairline under it, and the
 * same column width as the content below so nothing sits a few pixels off the
 * thing it belongs to. */

.app-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  padding-top: env(safe-area-inset-top);
}

.app-header-inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  align-items: center;
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding: var(--space-3) var(--gutter);
  min-height: var(--header-h);
  /* What the two navigation panels hang off. Both of them are taken out of the
     bar's flow so that the bar stays one line high while either is open, and an
     absolute inset resolves against the nearest positioned ancestor: without
     this it would be `.app-header`, which is the full width of the window, and
     the dropdown on a wide screen would line up with the edge of the display
     rather than with the end of the header it belongs to. */
  position: relative;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
  font-size: 1.0625rem;
  letter-spacing: -0.02em;
  color: var(--text);
  text-decoration: none;
  /* The wordmark pushes everything else to the far end, which is what makes
     the controls on the right read as one cluster. `.header-actions` pushes
     from the other side, so the group lands on the edge whether or not there is
     a tab strip on the line between them. */
  margin-right: auto;
}

.mark {
  display: grid;
  place-items: center;
  width: 1.625rem;
  height: 1.625rem;
  border-radius: var(--radius-xs);
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 800;
  font-size: 0.95rem;
}

/* Ten tabs do not fit across a phone. They scroll sideways rather than
 * wrapping to three rows, which would push the content below the fold before
 * it had drawn anything. The scrollbar is hidden because a scrollbar under a
 * row of pills is a second rule across the header, and snapping makes a flick
 * land on a tab rather than between two.
 *
 * Hiding it cost something, and `.tabstrip` below is what repays it. The claim
 * used to be that the overflow was obvious from the clipped tab at the edge.
 * It is not. At 375 pixels four tabs are on screen and the clipped fifth reads
 * as a tab that happens to end near the edge, so six screens of this product
 * sat behind a gesture nobody had been told about. */
.tabs {
  display: flex;
  gap: 0.25rem;
  width: 100%;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* The row is a horizontal scroller inside a page that scrolls vertically;
   * without this a diagonal drag scrolls both. */
  overscroll-behavior-x: contain;
  padding-bottom: 0.15rem;
  /* Two things read this. The browser's own scrolling does, which is what a
   * keyboard gets when it tabs along the strip, so a focused tab is never left
   * sitting under the fade that exists to say there are more of them. And
   * `app::show_active_tab` uses the same distance for the one case the browser
   * is not doing the scrolling, which is a tab changed by a press made
   * somewhere else on the screen. */
  scroll-padding-inline: 2.25rem;
  /* The offset parent of every tab in the row, which is what makes `offsetLeft`
   * read as a tab's place in the scrolled content rather than its place on the
   * screen. `app::show_active_tab` is the only reader, and it is why this is
   * here rather than on the wrapper. */
  position: relative;
}

.tabs::-webkit-scrollbar {
  display: none;
}

/* The two fades, and the whole of what they are for ------------------------
 *
 * The strip scrolls and its scrollbar is hidden, so without these a row that
 * continues past the edge of a phone looks exactly like a row that does not. A
 * gradient into the header's own colour is the smallest thing that says "this
 * continues": it adds no control, it takes no width from the tabs, and it is
 * the bar itself dissolving rather than an ornament laid over it.
 *
 * Each is drawn only while there is something behind it. `app::TabStrip`
 * measures the scroll and sets the two classes, so a strip whose tabs all fit
 * carries neither. That is the case a wide screen is normally in, which is why
 * nothing here is visible on a desktop that was not already clipping its tabs.
 *
 * Physical `left` and `right` rather than the logical pair used elsewhere in
 * this file, and deliberately: a gradient has no logical direction, so a
 * logical inset would put the opaque end of the fade at the wrong edge the day
 * this application is read right to left. Two properties that agree are better
 * than one that is half correct.
 *
 * `pointer-events: none` because a fade is a statement and not a target: the
 * tab under the right-hand one is still a tab to press. */
.tabstrip {
  position: relative;
  order: 3;
  flex: 1 0 100%;
  min-width: 0;
}

.tabstrip::before,
.tabstrip::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2.25rem;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 120ms ease;
}

.tabstrip::before {
  left: 0;
  background: linear-gradient(to right, var(--surface), transparent);
}

.tabstrip::after {
  right: 0;
  background: linear-gradient(to left, var(--surface), transparent);
}

.tabstrip.more-start::before,
.tabstrip.more-end::after {
  opacity: 1;
}

.tab {
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  padding: 0.4rem 0.85rem;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  scroll-snap-align: start;
  font-size: var(--text-sm);
  font-weight: 560;
  /* Every tab is a tap target on a touch screen, and 2.75rem is the smallest
   * one that is comfortably hit without looking. */
  min-height: 2.75rem;
  transition: color 120ms ease, background-color 120ms ease;
}

.tab:hover {
  color: var(--text);
}

.tab.active {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--text);
}

/* The unread count, riding on the Notifications tab. */
.pip {
  display: inline-grid;
  place-items: center;
  min-width: 1.25rem;
  height: 1.25rem;
  margin-left: 0.4rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 0.7rem;
  font-weight: 700;
}

/* The account menu, at the far end of the bar. Ordered rather than placed by
   markup because the tab strip between it and the wordmark moves: it is a row of
   its own under a phone and part of this line above one.

   The two doors used to be here as well. "Create your own" is now `.header-create`
   below and stands beside the hamburger at every width, and "Sign in" is inside
   the menu with the rest of the entry navigation, so what is left under this
   class is a signed-in account's own name and its way out. */
.account {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  order: 2;
}

/* The group at the end of the bar -----------------------------------------
 *
 * The offer and the control, held together and held to the edge. Everything a
 * reader can press in this header is in here, which is what makes the bar two
 * things rather than four: the wordmark at one end and this at the other.
 *
 * It was two loose flex items until the founder's review, and loose items are
 * placed by whatever else is on the line. With a tab strip between them and the
 * wordmark the pair sat at the edge; on the screens that draw no strip they
 * drifted into the middle of the bar, and at a narrow width they could be left
 * with different amounts of air around them. An element with an auto margin in
 * front of it has one place to be at every width, and the space between the
 * button and the control is one gap set once rather than the header's own,
 * which is also the gap between the wordmark and whatever follows it.
 *
 * `order: 4` is last, behind the tab strip at either of the two orders that
 * strip takes. `flex: 0 0 auto` because the two things inside are a button and
 * a 44 pixel target, and neither has anything to give: a narrow screen wraps
 * the whole group onto a line of its own, still at the end, rather than
 * squeezing it. */
.header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  order: 4;
  flex: 0 0 auto;
  margin-inline-start: auto;
}

/* The one thing beside the control.
 *
 * A visitor's single offer, and the only thing on the bar that is something to
 * do rather than somewhere to go. The entry navigation that used to sit around
 * it is behind the hamburger now, at every width, which is argued in
 * `app::NAV_MENU_ID` and at the markup.
 *
 * It needs no `order` of its own any more: it is the first child of the group
 * above, and a group is read from its start. `nowrap` because a primary button
 * that breaks across two lines inside a bar takes the bar with it. */
.header-create {
  white-space: nowrap;
}

.who {
  color: var(--muted);
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 14rem;
}

/* The way back to the explanation, inside the menu on every screen of the
   application.
 *
 * It used to be ordered into the bar just ahead of the doors. It is inside
 * `.header-entry` now, with the rest of the entry navigation, so it needs no
 * `order` at all: the group it is in is a column, and a column is read top to
 * bottom.
 *
 * Deliberately `.link` rather than a button with a border. What this offers is
 * an explanation, and a panel of bordered boxes is a panel of things that all
 * look equally like the thing to press. */
.howto {
  justify-self: start;
}

/* The header behind one control -------------------------------------------
 *
 * # The problem this started as
 *
 * Eleven tabs do not fit across a phone, and the answer this file carried for a
 * long time is that they scroll sideways with a fade at each end saying so. That
 * is a good answer to "the row is too long" and the wrong answer to the question
 * a person holding a phone actually asks, which is "where are the other
 * screens". A row that scrolls is still a row: it takes a line of the header at
 * every width, it is read by swiping rather than by looking, and beside it the
 * bar was also carrying a wordmark, two doors and a third control. At 375 pixels
 * that is a header with four things in it and a product underneath.
 *
 * # The problem it turned out to also be, on a desktop
 *
 * The bar at 1280 pixels was not crowded, and it was worse. A visitor arrives
 * *inside* the running product, which is this front door's whole argument, and
 * the bar answered that by offering them four audience pages, a sign in and an
 * explanation. Every one of those is a way out of the realisation the page
 * exists to produce, and the two loudest of them are a link to a page about the
 * thing the reader is already looking at and a form for people who are not the
 * reader.
 *
 * # What this is, at both widths
 *
 * A hamburger, and a panel under it. On a phone the panel is the whole header:
 * the sections, the workspace switch, the account's own controls, and the entry
 * navigation. On a wider screen the bar keeps the tab strip, because the tab
 * strip is the workspace rather than a way out of it, and the panel is the entry
 * navigation alone. Beside the control, at every width, one primary button.
 *
 * # Why it is one element and not a second header
 *
 * `.header-rest` is `display: contents` above 40rem, which means it is not a box
 * at all: every child of it is a flex item of `.app-header-inner` exactly as it
 * was before this wrapper existed, carrying the same `order`. Under 40rem the
 * same element is the panel. There is one list of sections, one set of pages and
 * one "How Verigrant works" in the markup, and the width decides how they are
 * drawn rather than which of two copies is shown. `app::NAV_MENU_ID` argues that
 * at length.
 *
 * The toggle carries `aria-expanded` and `aria-controls`, `app::App` moves the
 * keyboard into the panel when it opens and back onto the toggle when it
 * closes, escape shuts it and a press on the ground around it shuts it. None of
 * that is in this file, and all of it is the reason the panel may be a plain
 * element rather than a dialog. */
.navmenu-toggle {
  /* At every width. There is no screen wide enough that the audience pages and
     the sign in stop competing with the product under them.

     Its seat is its place in `.header-actions` rather than an `order` on the
     bar: it is the last thing in the group and the group is last on the line,
     so it is the last control in the header at every width. */
  display: inline-grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  min-height: 0;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
}

/* Three lines, drawn rather than fetched: this application ships no icon font
   and no sprite, and a Content-Security-Policy that names its own origin is
   easier to keep than one that does not need to be kept. The middle bar is the
   element and the other two are its own edges. */
.navmenu-bars,
.navmenu-bars::before,
.navmenu-bars::after {
  display: block;
  width: 1.15rem;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
}

.navmenu-bars {
  position: relative;
}

.navmenu-bars::before,
.navmenu-bars::after {
  content: "";
  position: absolute;
  left: 0;
}

.navmenu-bars::before {
  top: -0.36rem;
}

.navmenu-bars::after {
  top: 0.36rem;
}

.header-rest {
  display: contents;
}

/* The entry navigation, as one group ---------------------------------------
 *
 * The pages of the site, the control that explains the product and the sign in.
 * They are one element rather than three flex items because they are now one
 * thing: what the hamburger opens on a screen wide enough to have a bar.
 *
 * Mobile first, so what is written here is what a phone draws, which is a column
 * at the top of the panel `.header-rest.open` already is. The block under
 * `@media (min-width: 40rem)` is what makes the same element a dropdown hung off
 * the end of the bar, because at that width the panel around it is not a panel:
 * `.header-rest` is `display: contents` there, so this group has to become the
 * surface itself. */
.header-entry {
  display: grid;
  gap: var(--space-3);
  justify-items: start;
  min-width: 0;
}

@media (min-width: 40rem) {
  /* Shut, and shut is the default: the bar above this width is the wordmark,
     the sections, one primary button and the control. */
  .header-entry {
    display: none;
  }

  /* And open, which is a surface of its own rather than a row folded out of the
     bar. It hangs off `.app-header-inner`, which is positioned for exactly this,
     so its end lines up with the end of the header's own content rather than
     with the edge of the display.
   *
   * Out of the bar's flow, so the bar stays one line high while it is open, and
   * above the bar at `z-index: 30` for the same reason the phone panel is: both
   * are drawn inside `.app-header`, which carries its own stacking context, so
   * the two of them ride above the ground around them and everything below the
   * bar is behind it.
   *
   * It is narrow on purpose. Seven links in a full width sheet is a menu
   * pretending to be a page; this is the width of the longest thing in it. */
  .header-rest.open .header-entry {
    display: grid;
    position: absolute;
    top: 100%;
    inset-inline-end: var(--gutter);
    z-index: 30;
    width: max-content;
    max-width: min(22rem, calc(100% - var(--gutter) * 2));
    padding: var(--space-4) var(--space-5);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
  }
}

/* The ground around the open panel, and the whole of what it is for: a press
 * outside the menu shuts it.
 *
 * It sits under the header rather than over it, which is what keeps the panel
 * itself reachable while this is up. The panel is drawn inside `.app-header`,
 * and that element carries its own stacking context at `z-index: 20`, so
 * everything in the bar rides above this and everything below the bar is behind
 * it. A press on the record, on a card, or on the page's own background lands
 * here and closes the menu instead of doing what it was over, which is what a
 * menu is supposed to do and is the half of it a keyboard already had.
 *
 * It is drawn at every width, because the menu exists at every width. What is
 * only a phone's is the tint: a full sheet of colour laid over the page behind a
 * dropdown holding seven links would be the application making a modal out of
 * its own navigation, and on a phone the panel *is* most of the screen, so the
 * page behind it should read as set back. The phone block further down is where
 * the tint is, beside the rest of the panel's own rules. */
.navmenu-scrim {
  position: fixed;
  inset: 0;
  z-index: 15;
}

/* The rest of the site, in the header of the application rather than only in
   its footer.
 *
 * The document this application is served in carries these five links in its
 * own header, and boot.js takes that header away the moment the app mounts. A
 * reader who had them a second ago and does not have them now has watched the
 * navigation change under them, which is the seam this whole pass is about. So
 * the running app draws the same five, in the same order, in the same place.
 *
 * A column inside the menu rather than a row in the bar, at every width. They
 * were a row above 62rem and nothing below it, which was two answers to one
 * question and neither of them the right one: in the bar they were the loudest
 * way out of a product the reader had only just landed in, and below that width
 * they were simply gone. Now there is one answer. The same five, in the same
 * order, one press from every screen, and the press is the hamburger.
 *
 * Quiet, because they are still a way out rather than the thing to do here. */
.site-nav {
  display: grid;
  gap: 0.15rem;
  font-size: var(--text-sm);
}

.site-nav a {
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  /* A link in a menu is a target on a touch screen, and this is the one place
     in the application where five of them are stacked a few pixels apart. */
  padding: 0.35rem 0;
}

.site-nav a:hover {
  color: var(--text);
  text-decoration: underline;
}

/* Layout --------------------------------------------------------------- */

.app-main {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--space-4) var(--gutter) var(--space-6);
  display: grid;
  gap: var(--space-4);
  /* A grid item is as wide as its widest child unless it is told otherwise,
     and one long token or one wide table would otherwise widen every card on
     the screen. */
  align-content: start;
}

.app-footer {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter) var(--space-6);
}

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4);
  min-width: 0;
}

/* The workspace's first frame ---------------------------------------------
 *
 * The home screen is what a visitor sees before they have decided anything, and
 * what a thumbnail of this product shows. A column of cards put the record
 * fourth and the first match on another screen entirely, so the first frame was
 * a greeting and an empty "nothing is waiting" box: a product with nothing in
 * it.
 *
 * So the two things that prove the product works stand side by side at the top,
 * and everything that is a count rather than a demonstration goes under them.
 * One breakpoint, and it is the width at which two columns are each still wide
 * enough to hold a record card; under it the same cards stack in the same
 * order, which is why the record is first in the markup. */

/* The greeting is the title of the screen rather than a card on it. A page
   title in a box is a box, and the first frame of this product already has as
   many of those as it can carry. The small inline padding is what lines the
   heading up with the text inside the cards under it rather than with their
   borders. */
.record-head {
  display: grid;
  gap: var(--space-2);
  padding-inline: var(--space-1);
  min-width: 0;
}

.record-head h1,
.record-head p {
  margin: 0;
  max-width: var(--measure);
}

.record-head .banner {
  margin: 0;
}

/* The one line that says what this workspace is.
 *
 * It is body ink rather than the muted grey the sentence under it uses, and that
 * is the whole of its treatment: no rule, no tint, no box. This is a claim about
 * what the reader is looking at, so it reads as the screen speaking rather than
 * as a note attached to the screen, and a framer that decorated itself would be
 * the first thing on the page a reader learned to skip. `app::HomeView` argues
 * what it says and why everybody gets it. */
.record-frame {
  color: var(--text);
  font-size: var(--text-sm);
}

/* The quiet contextual card ------------------------------------------------
 *
 * One line of explanation beside the thing it explains, a plain text control
 * that opens one more sentence, and a plain text control that puts it away for
 * good. `web::reveal` argues the copy and `views::reveal` draws it.
 *
 * # Why it looks like almost nothing
 *
 * Because everything it could be instead has a cost this card cannot pay. A box
 * makes it a second card competing with the real one beside it. A tint makes it
 * a status. A pill, a badge or a coloured chip makes it an advertisement, and a
 * reader who has decided that a shape is decoration stops reading the sentences
 * inside that shape everywhere in the product.
 *
 * So it is a hairline down one side and the muted ink this file already uses for
 * a note. The hairline is doing one job, which is to say that these two or three
 * lines are an aside rather than part of the content they sit next to, and it is
 * `--line-strong` rather than the accent because this is not news.
 *
 * # The expansion
 *
 * `.reveal-more` is always in the document and is empty until the control is
 * pressed, which is what lets the control's `aria-controls` name an element that
 * actually exists; the module note in `views::reveal` argues that. `:empty` is
 * what keeps a closed card exactly the height it was before there was anything
 * to open. */
.reveal {
  display: grid;
  gap: var(--space-2);
  justify-items: start;
  max-width: var(--measure);
  padding: var(--space-2) 0 var(--space-2) var(--space-3);
  border-inline-start: 2px solid var(--line-strong);
  color: var(--muted);
  font-size: var(--text-sm);
}

.reveal p {
  margin: 0;
  max-width: var(--measure);
}

.reveal-more:empty {
  display: none;
}

/* The two controls on one line, which is where two short pieces of text belong.
   They wrap rather than shrink on a narrow screen, and the row is what keeps the
   card two or three lines tall instead of four. */
.reveal-controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
}

/* Both are `.link`, and the one that puts the card away is quieter still: it is
   the way out of being told something rather than the thing being offered, so it
   should be findable and never the first thing the eye lands on.
 *
 * Two classes on the selector so that it beats `button.link` on the colour,
 * which is the same arrangement `.link.quiet` and `button.welcome-close` make
 * further down this file and for the same reason. */
.reveal-controls button {
  font-size: var(--text-sm);
}

.link.reveal-hide {
  color: var(--muted);
}

.link.reveal-hide:hover:not(:disabled) {
  color: var(--text);
}

.home-grid {
  display: grid;
  gap: var(--space-4);
  align-items: start;
}

.home-col {
  display: grid;
  gap: var(--space-4);
  align-content: start;
  min-width: 0;
}

@media (min-width: 58rem) {
  .home-grid {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  }
}

.card.auth {
  max-width: 26rem;
  margin: 2rem auto;
}

/* The three doors under the sign-in form: create an account, hire instead, or
   the administrator's entrance. They are buttons and buttons are inline, so
   without this they render with nothing between them and read as one run-on
   sentence. `column-gap` separates them on a wide screen and `row-gap` keeps
   them apart once they wrap, which on a 375px phone is immediately: they stack
   as three tappable lines rather than being squeezed onto one. */
.auth-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  align-items: baseline;
  margin-top: 0.5rem;
}

/* Where the identity form's encrypted fields would be, in a tab that holds the
   session but not the keys. Set apart from the inputs around it because it is
   not one of them: it is the reason three of them are missing, and the way to
   get them back. */
.locked-fields {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.75rem;
  margin: 0 0 0.75rem;
}

.locked-fields h3 {
  margin-bottom: 0.35rem;
}

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

/* Wide enough that a field is still a field. The basis grew with the column:
   at twelve rems a form on a laptop laid five boxes across a line, which is a
   spreadsheet rather than something a person fills in. */
.row > * {
  flex: 1 1 14rem;
}

/* Forms ---------------------------------------------------------------- */

/* Every field is labelled, and the label is the element rather than a
   placeholder: a placeholder disappears the moment somebody types into it, and
   a screen reader announcing an input as "edit text, blank" is a screen nobody
   can fill in. Every control in this application is inside its own `label`, so
   this rule is also the accessible name. */
label {
  display: block;
  margin-bottom: 0.75rem;
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: 540;
}

label.check {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
  color: var(--text);
  font-size: var(--text-base);
  font-weight: 400;
}

label.check input {
  width: auto;
  margin-top: 0.3rem;
}

input,
select,
textarea {
  display: block;
  width: 100%;
  margin-top: 0.25rem;
  padding: 0.55rem 0.7rem;
  min-height: var(--control-h);
  background: var(--field);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: 1rem;
  font-weight: 400;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

input:hover,
select:hover,
textarea:hover {
  border-color: var(--accent-line);
}

/* `:focus` rather than `:focus-visible` on the fields alone, and deliberately:
   a text box that has been clicked into is a text box somebody is typing in,
   and the ring is the only thing saying which one of nine it is. */
input:focus,
select:focus,
textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

textarea {
  resize: vertical;
}

fieldset {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  margin: 0 0 0.75rem;
  padding: 0.75rem;
  min-width: 0;
}

legend {
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: 540;
  padding: 0 0.35rem;
}

button {
  font: inherit;
  padding: 0.5rem 1rem;
  min-height: var(--control-h);
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}

button:hover:not(:disabled) {
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

button:disabled {
  opacity: 0.55;
  cursor: default;
}

button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 620;
}

button.primary:hover:not(:disabled) {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
  color: var(--accent-ink);
}

/* A control that is a word rather than a box.
 *
 * `a.link` is here beside the button and is not a widening of the rule: three
 * places in this application already write `class="link"` on an anchor, and two
 * of them put it in the same `.form-actions` row as a button wearing the same
 * class. Nothing matched them, so they drew as the browser's own link, which is
 * an underlined blue that belongs to no theme this file defines and that sits on
 * a near black page in the dark one. They are the same control and they say so
 * in the markup; this is the rule catching up with what the markup asks for.
 *
 * `inline-block` for the one difference between the two elements: a button is
 * one already, an anchor is not, and `min-height` does nothing to an inline box.
 * That is what lets the phone give both of them a target a thumb can find. */
button.link,
a.link {
  display: inline-block;
  background: none;
  border: none;
  color: var(--accent);
  padding: 0.35rem 0;
  min-height: 0;
  text-decoration: underline;
  text-underline-offset: 0.18em;
  flex: 0 0 auto;
}

button.link:hover:not(:disabled),
a.link:hover {
  background: none;
  color: var(--accent-strong);
}

button.link.danger,
a.link.danger {
  color: var(--danger);
  /* Two classes, so this beats `button.danger` below: without it a link that
     is also danger took that rule's fill and drew its text in its own colour. */
  background: none;
  border-color: transparent;
}

/* The second click of a destructive pair — deleting a stored key — so that
   confirming looks nothing like the link that asked. */
button.danger {
  background: var(--danger);
  border-color: var(--danger);
  color: #1a0e0e;
  font-weight: 600;
}

/* Lists ---------------------------------------------------------------- */

.entries {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.entries li {
  display: flex;
  gap: 0.75rem;
  justify-content: space-between;
  align-items: flex-start;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem;
  min-width: 0;
}

/* On a phone a row with a control at each end is a title squeezed into forty
   pixels beside two buttons. It becomes two lines instead, which is the same
   row read top to bottom rather than a desktop layout with the middle removed. */
@media (max-width: 34rem) {
  .entries li {
    flex-direction: column;
    align-items: stretch;
  }

  .entries li .entry-actions {
    justify-content: flex-start;
  }

  /* `.entry-head` is the same row one level in, drawn by every entry that
     carries a panel under it: a title on the left and a badge or a pair of
     controls on the right. The rule above does not reach it, because that entry
     is `display: block` and this is a flex row inside it, so on a phone the
     applications list, the answer bank, the trail and the dossier's parts each
     kept squeezing a heading against its own controls. */
  .entry-head {
    flex-direction: column;
    align-items: flex-start;
  }

  /* A state is not a row of its own. Stretching every child to the width of the
     column is right for a body and for a line of buttons, and wrong for a pill:
     a badge run across a phone reads as a banner announcing something, which is
     the opposite of the restraint `.badge` is held to. The left margin goes with
     it, because it was the gutter between a badge and the text it trailed and
     there is no text beside it now. */
  .entries li > .badge,
  .entry-head > .badge {
    align-self: flex-start;
    margin-left: 0;
  }
}

/* The custody trail for one grant, nested inside that grant's row. The rule
 * above matches every `li` below `.entries`, including these, so this undoes
 * the card treatment: a trail entry is a line of history, not another card. */
.trail {
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.15rem;
}

.entries .trail li {
  display: block;
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
}

.chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1rem;
  padding: 0;
}

.chip {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 0.2rem 0.5rem 0.2rem 0.75rem;
  font-size: var(--text-sm);
}

/* A state, not a label on a heading.
 *
 * It is worth saying what this is not, because the shape invites it: a small
 * outlined capsule beside a title is the house style of generated interfaces,
 * where it carries a category nobody asked for. Every one of these says what
 * something *is right now* and comes from a value core-api sent: a grant is
 * active or revoked, a posting is open or closed, a check came back verified or
 * did not. Nothing here decorates a heading, and nothing here should. */
.badge {
  margin-left: 0.5rem;
  padding: 0.05rem 0.5rem;
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  letter-spacing: 0.01em;
  font-weight: 560;
  border: 1px solid var(--line-strong);
  color: var(--muted);
  white-space: nowrap;
}

/* `good` is the third of these and is the employer side's: a key unlocked in
 * this tab, and a sealed bundle that opened with it. Both are states of the
 * browser rather than of a record, and both are the state a recruiter is trying
 * to reach, which is the one case on this side where green really does mean
 * good. The badge is never the whole message: the words beside it say the same
 * thing.
 *
 * The green is --ok rather than the accent, which is the whole of the
 * difference between a verdict and a control. A verified badge in the colour of
 * the primary button beside it was one more green thing on a screen of green
 * things, and it read as something to press. */
.badge.active,
.badge.verified,
.badge.good {
  color: var(--ok);
  border-color: var(--ok);
}

.badge.revoked,
.badge.expired,
.badge.failed {
  color: var(--danger);
  border-color: var(--danger);
}

/* `pending` takes the neutral badge above on purpose: a check nobody has
 * reported on should not read as either good or bad news. */

/* The watchdog's verdicts, on the admin metrics screen. These two are the one
 * place on the whole console where green really does mean good and red really
 * does mean bad — everywhere else a `revoked` grant or a `closed` posting is the
 * product working, which is why colour is spent so sparingly above. A machine
 * check has no such nuance: the disk is under the limit or it is not. */
.badge.ok {
  color: var(--ok);
  border-color: var(--ok);
}

.badge.alert {
  color: var(--danger);
  border-color: var(--danger);
}

/* Notices -------------------------------------------------------------- */

.banner {
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem;
  margin: 0 0 0.75rem;
}

/* A sentence, laid out as a sentence. This was a flex row for two spans, the
   first of which was the machine-readable error code in monospace; on anything
   narrower than the banner that row wrapped and the code became a heading over
   the message. The code now rides on `data-error-code` and nothing renders it,
   so what is left here is prose and needs no layout of its own. See
   `views::ErrorBanner`. */
.banner.error {
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  color: var(--danger);
}

/* A key this browser has just minted, which is news rather than an achievement:
   the reader has done nothing yet, and the next thing they do is the thing that
   matters. Informational blue, for the reason --info is here at all. */
.banner.minted {
  background: var(--info-soft);
  border: 1px solid var(--info-line);
}

/* Trust on first use, said out loud. Amber rather than red because nothing has
   failed: the disclosure still passes every check, and what the reader is being
   told is that one of those checks is answered by the party it is about. An
   error colour here would train people to dismiss it. */
.banner.caution {
  background: var(--warn-soft);
  border: 1px solid var(--warn-line);
}

.banner.caution strong {
  display: block;
  margin-bottom: 0.35rem;
  color: var(--warn);
}

.banner.caution p {
  margin: 0;
  color: var(--muted);
}

/* The standing marker over the sample account, drawn above every tab a visitor
   who has not signed in is reading.

   Quiet on purpose, and the quietness is the argument rather than a taste: the
   demonstration is the pitch, so anything loud enough to compete with it is
   working against the thing it is marking. A surface tint and a rule down the
   leading edge are enough to say "this frame is not your record" on every
   screen, and the first three words say the rest. Not the accent colour, which
   this app spends on things a person can act on, and not the caution amber,
   which would report a fault where there is none.

   The same frame carries the other thing the sample says: the answer any
   control that would write gets, which is that nothing here is saved. That is
   a fact about where the reader is rather than a failure, so it is deliberately
   not `.banner.error`, and wearing the marking's own colours is what says the
   two sentences come from the same place. */
.banner.example {
  background: var(--surface);
  border: 1px solid var(--line);
  border-inline-start: 3px solid var(--accent);
  color: var(--muted);
  font-size: var(--text-sm);
}

.banner.example strong {
  color: var(--text);
  font-size: var(--text-base);
}

/* The marking is one line on a wide screen, because the first frame of this
   product is what a visitor judges it by and a four line notice above the
   workspace is four lines of the workspace they do not see. The sentences are
   unchanged; what changes is that they run on rather than stacking. */
@media (min-width: 48rem) {
  .banner.example {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.15rem 0.5rem;
  }

  .banner.example p {
    margin: 0;
  }
}

/* The voluntary EEO block. Not an error and not good news, so neither of the
   banners above fits: it is a passage of text the reader has to actually read
   before answering, and it is set apart to say so. */
.banner.voluntary {
  background: var(--surface-2);
  border: 1px solid var(--line);
}

.banner.voluntary strong {
  display: block;
  margin-bottom: 0.35rem;
}

.banner.voluntary p {
  margin: 0 0 0.4rem;
  color: var(--muted);
}

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

.token {
  display: block;
  margin: 0.5rem 0;
  padding: 0.6rem;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--text-xs);
  /* A grant token, a VERA id and a recovery key are all one unbroken run of
     characters, so the only place to break one is between two of them.
   *
   * `white-space` is here because of the one of those that is drawn in a `pre`:
   * the recovery key, on the screen an account is created. `pre` carries
   * `white-space: pre`, which switches soft wrapping off altogether, and a rule
   * that offers a break at every character offers nothing when nothing may
   * break. Forty three characters of monospace ran off the side of a 375 pixel
   * screen on the one screen in this application a person must not leave
   * without reading what is on it. `pre-wrap` keeps the newlines the element was
   * chosen for and lets `break-all` do the job it was already asking for. */
  white-space: pre-wrap;
  word-break: break-all;
  /* One click selects the whole token, for browsers that refuse the
     clipboard API. */
  user-select: all;
}

/* Account and agent ----------------------------------------------------- */

/* The account facts: label above value on a phone, two columns once there is
   room for them. */
.facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.25rem 0.75rem;
  margin: 0 0 0.5rem;
}

.facts dt {
  color: var(--muted);
  font-size: var(--text-sm);
}

.facts dd {
  margin: 0;
}

/* One line saying what is held: the provider, the hint, and nothing else. */
.keyline {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.75rem;
}

/* The four characters core-api will admit to. Monospace so they are read as
   the tail of a key rather than as a word. */
.hint {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  padding: 0.2rem 0.5rem;
  letter-spacing: 0.05em;
}

/* The ten recovery codes, shown once when a second factor is enrolled.

   A grid rather than a list, because they are shown to be copied onto paper and
   ten items in a single column is a scroll on a phone at the one moment nothing
   should be below the fold. `list-style: none` for the same reason the markup is
   a `ul`: they are a set and not a sequence, and numbering them would invite
   somebody to think the order meant something. */
.codes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.4rem;
  margin: 0 0 0.75rem;
  padding: 0;
  list-style: none;
}

.codes code {
  display: block;
  text-align: center;
  /* One click selects a whole code, as `.token` does and for the same reason. */
  user-select: all;
}

/* The drafted application. `pre` because the model's paragraph breaks are
   content, wrapped because it is prose and not code. */
.draft {
  margin: 0 0 0.75rem;
  padding: 0.75rem;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: inherit;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A legal document shown in full, on the screen that asks somebody to accept
   it. `pre` for the same reason `.draft` is one — the line breaks and the
   numbered clauses are content — and wrapped because it is prose.

   Scrolled rather than truncated, and that is the only decision here worth
   writing down. A contract behind a "read more" is a contract most people do
   not read, so the whole of it is in the box from the first frame and the box
   is tall enough to read in. What it must never become is a `details` that
   starts closed, or a rendering of a summary of it. */
.legal-document {
  margin: 0 0 0.75rem;
  padding: 0.75rem;
  max-height: 22rem;
  overflow-y: auto;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: var(--text-xs);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A block of machine text somebody copies: a key file, an attestation document,
   a header line. The same treatment as `.request` and `.token`, because they are
   the same thing at three sizes. */
.code.block,
pre.code {
  margin: 0.75rem 0;
  padding: 0.75rem;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--text-xs);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: all;
}

.id {
  display: block;
  margin-top: 0.25rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.muted {
  color: var(--muted);
}

.small {
  font-size: var(--text-sm);
}

.settings summary {
  color: var(--muted);
  font-size: var(--text-sm);
  cursor: pointer;
}

.settings .row {
  margin-top: 0.5rem;
}

.settings .row button {
  flex: 0 0 auto;
}

/* The record's sections --------------------------------------------------
 *
 * Profile is one tab with seven sections inside it. The sub-navigation is
 * deliberately quieter than the header tabs — smaller, squarer, no sticky
 * behaviour — so that "which screen am I on" and "which part of my record am I
 * editing" never look like the same question. */

.subtabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.subtab {
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  padding: 0.35rem 0.7rem;
  border-radius: var(--radius-xs);
  font-size: var(--text-sm);
  min-height: 2.25rem;
}

.subtab:hover {
  color: var(--text);
}

.subtab.active {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--text);
}

/* An entry is a row — body on the left, buttons on the right — until it is
   being edited or carries a panel of its own, and then it is a block. */
.entries li.editing,
.entries li.stacked {
  display: block;
}

.entry-body {
  min-width: 0;
}

.entry-head {
  display: flex;
  gap: 0.75rem;
  justify-content: space-between;
  align-items: flex-start;
}

.entry-actions {
  display: flex;
  gap: 0.5rem;
  flex-shrink: 0;
}

.entry-form {
  width: 100%;
}

.form-actions {
  margin-top: 0.5rem;
  align-items: center;
}

.form-actions button {
  flex: 0 0 auto;
}

/* The name/value pairs under an entry. The wrapper exists only to give the
   loop something to key on; `display: contents` keeps the two-column grid. */
.facts > div {
  display: contents;
}

/* The CDL panel hangs off its credential, and the dashed rule says so. */
.cdl {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px dashed var(--line);
}

/* One day of the weekly grid. The day name is fixed-width so the seven rows
   line up as a table would, without being one. */
.row.shift {
  align-items: center;
}

.row.shift .day {
  flex: 0 0 5.5rem;
  font-weight: 600;
}

/* A URL someone typed: it has to wrap, or it decides the width of the card. */
.break {
  overflow-wrap: anywhere;
}

/* Meters --------------------------------------------------------------- */

/* One bar, used by three screens now: the profile completeness meter, a match's
 * score, and the password strength meter under every field where a password is
 * chosen. They are the same shape — a proportion of a whole — so they are the
 * same component, and the only difference is what fills it. */
.meter {
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  border: 1px solid var(--line);
  overflow: hidden;
  margin: 0.4rem 0;
}

.meter-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  /* Animated only for people who have not asked for less motion; see the
   * reduced-motion block at the foot of this file. */
  transition: width 240ms ease-out;
}

/* The password meter's three tones. Colour is the second signal and never the
 * only one: the label beside the bar says "Very weak" or "Good" in words, and
 * the bar itself carries an aria-label, so a red bar is emphasis rather than
 * the message. Amber is the same amber `.stat.warn` uses, for the same reason —
 * something to look at rather than something that has gone wrong. */
.meter-fill.weak {
  background: var(--danger);
}

.meter-fill.fair {
  background: var(--warn);
}

.meter-fill.strong {
  background: var(--ok);
}

.meter-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

.meter-head h2 {
  margin-bottom: 0;
}

.meter-count {
  color: var(--muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* What is left to fill in. Each line is a link to the section that fills it,
   so the meter is a way in rather than a scolding. */
.todo {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
}

.todo button.link {
  padding: 0;
  font-weight: 600;
}

/* Matches and suggestions ---------------------------------------------- */

.match-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
}

.match-head h3 {
  margin-bottom: 0.15rem;
}

/* The score, large enough to scan a list by. Tabular figures so a column of
   them lines up at the decimal even though each is its own card.
 *
 * Informational blue rather than the accent, and the argument is the same one
 * `.topmatch` makes below: a score is the market's opinion of a record, not a
 * verdict on it and not a thing to press. In the accent it was the largest
 * green object on the screen, which made the number read as the control. */
.score {
  font-size: 1.75rem;
  font-weight: 700;
  color: var(--info);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1;
}

/* The one match on the home screen, which is the other half of the first frame.
 *
 * It is a card like the rest and is drawn in a tint of its own, because it is
 * the only thing on that screen that came from outside: everything else is the
 * person's own record, and this is the market answering it. The score is the
 * thing being scanned, so it sits at the top corner where a number belongs and
 * not in the flow of a sentence.
 *
 * The tint is the informational blue and used to be the accent. That mattered
 * more here than anywhere else in this file: the home screen draws the record
 * and this card side by side, and in one green they were two panels of the same
 * substance rather than the person's own record and somebody else's answer to
 * it. */
.topmatch {
  border-color: var(--info-line);
  background: linear-gradient(var(--info-soft), var(--info-soft)), var(--surface);
}

.topmatch .score {
  font-size: 2.1rem;
}

/* The factors behind a score. Not `.entries`, because these are lines of an
   explanation rather than records with actions on them. */
.factors {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.factors li {
  border-left: 2px solid var(--line);
  padding-left: 0.6rem;
}

.factor-head {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  align-items: baseline;
}

/* One unanswered screening question, in the agent's gap form. */
.gap {
  padding: 0.6rem 0;
  border-top: 1px dashed var(--line);
}

.gap:first-of-type {
  border-top: 0;
}

/* Notifications --------------------------------------------------------- */

/* An unread notice is marked twice: a left rule for scanning the list, and a
   dot beside the title for anyone reading one row at a time. Colour alone
   would leave both readings to hue.
 *
 * Informational blue, because that is what an unread notice is. Nothing has
 * succeeded and nothing has gone wrong; the service has said something and
 * nobody has read it yet. The unread pip on the tab wears the accent still, and
 * deliberately: that one is a count on a control. */
.entries li.unread {
  border-left: 3px solid var(--info);
}

.dot {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-left: 0.4rem;
  border-radius: 50%;
  background: var(--info);
  vertical-align: middle;
}

/* The end of the account screen ----------------------------------------- */

/* The one card that destroys something. Bordered in the danger colour so it
   cannot be mistaken for the settings above it while scrolling past. */
.danger-zone {
  border-color: var(--danger);
}

.danger-zone h2 {
  color: var(--danger);
}

/* A file input styled like the other fields, rather than left as whatever the
   browser draws by default — which is the one control that otherwise looks
   like it belongs to a different application. */
input[type="file"] {
  width: 100%;
  padding: 0.5rem;
  background: var(--bg-soft);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--muted);
}

input[type="file"]::file-selector-button {
  margin-right: 0.75rem;
  padding: 0.35rem 0.75rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
}

/* The employer's half ----------------------------------------------------
 *
 * One build serves a seeker and a recruiter, so the first thing the header has
 * to answer is which of the two you are being right now. The workspace switch
 * is therefore louder than the tabs under it — a segmented control rather than
 * another row of pills — because moving between "my record" and "my company" is
 * a bigger move than changing tab, and the two should never look alike. */

.workspaces {
  display: flex;
  gap: 0.15rem;
  padding: 0.15rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
}

.workspace {
  background: transparent;
  border: none;
  color: var(--muted);
  padding: 0.35rem 0.8rem;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  font-size: var(--text-sm);
  font-weight: 560;
  min-height: 2.25rem;
}

.workspace:hover:not(.active) {
  background: transparent;
  color: var(--text);
}

.workspace.active {
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 620;
}

.workspace.active:hover {
  background: var(--accent-strong);
  color: var(--accent-ink);
}

/* Who every call below is being made as. Always drawn, even for somebody with
   one organization: "which company am I acting for" should not be a question a
   screen answers only when it is ambiguous. */
.orgbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0.9rem;
  /* It carries three things now rather than two, and the third is a word and a
     dot. On a phone with a long organization name that is more than one line
     holds, so the standing group drops under the name instead of squeezing it
     to forty pixels. `space-between` over one item on the second line is
     `flex-start`, which is where it belongs. */
  flex-wrap: wrap;
  row-gap: 0.5rem;
}

.orgbar-who {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.orgbar-who select {
  width: auto;
  min-width: 0;
}

/* The right-hand end of that bar: what this tab can do, and as whom. The lock
   sits before the role badge because it is the one of the two that changes
   during a session. */
.orgbar-standing {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
}

/* `.badge` carries a left margin for the headings it usually trails. Here the
   gap above is doing that job, and the two together read as a gutter. */
.orgbar-standing .badge {
  margin-left: 0;
}

/* Whether this browser can open a candidate, said on every employer screen.
 *
 * A popover rather than a band across the card, because the answer is one word
 * nine times out of ten and the argument behind it is three paragraphs the
 * tenth. Anchored to the button and right aligned, so it opens inside the
 * column on a phone as well as on a desk; the width is clamped against the
 * viewport rather than set in rem alone, because this card is as wide as the
 * workspace and the viewport is what actually runs out. */
.keylock {
  position: relative;
  display: flex;
}

.keylock-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: 560;
  padding: 0.25rem 0.7rem;
  /* Shorter than a form control and still a target a thumb can find. The badge
     beside it is 1.3rem tall and is not a control, which is the difference
     these two rems are buying. */
  min-height: 1.9rem;
  white-space: nowrap;
  cursor: pointer;
}

.keylock-toggle:hover {
  border-color: var(--text);
  color: var(--text);
}

/* The unlocked state is the good-news green and the locked state is the neutral
   border, which is the same restraint `.badge` is held to: a locked key is the
   state every tab starts in and is not bad news. The word beside the dot says
   which either way, so colour is never carrying the message on its own. */
.keylock-toggle.unlocked {
  border-color: var(--ok);
  color: var(--ok);
}

.keylock-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  border: 1px solid currentColor;
  flex: 0 0 auto;
}

.keylock-toggle.unlocked .keylock-dot {
  background: currentColor;
}

.keylock-panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  z-index: 5;
  width: min(26rem, calc(100vw - 2.5rem));
  max-height: min(30rem, 70vh);
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--space-4);
  text-align: left;
  /* Prose, so it reads as sentences rather than as a label. The bar itself is
     a single line and sets no line height worth inheriting. */
  line-height: var(--leading);
}

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

/* The three claims about the private key, wherever they are drawn. They are
   sentences rather than a row of facts, so the flex layout `.entries li`
   carries is undone: a claim with a control at the other end of the line is
   exactly what these must not look like. */
.key-statement li {
  display: block;
}

/* The states worth colouring, and only those. A role, a posting's status and a
   pipeline stage all render through `.badge`, so the good news and the bad news
   are named here once rather than per screen — and everything unnamed keeps the
   neutral badge, which is the right answer for `draft`, `applied` or
   `screening`: they are where things are, not verdicts. */
.badge.owner,
.badge.open,
.badge.hired {
  color: var(--ok);
  border-color: var(--ok);
}

.badge.rejected,
.badge.withdrawn {
  color: var(--danger);
  border-color: var(--danger);
}

/* A candidate's record, read through their grant. Inset and quiet: it is
   somebody else's document sitting inside this company's screen, and it should
   read as a thing that was handed over rather than as part of the furniture. */
.dossier {
  margin-top: 0.75rem;
}

.dossier-part {
  background: var(--surface-2);
  margin-bottom: 0.5rem;
}

.dossier-part h4 {
  margin: 0;
}

/* The sealed panel is one card with several steps inside it, and its own title
   is the h4 the rule above pins to the top. These are the steps: the two doors
   onto a disclosure and the ask for the full set, which need air above them and
   would otherwise run into the paragraph before. */
.dossier-part h5 {
  font-size: var(--size-h4);
  line-height: var(--leading-tight);
  margin: 1rem 0 0.5rem;
}

/* A dossier is two documents now, and the headings say which is which: what
   this service still holds in the clear, and what the institution's own key
   opened in this browser. A recruiter reading a phone number is entitled to
   know which of the two it came out of, and a heading is the cheapest honest
   way to say it. */
.dossier h3 {
  font-size: var(--size-h4);
  line-height: var(--leading-tight);
  margin: 1.25rem 0 0.25rem;
}

.dossier h3:first-child {
  margin-top: 0;
}

/* The one line on the employer's side that names a person, and it exists only
   because a key opened it. A paragraph with weight rather than a heading: in
   this card a heading would claim to be one of the steps the sealed panel
   numbers below. */
.disclosed-name {
  font-size: var(--size-h4);
  font-weight: 600;
  margin: 0 0 0.25rem;
}

h4 {
  font-size: var(--size-h4);
  margin: 0 0 0.5rem;
}

@media (min-width: 40rem) {
  .app-main {
    padding-top: var(--space-5);
  }

  /* With room for them, the tabs come back onto the header's own line and
     take whatever is left between the brand and the account menu. They still
     scroll rather than wrap: a header that grows a second row when the window
     narrows by ten pixels moves the whole page under the reader. */
  .tabstrip {
    order: 0;
    flex: 1 1 auto;
    min-width: 0;
  }
}

/* The phone -----------------------------------------------------------------
 *
 * One block, at the width the rest of this file already treats as a phone,
 * rather than a value adjusted in thirty places. The argument is the one the
 * scales at the top of the file make: a rule that applies only under 40rem
 * should be findable by looking in the one place such rules are kept.
 *
 * Nothing here is a second layout. Every rule below is the same component given
 * the room a thumb and a 375 pixel viewport actually have, which is why this
 * application has no markup that exists for one width and not the other. It
 * does four things.
 *
 * # One. Nothing a finger has to hit is under 44 pixels
 *
 * 44 is the smallest target that is reliably hit without looking, and every
 * control in this application is pressed by somebody walking. The fields and the
 * buttons take it in one rule; the controls that set their own height are then
 * listed, and listed rather than swept because each of them had a reason to be
 * shorter than a form control on a desk. A tab strip that has to fit inside a
 * header, a link that sits in a sentence, a numbered pip in a rail of nine. None
 * of those reasons survives a thumb.
 *
 * # Two. Nothing a reader has to read is under 13 pixels
 *
 * `--text-xs` was 12.48 and the unread badge on the Notifications tab was 11.2.
 * Both are legible on a laptop at arm's length and neither is legible on a phone
 * in daylight. Raising the token raises every badge, stamp, code block and label
 * that was already spending it, which is the point: the sizes in this file are a
 * scale, so a floor belongs on the scale rather than on the handful of elements
 * somebody remembered to check.
 *
 * # Three. Nothing runs off the side of the page
 *
 * A card is `min-width: 0` so that one long token cannot widen the whole column.
 * That leaves the token hanging out of the card instead, and a page that scrolls
 * sideways. Every element known to carry one already breaks; the rule on
 * `.app-main` is the floor under the ones nobody predicted, and it is
 * `break-word` rather than `anywhere` so that it changes nothing at all until
 * something would otherwise overflow.
 *
 * # Four. A row that was two columns on a desk is one column here
 *
 * The facts list, the console's filters, the pagination line and the two
 * segmented controls on the front door. Each was a row of things sharing a width
 * that does not exist on a phone, and each becomes the same content read top to
 * bottom. */
@media (max-width: 40rem) {
  :root {
    /* 13 pixels, the floor, written against the 16 pixel root this application
       never changes. */
    --text-xs: 0.8125rem;
  }

  /* 44 pixels, the target, and it is a list of elements rather than a change to
     `--control-h` for one reason: the base rule that spends that token is
     `input, select, textarea`, and `input` is also every checkbox and every
     radio in this application. Those are drawn by the browser at the size the
     browser draws them, they are already hit through the whole of the label they
     sit inside, and moving the token would have quietly resized twelve of them
     to chase a target they were not missing. So the token keeps its meaning and
     the phone names the controls it means. */
  button,
  select,
  textarea,
  input:not([type="checkbox"]):not([type="radio"]) {
    min-height: 2.75rem;
  }

  /* The floor under everything a long value can be dropped into. Inherited, so
     one declaration covers every view rather than the ones that were audited. */
  .app-main,
  .app-footer,
  .site-links {
    overflow-wrap: break-word;
  }

  /* One of the two things in this file that set a size of their own below the
     scale rather than spending a token. This one is 11.5 pixels, and a record id
     or a grant id is what somebody reads out over the phone to support, which
     makes them the strings on these screens least able to afford being small.
     `.pip` is the other and is further down, with the rest of the tab strip. */
  .id {
    font-size: var(--text-xs);
  }

  /* The header, tightened by about the width of a space in each gap. At 375
     pixels the wordmark, "Create your own" and "Sign in" come to within a few
     pixels of the line, so which side of it they fall on is decided by the
     system font the phone happens to have. This buys the margin that makes it
     the same answer on all of them, and it costs nothing elsewhere because
     these are the only two gaps the header bar sets. Wrapping is still the
     honest outcome for a longer label, and `.account` wrapping inside itself is
     what keeps that a second line rather than an overflow. */
  .app-header-inner {
    gap: var(--space-3);
  }

  /* And the one button in that bar, tightened by a quarter of a rem at each
     end. The bar at 375 pixels is the wordmark, this and the control, which is
     one thing more than it carried while the two doors were both inside the
     menu; the padding a button spends on a desk is the easiest thing on that
     line to give back. It stays well clear of the 44 pixel target, because what
     changes is the inline padding and the height is set by `min-height` above. */
  .header-create {
    padding-inline: 0.75rem;
  }

  .account {
    flex-wrap: wrap;
    gap: 0.5rem;
  }

  /* The header, folded all the way. See `.navmenu-toggle` for the whole of the
     argument; what is here is the half of it that only a phone applies.

     The bar keeps the wordmark, one primary button and the control, and gives up
     everything else, which is what `display: none` on the wrapper does: the
     sections, the workspace switch, the account's controls and the entry
     navigation are all inside it, so there is nothing left in the bar to
     squeeze. Opening it swaps that for a panel hung under the header, and
     because the wrapper is the same element the panel is the same markup rather
     than a second copy of it.

     The entry navigation inside it needs no rule of its own here: `.header-entry`
     is written mobile first and is already the column this panel wants. What it
     is not at this width is a surface, because the panel around it is one. */
  .header-rest {
    display: none;
  }

  /* And the tint on the ground around the panel, which is only a tint at this
     width: here the panel is most of the screen, so the page behind it should
     read as set back. It is the same value the welcome panel's scrim uses, so
     the two things in this application that cover the page set it back by the
     same amount. */
  .navmenu-scrim {
    background: var(--scrim);
  }

  .header-rest.open {
    display: grid;
    gap: var(--space-4);
    /* Hung off `.app-header`, which is `position: sticky` and is therefore what
       an absolute inset resolves against. It is out of the bar's flow, so the
       bar stays one line high while the panel is open. */
    position: absolute;
    top: 100%;
    inset-inline: 0;
    z-index: 30;
    padding: var(--space-4) var(--gutter) var(--space-5);
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow);
    /* Eleven sections, a workspace switch and three actions do not fit a short
       phone in landscape, so the panel scrolls rather than running off the
       bottom of the screen with no way to reach the last of them. `contain`
       keeps that scroll off the page behind it. */
    max-height: calc(100vh - var(--header-h));
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* The sections, read down rather than swiped along. The strip is the same
     element it is on a desktop and keeps its own rule; what changes is the
     direction, which is the whole of what made it a strip. The two fades go
     with the scrolling they were describing. */
  .header-rest.open .tabstrip {
    order: 0;
  }

  .header-rest.open .tabstrip::before,
  .header-rest.open .tabstrip::after {
    display: none;
  }

  .header-rest.open .tabs {
    flex-direction: column;
    gap: 0.15rem;
    padding-bottom: 0;
  }

  /* `space-between` is for the unread pip, which rides on the Notifications
     tab: in a column it belongs at the end of the line rather than against the
     word it follows. */
  .header-rest.open .tab {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding-inline: var(--space-3);
    border-radius: var(--radius-sm);
  }

  /* The workspace switch keeps its shape and takes the width, so the three
     seats are three equal segments rather than three words at one end of a
     panel. */
  .header-rest.open .workspace {
    flex: 1 1 auto;
  }

  /* And the actions, one per line and full width, because a panel is a list of
     things to press and a row of them on a phone is three half targets. */
  .header-rest.open .account {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }

  .header-rest.open .account button {
    width: 100%;
  }

  .header-rest.open .account button.link,
  .header-rest.open .who {
    text-align: left;
    max-width: none;
  }

  /* Targets. `.link` is the one that mattered most: "Sign in", "Get something
     checked", "Open Grants" and every "Refresh" in this application are that
     class, and every one of them was 36 pixels of underlined text. The padding
     is vertical only, so a link still starts where its sentence starts and the
     row it sits in does not grow sideways. */
  button.link,
  a.link {
    padding: 0.65rem 0;
    min-height: 2.75rem;
  }

  .subtab,
  .workspace,
  .keylock-toggle {
    min-height: 2.75rem;
  }

  /* `.picker-option` belongs in that list and is deliberately not in it. It was,
     and the rule did nothing: this block is above the front door's own section
     in this file, `.picker-option` sets its own `min-height` down there, and two
     rules of equal specificity are settled by which comes last. The selector
     read as a target being enforced while a phone drew 36 pixel positions. It is
     enforced now, in the block beside the rule it has to beat. See the phone
     rules under "The audience selector". */

  /* The guide's numbered steps are answered beside their own rule, further down
     this file, for the reason the two blocks below give: the rule that sets
     their size is under this block, so a copy of it written here would be
     settled by that one and a phone would keep drawing the desktop size. It did,
     for as long as the rule was here. */

  /* The welcome panel's phone rules are beside its own section further down,
     for the reason the selector's are: this block is above both of them, and a
     rule here that names a property they set is a rule the later one settles. */

  /* The unread count, which is the smallest thing this application draws and is
     drawn on the tab a person is deciding whether to press. It stays a compact
     pip and stops being a compact pip nobody can read. */
  .pip {
    min-width: 1.4rem;
    height: 1.4rem;
    font-size: var(--text-xs);
  }

  /* The rest of the site, at the foot of the page, is answered beside its own
     rules for the reason the three blocks below give: the footer's section is
     under this block, and the row gap written here was being settled by it. */

  /* What is waiting for you, on the home screen. Each line is a link and then a
     sentence explaining it, which reads as one paragraph on a wide screen and as
     a link buried in a paragraph on a phone. As rows they are what they are: a
     thing to press, and the reason to press it under it. `justify-self` keeps the
     underline the width of the words rather than the width of the card. */
  .todo li {
    display: grid;
    gap: 0.1rem;
  }

  .todo button.link {
    padding: 0.5rem 0;
    justify-self: start;
  }

  /* The name and value pairs, which the comment above `.facts` has always said
     were "label above value on a phone" and which have been two columns at every
     width since. Two columns of a 375 pixel card is a label column as wide as its
     longest label and a value column holding a card number, a domain or a
     well-known URL in whatever is left. */
  .facts {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.1rem 0;
  }

  .facts dt {
    margin-top: 0.55rem;
  }

  .facts > dt:first-child,
  .facts > div:first-child > dt {
    margin-top: 0;
  }

  /* The console's filters, each taking the width. A search box sharing a phone
     with a status menu and a date is four controls none of which can be typed
     in, and the basis that makes them a sensible row on a laptop is what puts
     them all on one line here. */
  .filters > label,
  .filters > label:first-child,
  .filters > button {
    flex: 1 1 100%;
  }

  /* And the row of controls at the foot of a card, which is a flex row only
     where it is also written `.row`. On its own it is a block, and its
     `align-items` and the `flex` on its buttons have been declarations with
     nothing to act on: "Newer", "Showing 1 to 50" and "Older" ran together as
     one line of inline text, and `.guide-foot` asked for `space-between` and did
     not get it. On a phone those are the rows where it shows, because a
     tappable link is tall enough that three of them sharing a line of text is a
     paragraph rather than a row. */
  .form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1rem;
  }

  /* The organization bar. The standing group takes a line of its own rather than
     being squeezed against a company name, which it already did whenever the name
     was long; making it always true is what lets the key panel below be anchored
     to one edge instead of to whichever edge the wrap happened to leave it at. */
  .orgbar-standing {
    flex: 1 1 100%;
  }

  .orgbar-who select {
    max-width: 100%;
  }

  /* And the panel itself, from the leading edge. Right aligned it opened inside
     the column only while its button was at the right of the bar; on the line of
     its own above, the same panel hung off the left of the screen. The width is
     clamped against the viewport rather than the card for the reason the panel
     already gave: this card is as wide as the workspace, and the viewport is what
     actually runs out. */
  .keylock-panel {
    left: 0;
    right: auto;
    width: min(26rem, calc(100vw - 3rem));
  }

  /* The front door's two segmented controls used to be answered here and are
     answered under "The audience selector" instead. Not a change of mind about
     where phone rules live: every rule this block had for them named a property
     the picker's own section sets further down the file, so the grid, the
     legend width and the radius written here were all settled by the later rule
     and a phone got the desktop control at a phone's width. They are in a block
     beside the rules they have to beat now, which is the only place they can
     be. */
}

/* Someone who has asked their system for less motion gets none of it.
 *
 * Every animated thing in this application is decoration on a state that is
 * already drawn without it: a meter's width is a number written beside it, a
 * hover tint is a pointer that is already over the control. So this is a blanket
 * rule rather than a list, and the property it defends is the one the task of
 * respecting this setting is actually about: every element has a visible resting
 * state, and the motion only ever goes from that state to another one. Nothing
 * here fades in from nothing, so nothing here is invisible when the motion is
 * taken away. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }

  .meter-fill,
  .spark-fill {
    transition: none;
  }
}

/* The operator's console ------------------------------------------------
 *
 * Deliberately the same furniture as the rest of the app — the same cards, the
 * same badges, the same type — with one exception: the workspace switch and the
 * standing notice at the top of every screen. A console that looked like a
 * different product would be a console an operator forgets they are inside. */

.workspace.admin.active {
  /* The one place a workspace is tinted. Not decoration: the header is the only
     thing on screen that says which seat this session is sitting in, and this
     seat is the one that can see across accounts. */
  border-color: var(--danger);
  color: var(--danger);
}

.banner.console {
  border-color: var(--danger);
  color: var(--text);
}

.banner.console strong {
  color: var(--danger);
}

/* A quieter link than `.link`, for the administrator's door on the sign-in
   screen: it should be findable and should not compete with the two things
   almost every visitor is actually there to do. */
.link.quiet {
  color: var(--muted);
  font-size: var(--text-sm);
}

/* Counters ------------------------------------------------------------- */

.stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.stat {
  flex: 1 1 7rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.6rem 0.75rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

.stat-value {
  font-size: 1.5rem;
  line-height: 1.1;
  font-weight: 660;
  letter-spacing: -0.02em;
  /* Tabular figures so a column of counters lines up rather than shimmying as
     the numbers change width. */
  font-variant-numeric: tabular-nums;
}

.stat-label {
  font-size: var(--text-xs);
  color: var(--muted);
}

.stat.good .stat-value {
  color: var(--ok);
}

/* Amber rather than red: a pending check and a suspended account are things to
   look at, not things that have gone wrong. */
.stat.warn .stat-value {
  color: var(--warn);
}

/* Trends ----------------------------------------------------------------
 *
 * One series per row of columns, and two rows stacked rather than one chart
 * with two scales on it: signups and applications are different magnitudes on
 * any working deployment, and a chart with two y-axes is a chart that can be
 * made to say anything. Each row is scaled against its own peak, which the
 * sentence under it names, so the bars are never a comparison between the two.
 *
 * No axis furniture, no gridlines, no legend. One series needs no legend — the
 * heading names it — and thirty date labels under thirty columns would be
 * unreadable at any width this console is opened at, so the dates live where
 * they can be read one at a time: on each column's own `title` and
 * `aria-label`. The fill is the accent and carries no meaning beyond "this is
 * the data"; every number on screen wears the ordinary text colours. */

.spark {
  margin-top: 1rem;
}

.spark h4 {
  margin: 0;
  font-size: var(--size-h4);
}

.spark-bars {
  list-style: none;
  margin: 0.5rem 0 0.4rem;
  padding: 0;
  display: flex;
  align-items: flex-end;
  /* A 2px channel of the card's own ground between adjacent columns, so thirty
     bars read as thirty bars rather than as one filled block. */
  gap: 2px;
  height: 3rem;
}

.spark-bar {
  flex: 1 1 0;
  height: 100%;
  display: flex;
  align-items: flex-end;
  min-width: 0;
}

.spark-fill {
  display: block;
  width: 100%;
  background: var(--accent);
  /* Rounded at the data end only. The baseline is a shared edge and rounding
     it would lift every bar off it. */
  border-radius: 3px 3px 0 0;
  transition: height 240ms ease-out;
}

/* A day nothing happened on. A hairline rather than an absence, because a gap
   in a row of columns reads as a day with no figure, and there is no such day
   in this series — core-api generates the calendar and joins the counts onto
   it. Drawn in the line colour rather than the accent so it is legible as a
   floor and never as a quantity. */
.spark-fill.empty {
  height: 1px;
  background: var(--line);
  border-radius: 0;
}

.queue-call {
  border-color: var(--warn-line);
}

/* The institution's onboarding checklist, over every employer tab until
   Verigrant approves the institution (views::employer::checklist). The step in
   front of the reader carries aria-current="step", and that attribute is what
   marks it here, so what a screen reader announces and what a sighted reader
   sees are one fact rather than two kept in step. A later step is dimmed: it is
   on the list so the whole way is visible, and it asks for nothing yet. */
.checklist li[aria-current="step"] {
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

.checklist li.later strong {
  color: var(--muted);
  font-weight: 500;
}

/* A row of filters is a row of inputs that should not each grab twelve rems:
   they are short, and the search box is the one that deserves the space. */
.filters > label {
  flex: 0 1 10rem;
  margin-bottom: 0;
}

.filters > label:first-child {
  flex: 2 1 16rem;
}

.filters > button {
  flex: 0 0 auto;
}

.badge.suspended {
  color: var(--danger);
  border-color: var(--danger);
}

.badge.admin {
  color: var(--warn);
  border-color: var(--warn-line);
}

/* One entry of the trail. The action is the thing being scanned for, so it is
   the only part set in the mono face. */
.action {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

.trail-list .draft.small {
  font-size: var(--text-xs);
  padding: 0.5rem;
  margin: 0.5rem 0 0;
  color: var(--muted);
}

/* A card holding nothing but a list should not add its own padding on top of
   the list's. */
.card.list {
  padding-bottom: 0.25rem;
}

.card.check .facts {
  margin-bottom: 0.5rem;
}

/* The new seeker's guide ------------------------------------------------ */

.guide-head {
  border-color: var(--accent);
}

.guide-rail {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

/* Numbered pips rather than named steps: nine labels do not fit a phone, and
   the name of the step is already the heading directly above them. */
.pip-step {
  width: 2.25rem;
  height: 2.25rem;
  min-height: 0;
  padding: 0;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line-strong);
  background: var(--surface-2);
  color: var(--muted);
  font-size: var(--text-sm);
  cursor: pointer;
}

.pip-step.active {
  border-color: var(--accent);
  color: var(--accent-ink);
  background: var(--accent);
}

/* A pip is a circle, so the target a thumb needs is both of its dimensions
   rather than a min-height. The rule used to be in the one phone block near the
   top of this file, where the rule directly above settles it and a phone drew a
   36 pixel step; it is here, below that rule, so that it is the answer rather
   than a statement of intent. */
@media (max-width: 40rem) {
  .pip-step {
    width: 2.75rem;
    height: 2.75rem;
  }
}

.explainer {
  margin: 0 0 0.75rem;
  padding-left: 1.25rem;
}

.explainer li {
  margin-bottom: 0.75rem;
}

.explainer p {
  margin: 0.25rem 0 0;
  color: var(--muted);
  font-size: var(--text-sm);
}

.guide-foot {
  justify-content: space-between;
}

/* The platform, and the record standing on it -----------------------------
 *
 * This is the front door's whole composition, and it is a composition rather
 * than an order.
 *
 * What was here before was a stack: a marking, a selector, an explanation and
 * then the workspace, four surfaces at one elevation in one set of colours. Each
 * of them was well made and they were all asking for the same attention. So the
 * one question a front door exists to answer, which is what the thing on the
 * screen actually is, was the question the screen did not answer. A reader who
 * already knew the product saw a demonstration. A reader who did not saw four
 * boxes.
 *
 * So there are two planes. `.stage-platform` is everything that explains
 * Verigrant: the orienting line, the selector, the answer it produces and the
 * marking. It is set on `--stage`, which is a step behind the page in both
 * themes, its type is a step down, and its ink is the muted one. `.stage-record`
 * is the application: ordinary page ground, ordinary cards, full contrast, an
 * edge and a shadow, and it overlaps the platform's lower edge so that the
 * relationship is literal rather than implied. The eye lands on the lifted plane
 * first, which is the record, and the explanation is a glance away rather than
 * in the way.
 *
 * Nothing is hidden to achieve it. Every control the selector had is on the
 * screen, at full size, and still reshapes what is on top of it; the marking is
 * on every tab, in both sentences, as it was. What they gave up is contrast they
 * were spending against the product.
 *
 * The scrim is a gradient into `--bg` across the platform's last band rather
 * than a blur. It is cheap, it is exact in both themes, and it does the one
 * thing a blur would do here: the platform stops being a panel with a bottom
 * edge and becomes a ground the record is standing on. */

.stage {
  position: relative;
  display: grid;
  min-width: 0;
}

/* Which of the four is being read, resolved once at the top of the composition.
 *
 * Every element under here that carries the audience's colour spends
 * --audience, --audience-soft or --audience-line and none of them knows which
 * audience it is drawing. That is the point of doing it with a custom property
 * rather than with four rules per element: `app::App` writes one class on the
 * stage, and the selector's chosen position, the heading that answers it and
 * the record's own edge all change together. Adding a fifth audience is one
 * palette in the token block and one line here.
 *
 * The seeker's is the default in `:root`, so a composition with no modifier on
 * it draws exactly what the application opens on rather than nothing. */
.stage-seeker {
  --audience: var(--audience-seeker);
  --audience-soft: var(--audience-seeker-soft);
  --audience-line: var(--audience-seeker-line);
}

.stage-employer {
  --audience: var(--audience-employer);
  --audience-soft: var(--audience-employer-soft);
  --audience-line: var(--audience-employer-line);
}

.stage-school {
  --audience: var(--audience-school);
  --audience-soft: var(--audience-school-soft);
  --audience-line: var(--audience-school-line);
}

.stage-assistant {
  --audience: var(--audience-assistant);
  --audience-soft: var(--audience-assistant-soft);
  --audience-line: var(--audience-assistant-line);
}

.stage-platform {
  position: relative;
  display: grid;
  gap: var(--space-4);
  min-width: 0;
  background: var(--stage);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  color: var(--muted);
  /* The last band of padding is what the record is laid over, so it is the
     overlap plus the space the record should stand clear of the scrim by. */
  padding: var(--space-5) var(--space-4) calc(var(--space-6) + var(--space-5));
}

/* The scrim runs the whole of that last band, so the part of it a reader can
   see above the record's edge is the part that is already fading. A shorter one
   would put its strongest end behind the record, which is the half nobody
   looks at. */
.stage-platform::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: calc(var(--space-6) + var(--space-5));
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  background: linear-gradient(to bottom, transparent, var(--bg));
  pointer-events: none;
}

/* The one line that has to land, and the only thing on this layer set in the
   body ink at the body's own size. Everything under it is quieter, which is
   what makes it read as an introduction rather than as the first of four
   paragraphs. */
.stage-lede {
  margin: 0;
  max-width: 44rem;
  color: var(--text);
  font-size: var(--text-lede);
  font-weight: 560;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

/* The marking, at the foot of the layer that explains rather than across the
   top of the thing being explained. Small, because a label is a label; the
   first three words keep the body ink, because they are the part that must not
   be skimmed past. */
.stage-mark {
  margin: 0;
  max-width: var(--measure);
  color: var(--muted);
  font-size: var(--text-xs);
}

.stage-mark strong {
  color: var(--text);
  font-weight: 620;
}

/* The record, lifted. `--bg` rather than `--surface` because what is inside it
   is the workspace, cards and all: a slab the same colour as the cards on it
   would flatten the thing it is meant to be holding.
 *
 * Three things make it stand on the platform rather than after it. The negative
 * top margin is the overlap. The inline margin is what leaves the platform's
 * shoulders showing on either side, which is what a reader actually reads as
 * depth: two planes of the same width would simply abut, and the lower one
 * would look like it had stopped. And `z-index` puts this plane's shadow over
 * the platform instead of under it. */
.stage-record {
  position: relative;
  z-index: 1;
  display: grid;
  gap: var(--space-4);
  align-content: start;
  min-width: 0;
  margin: calc(var(--space-5) * -1) var(--space-4) 0;
  padding: var(--space-4);
  background: var(--bg);
  /* The edge is the chosen audience's, which is the one place on this
     composition where the colour is load bearing rather than decorative: the
     platform says who is reading and this says that the screens standing on it
     are that reader's. Four audiences that swapped four workspaces behind one
     grey hairline were four screens a reader had to compare from memory. */
  border: 1px solid var(--audience-line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
}

/* On a phone the lift is the whole of the effect and the inset is what costs
   something. Every rem the slab spends on its own margin and padding comes off
   the width of the cards inside it, and the workspace has to stay a workspace
   rather than become a column of cards with a frame drawn around it. So the
   shadow and the scrim carry the depth, the shoulder narrows to a hairline that
   is still visible, and the padding goes down to the smallest gutter that keeps
   a card off the edge. */
@media (max-width: 40rem) {
  .stage-platform {
    padding: var(--space-4) var(--space-3) calc(var(--space-5) + var(--space-4));
  }

  .stage-record {
    margin-inline: var(--space-1);
    padding: var(--space-2);
  }
}

/* The audience selector ---------------------------------------------------
 *
 * Two rows of buttons, asking who is reading and how much of the machinery they
 * want. Buttons rather than a select for the reason the component gives: the
 * whole argument of the control is that the other answers are visible and that
 * trying one costs a press.
 *
 * It borrows the workspace switch's shape rather than the tab strip's, because
 * it is the same size of move: changing audience swaps the screens under it,
 * and it should not look like changing tab.
 *
 * # Why it is two columns on a laptop
 *
 * Because of what is underneath it. The answer this control produces is a
 * heading and a paragraph, and stacking those under two rows of controls put
 * four hundred pixels of explanation between a visitor and the working product
 * the explanation is about. Side by side it is about half that, which is the
 * difference between a first screen that shows the record and a match and one
 * that shows a sentence promising them. The question is still read first: it is
 * the first thing on the layer, and on a phone, where there is one column, it is
 * still first.
 *
 * The audience row is the exception and is above the split rather than inside it,
 * because four positions need a whole line and half a laptop is not one. The
 * argument and the measurements are at the two column block below. */

.picker {
  display: grid;
  gap: var(--space-3) var(--space-5);
  align-items: start;
  min-width: 0;
}

/* It is no longer a card, so the measure a card gave its prose has to come from
   somewhere. */
.picker p {
  max-width: var(--measure);
}

.picker-rows {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
}

.picker-answer {
  min-width: 0;
}

/* The explanation, a step down from the line above it and a step down again
   from the record beside it. It is still a heading and still the sentence that
   answers what the reader chose; what it is not any more is the largest thing
   on the screen, which it was while the record it introduces was a card of the
   same weight.
 *
 * It is set in the chosen audience's colour, which is the one heading in this
 * application that is not the body ink. The reason is what the heading is: it
 * is the answer to the position the reader just pressed, and colouring it is
 * what joins the three places the choice shows up. Pressing "School" moves the
 * fill in the control, this line, and the edge of the record below, which is a
 * screen that visibly changed rather than a screen that says it did. Every one
 * of the four clears 4.5:1 against the platform it is set on, in both themes,
 * and the test that says so is in `crate::stylesheet`. */
.picker-answer h2 {
  margin-bottom: 0.35rem;
  font-size: var(--size-h3);
  color: var(--audience);
}

.picker-answer p {
  margin: 0;
  font-size: var(--text-sm);
}

/* The one contextual card on this layer, set off from the sentence above it.
   `.picker-answer` is a plain block rather than a grid with a gap, and the
   paragraphs inside it carry no margin, so without this the card's hairline
   would start on the same line the lede ends on. The card's own rules are its
   own; this is the space between two things, which belongs to whichever of them
   knows they are neighbours. */
.picker-answer .reveal {
  margin-top: var(--space-3);
}

/* One question and its answers, on one line ---------------------------------
 *
 * The legend, then the track, and the track takes whatever is left of the line.
 *
 * Nothing here wraps any more, and that is the founder's note on this control.
 * A wrapping row of four positions is a block of boxes two above two, which at a
 * glance is not one control with four answers in it: it is what he read as
 * forms stacked on top of each other. A control that is a line is read as a
 * line, and the four audiences are then plainly four ways of answering one
 * question rather than four things on a screen.
 *
 * So the width is found rather than folded, and the finding is the layout's job
 * rather than the track's. The audience row is given the whole platform at every
 * width, its positions grow to share that line, they never shrink below their own
 * labels, and only on a phone under about 360 pixels does the track scroll
 * sideways by the few pixels it is over. See `.picker-track` for what sits on
 * the line, and the two blocks at the foot of this section for where the line
 * comes from on a laptop and for the one screen that is short of it. */
.picker-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.picker-legend {
  /* It is a label on a control and gives up none of its width to the track: a
     legend that shrank would set "I am" over two lines beside a row that is one
     line high. */
  flex: 0 0 auto;
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: 540;
  min-width: 4.5rem;
}

/* `--bg` rather than `--surface-2`, and the reason is the layer under it: the
   platform is a recessed plane, and a track filled with the raised-field colour
   sat invisibly on it in the light theme. The page ground reads as a step up
   from the platform in both themes, which is what a control should do.
 *
 * A rectangle rather than a capsule, and that is the shape change the whole of
 * this control needed. Four capsules inside a fifth read as bubbles laid on a
 * panel: on a phone they were four large rounded blobs, and the thing they are
 * actually for, which is that these are the positions of one control, was the
 * thing the shape did not say. One rounded rectangle with segments in it says
 * it, and it says it at a size a header can carry.
 *
 * The gap is the divider. It is a single pixel with the track's own ground
 * showing through, so a hairline falls between two segments without either of
 * them carrying a border, which is what keeps the row one control instead of a
 * line of chips that happen to be adjacent.
 *
 * It takes the rest of the line and it does not wrap, and on every screen from
 * a phone up it is given a line the positions fit: the audience row is the width
 * of the platform, which is two and a half times what its four labels ask for.
 * So they spread across it, which is the even single line a desk should see, and
 * they do it by being sized rather than by being hidden. Nothing above 40rem
 * clips, scrolls or folds, and none of those three is a promise this file is
 * keeping by hand: see the laptop block at the foot of this section for the
 * width, and `.picker-option` below for why a label cannot be cut down to fit
 * one.
 *
 * The one screen that genuinely runs out of line is a phone under about 360
 * pixels, and what it gets is a sideways scroll with the scrollbar hidden. Those
 * declarations are in the phone block at the foot of this section, with the rest
 * of what a narrow screen is charged, because that is the only width they are
 * ever wanted at: a track that is a scroll container on a desk is a track that
 * answers a mistake in the layout by hiding the evidence of it. */
.picker-track {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: nowrap;
  gap: 1px;
  padding: 1px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg);
  min-width: 0;
}

.picker-option {
  /* Grow to share the line, and never shrink below the label. The first half is
     what spreads four positions across the track; the second is the whole of why
     nothing in this control is ever drawn as "AI assis". A flex item that cannot
     shrink is never narrower than its own text, so a cut label is not a thing
     this row has to be defended against: it is a thing the shrink factor makes
     unreachable. The `min-width: 0` and the `text-overflow: ellipsis` that used
     to sit here were the two halves of a clip, kept as a floor nothing could
     reach, and a row that is offered a clip is a row that eventually takes one.
     Neither is missed, because a control whose labels fit needs no fallback for
     labels that do not. */
  flex: 1 0 auto;
  background: transparent;
  border: none;
  color: var(--muted);
  padding: 0.35rem 0.75rem;
  border-radius: var(--radius-xs);
  white-space: nowrap;
  font-size: var(--text-sm);
  font-weight: 560;
  min-height: 2.25rem;
  /* From one drawn state to another, like the tab strip's: the resting state is
     the control itself, and the blanket reduced-motion rule above takes the
     travel away without taking anything else with it. */
  transition: color 120ms ease, background-color 120ms ease;
}

/* The ring is drawn inside the segment rather than two pixels outside it. There
   is one pixel of track around the row and one between any two positions, so the
   offset ring the rest of this application uses would be drawn over the track's
   own edge and over the segment beside it. On the narrow phone where the track
   scrolls it would also be trimmed at the two ends of the row, which is a scroll
   container clipping at its padding box. Inside, a keyboard reader gets the whole
   ring wherever they are along the track. */
.picker-option:focus-visible {
  outline-offset: -2px;
}

/* The inactive positions are a quiet neutral rather than nothing at all: a
   segment that lights under the pointer is a segment somebody can tell is a
   segment before they press it. */
.picker-option:hover:not(.active) {
  background: var(--surface-2);
  color: var(--text);
}

/* The two controls, drawn as two controls --------------------------------
 *
 * They used to be the same green. Four accent-filled positions above two more
 * accent-filled positions, on a layer whose one other piece of colour is the
 * accent, is a band in which nothing is distinguishable from anything: which
 * of the two rows was which question, and which position in a row was the
 * answer, were both being carried by reading the words.
 *
 * So colour is spent on one of them. The audience is the choice that swaps the
 * screens underneath, so its answer is filled: it is the loudest thing on this
 * layer because it is the only control on it that changes what the reader is
 * looking at. The depth chooses between two ways of saying the same thing, so
 * its answer is a raised neutral segment with a hairline round it, which is how
 * a mode switch has looked since before any of this.
 *
 * # And each audience fills it with its own hue
 *
 * The four positions were four fills of one green, so pressing one changed a
 * word and nothing else: the control said which answer was chosen only to
 * somebody already reading it, and the screens it swapped between were
 * identical in colour. Each position now names its hue on its own class, the
 * chosen one fills with it, and the same hue runs down through the heading that
 * answers the choice and the edge of the record standing on the platform. See
 * "The four audiences, one hue each" in the token block.
 *
 * `--option` is the position's own colour and `--audience` is the composition's.
 * They are the same value whenever a position is the chosen one, which is the
 * only time the fill is drawn, and they are deliberately two properties: one
 * belongs to a button and the other to the screen, and a control that read the
 * screen's would light up in the colour of whichever audience was already
 * showing.
 *
 * An unchosen position stays the muted ink it has always been and takes its own
 * colour under the pointer. Four coloured words in one track would be a rainbow
 * rather than a control; a position that lights in the colour it is about to
 * paint the screen is a preview. */

.picker-row.who .picker-track {
  border-color: var(--audience-line);
}

/* The audience row is marked up as `picker-row who`, and `who` is already taken.
 *
 * Far up this file, `.who` is the byline that sits under a name: muted, small,
 * and cut off with an ellipsis at `14rem` so a long title cannot push the line
 * it shares out of shape. That is a sound rule for a byline and a ruinous one
 * for a four-position control, and the audience row was matching both. The row
 * was being held at 224px and clipped there, so at 1024px the control read
 * "Job seeker | Emp" and Employer, School and AI assistant were simply gone —
 * not overflowing, not wrapped, hidden. The grid underneath had been giving the
 * row its full track the whole time; a name collision was throwing it away.
 *
 * So the four properties the byline sets are undone here, on the row, and only
 * on the row. The byline keeps its ellipsis: this selector carries a class more
 * than the bare `.who` does and wins on specificity without touching it. The
 * alternative was renaming one of the two, which is a larger change across the
 * markup for the same result. */

.picker-row.who {
  max-width: none;
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
}

.picker-option.who-seeker {
  --option: var(--audience-seeker);
}

.picker-option.who-employer {
  --option: var(--audience-employer);
}

.picker-option.who-school {
  --option: var(--audience-school);
}

.picker-option.who-assistant {
  --option: var(--audience-assistant);
}

.picker-row.who .picker-option:hover:not(.active) {
  color: var(--option);
}

.picker-row.who .picker-option.active {
  background: var(--option);
  color: var(--audience-ink);
  font-weight: 620;
}

.picker-row.who .picker-option.active:hover {
  background: var(--option);
  color: var(--audience-ink);
}

.picker-row.depth .picker-track {
  border-color: var(--line-strong);
}

/* The two front doors, first and largest ------------------------------------
 *
 * They are the product's own division (a person and their record, or an
 * organization taking applications in) rather than a fifth and sixth audience,
 * so they are two cards rather than two positions in a segmented control, and
 * they are drawn in the brand's accent rather than in one of the four audience
 * hues: a reader sees two ways in before any detail about either, and sees the
 * examples below as what is behind the one they chose.
 *
 * They were a segmented pill in the same row as the line describing them, the
 * same weight as the example and depth rows, and "For organizations" ran out of
 * its segment and over the description beside it. A card cannot do that: its
 * title and its line wrap inside it, the two cards share the row as two equal
 * columns that are allowed to shrink (`minmax(0, 1fr)`), and on a phone they
 * stack, so neither is ever narrower than a readable line.
 *
 * The chosen card is marked three ways at once, none of which leans on colour
 * alone: an accent border drawn two pixels thick (the second pixel is an inset
 * shadow, so the card does not change size when it is chosen), a tinted ground,
 * and `aria-pressed` for anything reading the markup rather than the pixels. */
.door-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2);
  min-width: 0;
  /* The room between the doors and the examples behind them, so the two rows
     read as a choice and then its detail rather than as one block. */
  margin-bottom: var(--space-3);
}

.door-cards.compact {
  margin-bottom: 0;
}

.door-card {
  display: grid;
  align-content: start;
  gap: var(--space-1);
  min-width: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  color: var(--text);
  font: inherit;
  text-align: left;
  white-space: normal;
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.door-card:hover:not(.active) {
  border-color: var(--line-strong);
}

.door-card:focus-visible {
  outline-offset: 2px;
}

.door-card.active {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent), var(--shadow-sm);
}

.door-card-title {
  font-size: var(--size-h4);
  font-weight: 680;
  line-height: var(--leading-tight);
  overflow-wrap: break-word;
}

.door-card.active .door-card-title {
  color: var(--accent);
}

.door-card-line {
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: var(--leading);
}

/* The compact pair at the top of the sign up and sign in card: titles only,
   and small enough to sit side by side even on a phone, where each title
   wraps to two lines inside its card rather than leaving it. */
.door-cards.compact .door-card {
  padding: var(--space-2) var(--space-3);
}

.door-cards.compact .door-card-title {
  font-size: var(--text-base);
  font-weight: 620;
}

@media (max-width: 40rem) {
  .door-cards {
    grid-template-columns: minmax(0, 1fr);
  }

  .door-cards.compact {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .door-card {
    padding: var(--space-2) var(--space-3);
  }
}

/* The same two doors at the top of the sign up and sign in card. */
.auth-door {
  display: grid;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}

/* The hairline is an inset shadow rather than a border, so the chosen position
   is exactly the size the unchosen ones are. A border here would grow the
   segment by two pixels and nudge the one beside it every time somebody
   pressed either. */
.picker-row.depth .picker-option.active {
  background: var(--surface-2);
  color: var(--text);
  font-weight: 620;
  box-shadow: inset 0 0 0 1px var(--line-strong), var(--shadow-sm);
}

/* The selector on a laptop, and why the audience row is above the split ----
 *
 * Here rather than at the head of this section, where the split used to be
 * written, and the reason is the phone block's reason one paragraph down: a media
 * query adds no specificity, `.picker-rows` sets a `display` of its own above,
 * and a `display` written for a laptop in a block above that one would be settled
 * by it. The wrapper would stay a box, the audience row would stay in a column,
 * and the defect below would be shipped again with a rule in the file saying it
 * had been fixed. So the block that has to beat those rules is written after
 * them.
 *
 * # What was wrong at 1024 pixels
 *
 * Four positions do not fit half of a laptop, and the arithmetic that said they
 * did was the arithmetic of a row that just fits. The split used to hand both
 * questions to the first column, and the first column at 64rem is about four
 * hundred and eighty pixels: seventy two of them are the legend, nine are the
 * gap, and the track gets a little over four hundred. Inside it, "Job seeker",
 * "Employer", "School" and "AI assistant" come to about three hundred pixels of
 * label at this type and the four insets come to another ninety six, which is a
 * row within a few pixels of the track it has to fit. Which side of those few
 * pixels it fell on was then decided by the system font the reader happened to
 * have, and on a 1024 pixel window with a wide one it fell outside. The previous
 * pass answered that by letting the track scroll, so the first control on the
 * front door read "Job seeker | Emp" and three of the four audiences were off the
 * end of a row nothing said was a row.
 *
 * # What it is instead
 *
 * The row with the most positions in it is given the whole platform, and the
 * split applies to what is under it. The audience track is then about eight
 * hundred and twenty pixels at 1024 and about nine hundred and twenty at the 1120
 * pixel container, against the three hundred its labels need, and between 40rem
 * and 64rem it is the single column's full width for the same reason. There is no
 * width at or above 40rem where this row and its own labels are within measuring
 * distance of each other any more, which is what it takes for a control to be
 * drawn the same way on every machine rather than only on the machine it was
 * checked on.
 *
 * The depth keeps its place in the first column beside the answer, because two
 * positions and a legend want about two hundred and sixty pixels of the four
 * hundred that column has. The answer takes the wider half now: the reason the
 * question used to have it was the four positions, which are no longer in it, and
 * what is in the second column is a heading, a sentence and a card.
 *
 * Both legends keep the same minimum width, which is what starts the two tracks
 * on one vertical line while they end on different ones. They are two questions
 * of different sizes and they are allowed to look it. What they are not allowed
 * to do is hide an answer.
 *
 * `display: contents` is how the audience row reaches out of the element it is
 * marked up inside. `.picker-rows` is one box holding both questions and a box
 * cannot be in two places on this grid, so either it stops being a box here or
 * the component is cut in two in the markup to suit a stylesheet. It stops being
 * a box. What that costs is the wrapper's own gap, which goes with it, so the row
 * gap below is that gap and the mechanism list makes up the difference to the
 * step it used to have. The two declarations are load bearing for each other and
 * `crate::stylesheet` checks them as a pair: a span across both columns does
 * nothing while the wrapper is still a box, and either one alone is the four
 * hundred pixel column back again.
 *
 * Between 40rem and 64rem the control is a single column with the whole platform
 * to lay both rows across. What that costs is the answer sitting under the
 * question rather than beside it at those widths, and that is the right way
 * round: a window that size has height to spend, and the thing it does not have
 * is a line wide enough for a control and an explanation side by side. */
@media (min-width: 64rem) {
  .picker {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
    /* The space between the two questions, which is what `.picker-rows` was
       holding them at while it was a box. The column gap is the base rule's and
       is deliberately not restated. */
    row-gap: var(--space-2);
  }

  .picker-rows {
    display: contents;
  }

  /* The audience across the platform, the depth under it, the answer beside both
     of them. The span is the whole of the fix: a row placed in column one is a
     row inside the four hundred pixels that were not enough. */
  .door-cards {
    grid-column: 1 / -1;
    grid-row: 1;
  }

  .picker-row.who {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .picker-row.depth {
    grid-column: 1;
    grid-row: 3;
  }

  .picker-answer {
    grid-column: 2;
    grid-row: 3;
  }

  /* The mechanism list is the one thing that is allowed the full width: it is
     three numbered paragraphs and a column half the platform wide would set them
     as a ladder of two word lines. It is placed by the flow rather than on a row
     of its own, so the two rows above are the only explicit ones and a reader at
     the first depth is not charged a gap for a list that is not there.
   *
   * The margin is the other half of the row gap: the gap is now the space between
   * the two questions, and this is the difference between that and the step this
   * list has always stood off the control by. */
  .explainer {
    grid-column: 1 / -1;
    margin-top: var(--space-1);
  }
}

/* The selector on a phone -------------------------------------------------
 *
 * Here rather than in the one block at the top of this file that is otherwise
 * where rules under 40rem live, and the reason is the cascade rather than a
 * preference. That block is above this section; every rule it carried for this
 * control named a property the rules above set, and a later rule of the same
 * specificity wins, so a phone has been drawing the desktop control at a
 * phone's width for as long as those rules have been there. What follows is
 * what they were meant to do, in the only place they can do it.
 *
 * # What is wrong with it on a phone
 *
 * It is too tall, and being too tall it is the product. The composition this
 * control belongs to is a recessed platform with the record standing on it: the
 * platform explains and the record is the thing being explained, and the eye is
 * supposed to land on the record. On a 375 pixel screen the explanation was
 * taking the better part of four hundred pixels before the record's first card
 * began, so a visitor's whole first screenful was the selector, the answer to
 * it and the marking. The thing it is a platform for was below the fold. That
 * is not a control that is slightly large; it is a composition inverted by a
 * width.
 *
 * # What it gives up, and what it must not
 *
 * Every rem here comes off the type, the padding and the gaps, and none of it
 * comes off what the control offers. All four audiences are on the screen, both
 * depths are on the screen, each of them is one press, and each of them is
 * 2.75rem tall, which is the figure the rest of this file uses for anything a
 * thumb has to hit. A selector that answered "too tall" by hiding two of its
 * four positions behind a menu would have solved the founder's complaint by
 * removing the thing the front door exists to demonstrate.
 *
 * **The legend takes a line of its own, at the small size.** "Show me" and its
 * positions do not share 375 pixels, and the four and a half rems the legend is
 * held to on a desk is a third of a phone spent on two words. It is
 * `--text-xs`, which the phone block at the top of this file raises to 13
 * pixels, set on a tight leading: it is a label on a control rather than
 * something to read, and given the line back the positions have the width of
 * the platform to divide.
 *
 * **The four positions fit one line, by shrinking.** They used to be pinned to
 * 45 percent, which is two above two, because at the desk's type and padding
 * three of them could not share a line. At 13 pixels and a quarter rem inset all
 * four can. At 375 the platform gives the track about three hundred and ten
 * pixels, the four insets take thirty two of them, and the labels come to
 * something between two hundred and two hundred and sixty five depending on the
 * system font the phone has: the wide end of that is the one this file is sized
 * against, and it fits. The inset is a quarter rem rather than the 0.4rem it was
 * for that reason alone. It is a floor rather than the look of the control, since
 * whatever the line has spare is shared out on top of it, and at 375 on a
 * narrower font that is fifty pixels of breathing room the positions get for
 * free. The tracking comes in with it, by about the width the header's own gaps
 * were tightened by and for the same reason: a row decided by a few pixels is a
 * row decided by whichever font the phone happens to have.
 *
 * Nothing above is an even split, and that is `flex: 1 0 auto` doing it. Each
 * position starts at the width of its own label and grows from there, so the
 * spare is shared on top of four labels that already fit rather than divided into
 * four equal segments, one of which would be too narrow for "AI assistant" while
 * the segment holding "School" had room going begging.
 *
 * **And a line is what it stays.** This block used to wrap instead, and the
 * founder's second look at the control is why it does not: two rows of boxes
 * read as forms stacked on top of each other rather than as one question with
 * four answers, which is the thing the whole platform is for. Under about 360
 * pixels, where the labels genuinely will not fit at any size this file is
 * willing to draw a control at, the track scrolls sideways by the few pixels it
 * is over and every position is still one press. That is a real cost and it is
 * the smaller one: an audience a thumb has to flick to reach is worse than
 * nothing only if a reader cannot tell it is there, and what tells them is the
 * position cut off at the edge of the track.
 *
 * The scroll is written for the whole of this block rather than for a query at
 * 360 pixels, and it is worth being plain about why. It pays out only where the
 * labels overrun, which is under about 360 with the widest font and under about
 * 300 with the fonts a phone actually ships, and where they overrun is not a
 * number this file can know. What it must not be is a scroll a desk has, which is
 * how the audience row came to be drawn as "Job seeker | Emp" on a 1024 pixel
 * window: above 40rem the positions fit because the layout gives them the room,
 * and `crate::stylesheet` fails a build in which any track wider than a phone's
 * has an overflow to hide them behind.
 *
 * The depth's two positions grow from their natural widths for the same reason
 * they always have: "Guide me" needs about seventy pixels and "Show me how it
 * works" about a hundred and thirty five, so an even split would give the short
 * one room it does not want and leave the long one short of its own label. */
@media (max-width: 40rem) {
  .picker {
    gap: var(--space-2);
  }

  .picker-rows {
    gap: var(--space-1);
  }

  .picker-row {
    display: grid;
    gap: 0.15rem;
  }

  .picker-legend {
    min-width: 0;
    font-size: var(--text-xs);
    line-height: var(--leading-tight);
  }

  /* The scroll, at the one width that wants one. `overscroll-behavior-x` is
     there because the row scrolls sideways inside a page that scrolls down, and
     without it a diagonal drag scrolls both. The scrollbar is hidden for the tab
     strip's reason, which is that a scrollbar under a four segment control is a
     second rule across the screen; the honest cost is that the only thing saying
     the row continues is the position cut off at the edge of it, and that cost is
     paid on one screen size rather than on all of them. */
  .picker-track {
    overflow-x: auto;
    scrollbar-width: none;
    overscroll-behavior-x: contain;
  }

  .picker-track::-webkit-scrollbar {
    display: none;
  }

  .picker-option {
    min-height: 2.75rem;
    padding-inline: 0.25rem;
    font-size: var(--text-xs);
    letter-spacing: -0.01em;
  }

  .picker-row.who .picker-option,
  .picker-row.depth .picker-option {
    flex: 1 0 auto;
  }

  /* The answer under the control comes down a step with it. It is a heading
     introducing a record that is now two inches lower than it should be, and at
     the desk's size it was the second largest thing on the screen after the
     orienting line above it. */
  .picker-answer h2 {
    font-size: var(--size-h4);
  }
}

/* What Verigrant is, in front of everything else ---------------------------
 *
 * The one panel in this application that covers the page, and it covers the
 * page because of what it is for: a stranger arriving at a working workspace
 * full of somebody else's record has no way of knowing what the object on the
 * screen is, and an explanation drawn *inside* that screen is one more box
 * competing with the four already there. This is the answer given the room to be
 * an answer, once, and then out of the way. `app::WelcomePanel` argues the copy.
 *
 * It is a panel of prose and nothing else. No eyebrow label over the heading, no
 * capsule of shouting capitals, no numbered steps: four paragraphs, a control
 * that closes it and a control in the header that brings it back.
 *
 * The scroll is on the panel rather than on the ground behind it, which is the
 * one layout decision here worth stating. A scrim that both centres its child
 * and scrolls clips the top of anything taller than the viewport, and the
 * viewport this has to survive is a phone in landscape. Giving the panel the
 * overflow means it is centred when it fits and scrolls inside its own edges
 * when it does not, at every height, with no second rule. */

.welcome-scrim {
  position: fixed;
  inset: 0;
  /* Above the header, which is 20 and is the highest thing in the application
     until this exists. */
  z-index: 100;
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: var(--scrim);
}

.welcome {
  position: relative;
  width: min(34rem, 100%);
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: grid;
  gap: var(--space-3);
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  color: var(--text);
}

/* Focused as the panel opens so that a screen reader begins at the heading
   rather than at a button, which is what the `tabindex` on it is for. The ring
   would then be drawn around the whole panel, which says nothing a reader
   needs: the panel is the only thing on the screen. */
.welcome:focus-visible {
  outline: none;
}

.welcome h2 {
  margin: 0;
  font-size: var(--size-h1);
  /* Clear of the control in the corner, whatever the heading wraps to. */
  padding-inline-end: 4rem;
}

.welcome h3 {
  margin: 0;
  font-size: var(--size-h3);
}

.welcome p {
  margin: 0;
  max-width: var(--measure);
}

/* The line the panel opens with, and for a moment nearly the only thing on it.
 *
 * It is set a little tighter than the heading scale would leave it, because it
 * is a spoken sentence rather than a title and has to read as one thought. The
 * measure is the reason it is not wider: a correction that runs the full width
 * of a 34rem panel is a line the eye has to track back along, and this is the
 * one line in the product that has to land first time. */
.welcome-framing {
  max-width: 30ch;
  line-height: 1.25;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* And what is true instead, directly under it.
 *
 * Set at the lede size rather than at the body's: it is two sentences, it is the
 * whole product, and a reader who stops after the headline has been told only
 * what this is not. Larger than the body and quieter than the heading is what
 * makes the pair one block rather than a title with a paragraph after it, which
 * is why it also takes a little of the panel's own gap back.
 *
 * The element is named as well as the class, and that is not decoration:
 * `.welcome p` above sets a margin and a measure on every paragraph in here and
 * would otherwise win both on specificity. */
.welcome p.welcome-framing-sub {
  margin-top: calc(var(--space-1) * -1);
  max-width: 36ch;
  font-size: var(--text-lede);
  color: var(--muted);
}

/* And everything that used to be in front of the reader, for the reader who
   asked for it. A plain group with the panel's own gap: it is not a second
   surface, a drawer or a tab, because it is the same panel with more of itself
   showing. */
.welcome-explainer {
  display: grid;
  gap: var(--space-3);
}

/* The offer, in the sentence the served document leads with, set in the brand
   colour so that a reader who has just read it on the page behind recognises it
   rather than reads it twice. It is the one line in this panel that is not the
   body ink, which is what keeps it an echo rather than a second heading. */
.welcome-hero {
  color: var(--accent);
  font-size: var(--text-lede);
  font-weight: 640;
  letter-spacing: -0.01em;
}

.welcome-lede {
  font-size: var(--text-lede);
}

/* How it works, in three -------------------------------------------------
 *
 * A numbered list rather than three more paragraphs, and the number is drawn
 * rather than left to the list marker. What a reader arriving at a screen they
 * do not understand does is scan, so the three leads have to be findable down
 * the left of the panel without the sentences under them being read first. A
 * default `ol` marker sits in the text flow and is the same size and colour as
 * the words beside it, which is exactly the thing this needs not to be.
 *
 * The counter is the element's own rather than the document's, so a second list
 * anywhere in this application cannot continue this one's numbering.
 *
 * Two columns and two rows per step: the pip spans both rows in the first
 * column, the lead is the second column's first row, and the sentence is under
 * it. That is what keeps a wrapped lead from flowing under the number. */
.welcome-steps {
  counter-reset: welcome-step;
  display: grid;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.welcome-steps li {
  counter-increment: welcome-step;
  display: grid;
  grid-template-columns: 1.75rem minmax(0, 1fr);
  column-gap: var(--space-3);
  align-items: baseline;
}

.welcome-steps li::before {
  content: counter(welcome-step);
  grid-column: 1;
  grid-row: 1 / span 2;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--text-sm);
  font-weight: 640;
  font-variant-numeric: tabular-nums;
}

.welcome-steps strong {
  grid-column: 2;
  font-weight: 620;
}

.welcome-steps p {
  grid-column: 2;
  margin: 0.15rem 0 0;
  color: var(--muted);
  font-size: var(--text-sm);
}

/* The two controls at the foot of the panel -------------------------------
 *
 * "See how it works" and "Get started", with air between them.
 *
 * `.form-actions` is a plain block above 40rem, which is deliberate everywhere
 * else in this application: the rows that are flex rows are written
 * `.form-actions row`, and the rest are blocks holding one control. This panel
 * holds two, and a block lays two buttons out as inline boxes with nothing
 * between them, so the primary and the way past it were touching on every
 * screen wider than a phone. That is what the founder read as the two controls
 * being on top of each other, and it was exactly that.
 *
 * So the panel's own row is a row at every width. The gap is the panel's
 * standard space between two different things rather than the smaller one
 * between two of a kind, because these are two different things: one opens the
 * explanation and the other leaves the panel for the product. If a long label
 * or a large system font runs the line out they wrap, and the row gap is what
 * keeps a wrapped pair apart. */
.welcome .form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  margin-top: var(--space-2);
}

/* One of the four ways out, in the corner where a reader looks for it. It is
   `.link` and therefore quiet, because the primary control under the last
   paragraph is the one this panel is actually asking for; this is for somebody
   who has read enough.
 *
 * The selector names the element as well as the class so that it beats
 * `button.link` on the colour. Muted rather than the accent: two accent controls
 * in one panel, one of them the thing being asked for and the other a way out of
 * being asked, is a panel with two primary controls. */
button.welcome-close {
  position: absolute;
  top: var(--space-4);
  inset-inline-end: var(--space-5);
  color: var(--muted);
  font-size: var(--text-sm);
}

button.welcome-close:hover:not(:disabled) {
  color: var(--text);
}

/* The panel on a phone. Beside its own rules for the reason the selector's phone
   block gives: the one block at the top of this file is above this section, so a
   rule there naming a property set here is a rule that never lands. Every rem of
   scrim around the panel on a 375 pixel phone is a rem off four paragraphs that
   are the first thing anybody reads, and the corner control follows the padding
   in so that it stays in the corner rather than a gutter's width inside it. */
@media (max-width: 40rem) {
  .welcome-scrim {
    padding: var(--space-3);
  }

  .welcome {
    padding: var(--space-4);
  }

  /* And the two controls, one above the other with a whole step between them.
     A phone is where a pair of buttons on one line is most likely to end up a
     hair apart, and where a mis-press costs the most: the panel is the width of
     the screen, so the two are stacked deliberately rather than left to wrap
     when the line happens to run out. `start` rather than stretched, because
     the second of them is a quiet link and a link drawn as a full width slab is
     a second primary control. */
  .welcome .form-actions {
    display: grid;
    justify-items: start;
    gap: var(--space-3);
  }

  button.welcome-close {
    top: var(--space-2);
    inset-inline-end: var(--space-4);
  }
}

/* A call somebody is going to make from their own software. Monospaced and
   selectable a line at a time, because it is meant to be copied; `pre` because
   the line breaks are the content. */
.request {
  margin: 0.75rem 0;
  padding: 0.75rem;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--text-xs);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.request code {
  user-select: all;
}

/* The footer, and there is one of it ---------------------------------------
 *
 * Three documents draw a footer: this application, the document it is served in
 * before the WebAssembly arrives, and every marketing page. They carry the same
 * links in the same order under the same names, set at the same size, at the
 * same distance from the content, inside the same column. That is the whole of
 * what "one footer" means and it is worth stating, because the failure it
 * prevents is not ugliness: it is a person watching the bottom of the page
 * rearrange itself a second after it loaded.
 *
 * Quiet on purpose. It is a way out for somebody who wants the long version of
 * an argument, not a second call to action competing with the two in the
 * header. */
.site-links {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--space-5) var(--gutter) var(--space-6);
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1.25rem;
  color: var(--muted);
  font-size: var(--text-sm);
}

.site-links p {
  margin: 0;
}

.site-links nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
}

.site-links a {
  color: var(--muted);
  text-decoration: none;
}

.site-links a:hover,
.site-links a:focus-visible {
  color: var(--text);
  text-decoration: underline;
}

/* Three of these were 22 pixel lines a thumb had to land on between two others.
   `inline-flex` is what turns the padding into a box the whole of which is the
   link, rather than into space above and below a line of text, and the row gap
   then comes off because the boxes are already apart. That gap used to be
   written in the one phone block near the top of this file, where the rule
   directly above settles it; here it is below that rule and therefore applies. */
@media (max-width: 40rem) {
  .site-links nav {
    gap: 0 1.25rem;
  }

  .site-links a {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
  }
}

/* The still frame, and the document around it ----------------------------
 *
 * Everything below styles `#prerender` in index.html: the marketing narrative
 * the app root is served with, and the still of the workspace under it. It is
 * in this stylesheet rather than a second one for two reasons. The frame is a
 * picture of *this* application, so it should be drawn in this application's
 * own tokens and go wrong in the same direction when one of them changes; and a
 * separate file would be a second blocking request in front of the one paint
 * this whole arrangement exists to make fast.
 *
 * Every rule here is scoped under `.prerender` or one of its own class names,
 * so nothing in it can reach the running app. boot.js removes the element the
 * moment the app has mounted, which is the other half of the same guarantee. */

.prerender {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter) var(--space-6);
}

/* Nothing painted until a keyboard asks for it, and then it is the first thing
 * in the tab order. The app itself has no equivalent because the app has no
 * page to skip past. */
.skip {
  position: absolute;
  left: -9999px;
}

.skip:focus {
  position: static;
  display: inline-block;
  margin: 0.75rem 0;
}

/* The same bar as the application's, at the same height and with the same
   hairline under it, because the application replaces it in place and a header
   that changed height on boot would move the whole page under the reader. */
.prerender-header {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  align-items: center;
  min-height: var(--header-h);
  padding: var(--space-3) 0;
  padding-top: calc(var(--space-3) + env(safe-area-inset-top));
  border-bottom: 1px solid var(--line);
}

.prerender-header .brand,
.prerender-nav a {
  text-decoration: none;
}

.prerender-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 1rem;
  font-size: var(--text-sm);
}

.prerender-nav a {
  color: var(--muted);
  white-space: nowrap;
}

.prerender-nav a:hover,
.prerender-nav a:focus-visible {
  color: var(--text);
  text-decoration: underline;
}

.prerender-main section {
  padding: var(--space-6) 0;
  border-bottom: 1px solid var(--line);
}

.prerender-hero h1 {
  font-size: var(--size-display);
  letter-spacing: -0.028em;
  max-width: 20ch;
}

/* The one paragraph that carries the offer, so it is set larger than the body
 * and narrower than the column: a measure this long is read once and has to be
 * read all the way through. */
.prerender-lede {
  max-width: 40rem;
  font-size: 1.1rem;
}

.prerender-note,
.prerender-sub {
  max-width: 40rem;
  color: var(--muted);
  font-size: 0.95rem;
}

.prerender-audiences article {
  padding: 1rem 0;
  border-top: 1px solid var(--line);
}

.prerender-door-summary {
  color: var(--muted);
}

.prerender-audiences p,
.prerender-trust p {
  max-width: var(--measure);
}

/* The two doors read side by side once there is room, because they are two
   answers to one question rather than two sections of an argument: a reader is
   looking for the one that is about them and should be able to see both at
   once to find it. */
@media (min-width: 52rem) {
  .prerender-audiences {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 var(--space-5);
  }

  .prerender-audiences h2 {
    grid-column: 1 / -1;
  }
}

.prerender-main a {
  color: var(--accent);
}

.prerender-main a:hover {
  color: var(--accent-strong);
}

/* The workspace, standing still ------------------------------------------
 *
 * A picture rather than a mock up: the same header, the same tab strip, the
 * same marking over the sample and the same selector the app draws, in the same
 * furniture, so that what replaces it a moment later is the screen that was
 * already there rather than a different one. Nothing in it is a control, which
 * is why every one of these is a `span`: a button in a still frame is a button
 * that answers a press with nothing, and that is the one thing a
 * demonstration must not teach. */
.frame {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg);
  box-shadow: var(--shadow);
  overflow: hidden;
  /* The frame is a scaled down view of a wider screen, so its text runs a step
   * smaller than the page around it. Everything inside inherits it. */
  font-size: var(--text-sm);
}

.frame-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  align-items: center;
  justify-content: space-between;
  padding: 0.65rem 0.85rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.frame-tabs {
  display: flex;
  gap: 0.25rem;
  order: 3;
  flex: 1 0 100%;
  overflow: hidden;
}

.frame-tab {
  color: var(--muted);
  padding: 0.3rem 0.7rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  font-size: var(--text-xs);
}

.frame-tab.active {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--text);
}

.frame-door {
  padding: 0.35rem 0.8rem;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 620;
  font-size: var(--text-xs);
  white-space: nowrap;
}

/* The still's own two planes, and they are the running application's two planes
 * drawn at a smaller size: `.stage-platform` and `.stage-record` above are where
 * the argument for the composition is made in full. What matters here is that
 * the picture and the screen that replaces it a second later are the same
 * picture, so the platform recedes on the same token, the record is lifted by
 * the same shadow, and the record overlaps the platform's lower edge by the same
 * kind of amount. A still that showed a stack would be a promise the application
 * then broke. */
.frame-stage {
  position: relative;
  display: grid;
  padding: 0.85rem;
}

.frame-platform {
  position: relative;
  display: grid;
  gap: 0.7rem;
  background: var(--stage);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--muted);
  padding: 0.85rem 0.85rem 2rem;
}

.frame-platform::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 2rem;
  border-radius: 0 0 var(--radius) var(--radius);
  background: linear-gradient(to bottom, transparent, var(--bg));
  pointer-events: none;
}

/* The one line, at the head of the layer. Body ink and a step up in size, like
   the running application's, because everything under it is quieter. */
.frame-lede {
  margin: 0;
  color: var(--text);
  font-size: var(--text-base);
  font-weight: 560;
  max-width: 46ch;
  text-wrap: balance;
}

.frame-answer h3 {
  margin: 0 0 0.3rem;
  font-size: var(--size-h4);
  color: var(--text);
}

.frame-answer p {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-xs);
}

/* The title of the lifted plane, in the body ink and a step above everything on
   the layer behind it, because the record is the one thing on this picture that
   is not receding. It is the same relationship the running application has
   between its orienting line and the heading on the record under it, drawn at
   the scale this frame is drawn at. */
.frame-greeting {
  margin: 0;
  font-size: var(--size-h3);
  color: var(--text);
}

.frame-record {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 0.85rem;
  margin: -1.1rem 0.85rem 0;
  padding: 0.85rem;
  background: var(--bg);
  /* The seeker's edge, for `.stage-record`'s reason and for the frame's own:
     this is a picture of the screen that is about to replace it, and a grey
     hairline here under a coloured one there would be the page rearranging
     itself the moment the WebAssembly lands. */
  border: 1px solid var(--audience-seeker-line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
}

/* The same marking the app draws over the sample, in the same colours and at
 * the same size, because it says the same thing and a reader meets it in both
 * places a second apart. It is the last line of the layer that explains rather
 * than a bordered banner across the top of the thing being explained, which is
 * the change the running application made for the reason `SampleNotice` gives:
 * a marking loud enough to compete with what it marks is one a reader learns to
 * look past. */
.frame-notice {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-xs);
}

.frame-notice strong {
  color: var(--text);
  font-weight: 620;
}

/* Every card in the still is now a cell of one of the two grids inside the
   lifted plane, so the spacing between them is that grid's gap and a margin
   here would be a second opinion about it. */
.frame-card {
  margin: 0;
  padding: 0.85rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.frame-card h4 {
  margin: 0 0 0.6rem;
}

.frame-card p {
  margin: 0;
  color: var(--muted);
}

.frame-pickers {
  display: grid;
  gap: 0.3rem;
}

.frame-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  align-items: center;
}

.frame-legend {
  color: var(--muted);
  font-size: var(--text-xs);
  min-width: 4.5rem;
}

/* The same segmented shape and the same two treatments the running selector
   has, drawn at the size this picture is drawn at. See `.picker-track` for the
   argument; what matters here is only that the still and the screen that
   replaces it a second later are the same picture. */
.frame-option {
  padding: 0.3rem 0.7rem;
  border-radius: var(--radius-xs);
  color: var(--muted);
  font-size: var(--text-xs);
}

/* The still frame's chosen position is "Job seeker", because that is the
   audience the application opens on, so it is filled with that audience's hue
   rather than with the accent. The two are the same colour today and are two
   tokens on purpose: the day the seeker's hue moves, the frame a reader sees
   before the WebAssembly lands has to move with the screen that replaces it. */
.frame-picker.who .frame-option.active {
  background: var(--audience-seeker);
  color: var(--audience-ink);
  font-weight: 620;
}

/* The two door cards, drawn at the size this picture is drawn at. The same
   shape and the same chosen state as `.door-cards` in the running selector, so
   the still and the screen that replaces it a second later are the same
   picture. */
.frame-doors {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.4rem;
  margin-bottom: 0.2rem;
}

.frame-door-card {
  display: grid;
  gap: 0.15rem;
  min-width: 0;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

.frame-door-card.active {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.frame-door-title {
  font-size: var(--text-sm);
  font-weight: 680;
}

.frame-door-card.active .frame-door-title {
  color: var(--accent);
}

.frame-door-line {
  color: var(--muted);
  font-size: var(--text-xs);
  line-height: var(--leading-tight);
}

@media (max-width: 40rem) {
  .frame-doors {
    grid-template-columns: minmax(0, 1fr);
  }
}

.frame-picker.depth .frame-option.active {
  background: var(--surface-2);
  color: var(--text);
  font-weight: 620;
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

.frame-grid {
  display: grid;
  gap: 0.85rem;
  grid-template-columns: 1fr;
  align-items: start;
}

/* The still's own first row: the record on one side, a match on the other, in
   the proportions the running application uses. It is the picture of the frame
   that matters most, because it is the one a thumbnail crops to. */
.frame-card.match {
  border-color: var(--accent-line);
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--surface);
}

.frame-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

.frame-head h4 {
  margin: 0;
}

.frame-count {
  color: var(--muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The completeness meter, standing still. Drawn at a width rather than left
   empty because an empty bar is a claim about the record, and the record this
   is a picture of is complete. */
.frame-meter {
  display: block;
  height: 0.4rem;
  margin: 0.55rem 0;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  border: 1px solid var(--line);
  overflow: hidden;
}

.frame-meter span {
  display: block;
  height: 100%;
  background: var(--accent);
}

/* Where a record would be. Bars rather than invented rows of data: the sample
 * this frame is a picture of has real content in it, and typing a second,
 * shorter version of that content here would be two samples disagreeing. */
.frame-bars {
  display: grid;
  gap: 0.45rem;
}

.frame-line {
  height: 0.55rem;
  border-radius: 999px;
  background: var(--surface-2);
}

.frame-line.w40 {
  width: 40%;
}
.frame-line.w50 {
  width: 50%;
}
.frame-line.w55 {
  width: 55%;
}
.frame-line.w60 {
  width: 60%;
}
.frame-line.w65 {
  width: 65%;
}
.frame-line.w70 {
  width: 70%;
}
.frame-line.w80 {
  width: 80%;
}
.frame-line.w85 {
  width: 85%;
}
.frame-line.w90 {
  width: 90%;
}

/* The same footer as the application's, because the application replaces this
   document with itself and the reader is looking at the same page. */
.prerender-footer {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  align-items: baseline;
  justify-content: space-between;
  padding-top: var(--space-5);
  color: var(--muted);
  font-size: var(--text-sm);
}

.prerender-footer p {
  margin: 0;
}

/* The same trade the running stage makes at the same width, and for the same
   reason: on a phone the two planes are carried by the shadow and the scrim
   rather than by their insets, because every rem of inset here comes off a
   picture that is already drawn small. */
@media (max-width: 40rem) {
  .frame-stage {
    padding: 0.5rem;
  }

  .frame-platform {
    padding: 0.6rem 0.6rem 1.6rem;
  }

  .frame-record {
    margin-inline: 0.35rem;
    padding: 0.5rem;
  }
}

@media (min-width: 44rem) {
  /* Two, and the wider one holds the record. The same proportion the running
     workspace uses at the same width, so the still and the live screen are the
     same picture. */
  .frame-grid.workspace {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  }

  .frame-grid.thirds {
    grid-template-columns: repeat(3, 1fr);
  }

  /* And the platform lays its question beside its answer, which is what the
     running selector does at its own breakpoint. It is the same saving in the
     same place: the layer that explains stays a band rather than becoming a
     column, so the record is higher up the picture. */
  .frame-platform {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  }

  .frame-lede,
  .frame-notice {
    grid-column: 1 / -1;
  }
}

/* The sidebar ---------------------------------------------------------------
   The sections, down the left of a wide screen. The header used to carry the
   workspace switch, eleven tabs and the account in one line, which was more
   than a bar can hold. Above 40rem this column takes the workspace switch,
   every section, grouped under quiet headings, and the account. Under 40rem it
   does not exist and the menu carries every section in the same order.

   It is sticky under the header, so the sections stay in reach on a long
   screen, and scrolls on its own if a short window cannot show all of it. */
.app-sidebar {
  display: none;
}

@media (min-width: 40rem) {
  .app-body.with-sidebar {
    display: grid;
    grid-template-columns: 12rem minmax(0, 1fr);
    gap: var(--space-5);
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  /* The screen beside it keeps its own vertical rhythm and gives its width and
     side gutters to the grid, which already has them. */
  .app-body.with-sidebar > .app-main {
    max-width: none;
    margin: 0;
    padding-inline: 0;
    min-width: 0;
  }

  .app-sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    position: sticky;
    top: calc(var(--header-h) + env(safe-area-inset-top));
    align-self: start;
    max-height: calc(100vh - var(--header-h) - env(safe-area-inset-top));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--space-5) 0;
  }

  /* What moved out of the bar. The markup stays where the phone panel needs
     it; on a wide screen with a sidebar the bar simply does not draw it. */
  .app-header[data-sidebar="true"] .header-rest > .workspaces,
  .app-header[data-sidebar="true"] .header-rest > .tabstrip,
  .app-header[data-sidebar="true"] .header-rest > .account {
    display: none;
  }

}

/* The workspace switch, stacked: three seats in a 12rem column would be three
   clipped words side by side. */
.side-workspaces {
  flex-direction: column;
  border-radius: var(--radius);
}

.side-workspaces .workspace {
  width: 100%;
  text-align: left;
  border-radius: var(--radius-sm);
}

.side-nav {
  display: grid;
  gap: var(--space-3);
}

/* One run of sections under one heading. The heading is a label, set small and
   muted, so the column reads as a list with a few signposts rather than as a
   second navigation. A run with no heading after the first is Settings, set
   apart by a rule so the account is never mistaken for part of the work. */
.side-group {
  display: grid;
  gap: 0.15rem;
}

.side-group.apart {
  padding-top: var(--space-3);
  border-top: 1px solid var(--line);
}

.side-heading {
  margin: 0;
  padding: 0 var(--space-3) 0.2rem;
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.side-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  padding: 0.45rem var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 560;
  min-height: 2.5rem;
}

.side-item:hover:not(:disabled):not(.active) {
  background: var(--accent-soft);
  border-color: transparent;
  color: var(--text);
}

.side-item.active {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--text);
}

/* The account at the foot of the column: the name and the way out, one above
   the other, below everything a person came to do. */
.side-account {
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: 1px solid var(--line);
}

.side-account .account {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
}

.side-account .who {
  max-width: 100%;
}

/* Orders, activity and previews -------------------------------------------
 *
 * A form or a reading that opens inside a card, under the line that opened
 * it: the traveller details sent for another purchase, an institution's
 * questions, and the sentence a full pull or a withdrawal is confirmed with.
 * Set in from the card's edge with the same rule a fieldset draws, so it reads
 * as part of the line above it rather than as a card of its own.
 */
.subform {
  margin: 0.75rem 0 0;
  padding: 0 0 0 0.75rem;
  border-left: 2px solid var(--line);
}
