@import url("tokens.css");

/* The candidate apply funnel.
 *
 * Mobile first, because most candidates arriving from a job portal are on a
 * phone. On a phone the frame is the viewport, edge to edge. On anything wider
 * it is one 520px card centred on the page's ground: header, transcript and
 * dock inside a single border, because they are three parts of one object and
 * had been reading as three bands across an empty grey field.
 *
 * It shares the design tokens with the recruiter application and nothing else.
 * A candidate has no nav, no session and no product to learn. */

* { box-sizing: border-box; }

/* The avatar gutter, named once. Everything in the conversation column — the
   bubbles, and now the cards under them — is inset by exactly this much. */
:root {
  --avatar: 26px;
  --avatar-gap: 8px;
}

/* The `hidden` attribute means hidden.
 *
 * Without this, any class that sets `display` outranks the user agent's
 * `[hidden] { display: none }` — which is exactly what happened to the upload
 * fallback: the script set `hidden`, `.actions { display: flex }` won on
 * specificity, and the button the script had just retired stayed on screen. */
[hidden] { display: none !important; }

/* The frame is the viewport.
 *
 * This was an ordinary document: the header scrolled out of view, the
 * transcript ran the whole height of the page, and there was no bounded area
 * anywhere. A chat has a shape — a header that stays, a transcript that
 * scrolls inside it, and the thing being asked docked at the bottom where it
 * cannot be scrolled away from — and none of it was true here.
 *
 * Nothing about this needs JavaScript. With the script blocked the header is
 * still pinned, the stream still scrolls and the dock is still docked; the
 * only thing the script adds is scrolling the stream to the newest message.
 */
html { height: 100%; }

body {
  margin: 0;
  /* dvh follows the phone's URL bar as it collapses; vh does not, and a fixed
     frame measured against the wrong height is how a docked control ends up
     under the browser chrome. The fallback is for browsers without dvh.

     Neither of them follows the *keyboard*: dvh is the layout viewport, which
     Android Chrome shrinks when a keyboard opens and iOS Safari does not. The
     script measures `visualViewport` and adds `.vv` below; these two lines are
     what a browser without it, and the moment before the script runs, get. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* The frame is centred in here. On a phone it fills the box and none of this
     ground is ever seen; on a wide screen the padding below is the margin. */
  align-items: center;
  background: var(--ground);
  color: var(--ink);
  font: 400 16px/1.6 var(--body);
  -webkit-text-size-adjust: 100%;
}

/* The frame, measured against what is actually on screen.
 *
 * `.vv` is set by the script once it has a `visualViewport` to read, so
 * everything here is an upgrade on the `dvh` frame above rather than a
 * replacement for it. `position: fixed` with `--frame-top` is for iOS, which
 * scrolls the layout viewport to bring a focused field into view: without it
 * the frame stays where the document is and the dock walks off the bottom.
 */
body.vv {
  position: fixed;
  top: var(--frame-top, 0px);
  left: 0;
  right: 0;
  height: var(--frame-h);
}

/* One frame.
 *
 * The header, the transcript and the dock used to be three separate bands: a
 * full-width header rule, a 520px column of transcript, and a dock whose only
 * separation from it was a hairline — all of it floating in a grey field that
 * ran to the edges of the window. Nothing said the three were one thing.
 *
 * So: one box, one border, one radius, and the ground showing outside it as
 * margin. On a phone there is no margin to show, and the frame simply is the
 * viewport — see the media query at the foot of this section. */
.frame {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  /* Wide enough for a six-option question to be two rows of chips. At 520px
     it was three rows of two, and the dock took half of a laptop screen. */
  max-width: 720px;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  /* So the radius clips the header's top corners and the dock's bottom ones,
     and nothing inside can paint over the border. */
  overflow: hidden;
}

/* Wide enough for the column to be narrower than the window. Below this the
   frame is the viewport: no margin, no border, no radius, nothing to show them
   against. Everything else about it — the dock surface, the bubble fills — is
   the same at every width. */
@media (min-width: 560px) {
  /* The viewport's height, less a small margin: 24px top and bottom was 48px
     taken from the conversation to frame it. */
  body { padding: 12px 20px; }
  .frame {
    border: 1px solid var(--rule);
    border-radius: 14px;
  }
}

/* A third of the frame while there is room, and as much as it needs when a
   keyboard has taken two thirds of the screen. See `syncFrame`. */
body.vv .controls { max-height: var(--dock-max); }

/* A turn that will not fit the budget takes what it needs rather than losing
   its buttons off the bottom. `capDock` sets this and logs which turn it was. */
.controls.over-cap,
body.vv .controls.over-cap { max-height: none; }

.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: 8px; top: 8px; z-index: 20;
  background: var(--paper); padding: 8px 12px; border: 1px solid var(--accent);
}

/* --- header ------------------------------------------------------------- */

/* Pinned by being a flex item that does not grow, rather than by `sticky`:
   inside a frame that does not scroll there is nothing for sticky to stick to. */
.topbar {
  flex: none;
  z-index: 10;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}

/* No max-width of its own any more: the frame is 520px and this is the top of
   it. It used to centre itself inside a full-width bar. */
.topbar-inner {
  padding: 10px 20px;
  display: flex;
  align-items: center;
  gap: 12px;
}

/* The hiring company's initial. A candidate recognises who they are applying
   to before they read a word, which is the one thing this bar is for. */
.mark {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  font: 700 16px/36px var(--display);
  text-align: center;
  letter-spacing: 0;
}

.topbar-text { min-width: 0; flex: 1; }
/* The company and the role on one line. The role gives way first — it is
   the long one, "Sr. Staff Backend Engineer - Python …", and its full title is
   on hover — and the company only when there is nearly nothing left. */
.topbar .headline {
  display: flex;
  min-width: 0;
  margin: 0;
  font: 600 15px/1.3 var(--display);
  white-space: nowrap;
}
.topbar .org { flex: 0 0 auto; max-width: 60%; overflow: hidden; text-overflow: ellipsis; }
.topbar .sep { flex: none; white-space: pre; color: var(--muted); }
.topbar .role {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 500;
  color: var(--muted);
}

/* Sentence case, muted, small. It was the loudest element above the fold in
   full capitals, which is a strange thing to shout at a job applicant. */
.topbar .bot {
  flex: none;
  font-size: 12px;
  color: var(--muted);
}
/* Never hidden: it is the AI disclosure. Narrow screens move it under the
   company's name instead (see "the header at every width"). */

/* A courtesy, and it looks like one: small, quiet, and hidden entirely until
   the script that makes it work is running. "Listen" in words until it has
   been used once; the speaker alone after that. */
.listen {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 30px;
  min-width: 30px;
  border-radius: 999px;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--muted);
  cursor: pointer;
  padding: 0 10px 0 8px;
  font: inherit;
  font-size: 12.5px;
}
.listen.is-used { padding: 0; justify-content: center; }
.listen.is-on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.listen-icon {
  width: 16px;
  height: 16px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.listen-icon .listen-body { fill: currentColor; stroke: none; }
.listen .listen-waves { opacity: 0.35; transition: opacity 0.12s ease; }
.listen.is-on .listen-waves { opacity: 1; }
/* On, and the browser is still waiting for a tap before it will speak. */
.listen.is-on.is-waiting .listen-waves { opacity: 0.35; }

/* The arrival sound's own mute. A courtesy, and it looks like one: small,
   quiet, and hidden entirely until the script that makes it work is running. */
.sound {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.sound.is-on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* The icon is one drawing in three states: the slash is hidden when sound is
   on, the waves when it is off, and while the browser is still refusing to
   start audio the waves go faint rather than the whole thing going dead. */
.sound-icon {
  width: 16px;
  height: 16px;
  display: block;
  margin: auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sound-icon .sound-body { fill: currentColor; stroke: none; }
.sound .sound-waves,
.sound .sound-slash { transition: opacity 0.12s ease; }
.sound .sound-waves { opacity: 0; }
.sound.is-on .sound-waves { opacity: 1; }
.sound.is-on .sound-slash { opacity: 0; }
.sound.is-on.is-waiting .sound-waves { opacity: 0.35; }

/* --- shell -------------------------------------------------------------- */

.shell {
  /* Everything below the header, in two rows. The width is the frame's. */
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 0;
}

/* The transcript, and under it the questions and the footer. They sit inside
   the scroll rather than under the dock, so they stay reachable and never take
   space from the conversation — and `#latest` is above them, so "scroll to the
   newest message" does not mean "scroll to the legal line". */
.stream {
  /* Positioned, so a message's offsetTop is measured from here: the script
     places replies by layout, which a reveal's transform does not disturb. */
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  background: var(--paper);
  padding: 20px 20px 8px;
}

/* Docked, and controls only.
 *
 * It used to scroll inside itself, which made two scroll regions on one screen
 * — and worse, it held things that were read rather than answered, so a single
 * thought was split across both and the candidate had to work the two of them
 * to follow it. Everything read is a card in the stream now, and what is left
 * here is inputs, chips, buttons and their labels.
 *
 * So: height by content, no scrollbar, and a cap low enough that the
 * conversation is always the larger half of the screen. A turn whose controls
 * do not fit under the cap is a turn to redesign, not a reason to put the
 * scrollbar back. */
.controls {
  flex: none;
  height: auto;
  max-height: 35vh;
  max-height: 35dvh;
  overflow: hidden;
  /* Half a step off the transcript, and the rule stays. A hairline on its own
     was the only thing separating the thing being asked from the conversation
     about it, which is not enough to carry the difference between the two. */
  background: var(--paper-alt);
  border-top: 1px solid var(--rule);
  /* The home indicator on a phone sits over the last few pixels. */
  padding: 12px 20px calc(12px + env(safe-area-inset-bottom));
}

/* --- the conversation --------------------------------------------------- */

.thread { display: flex; flex-direction: column; gap: 4px; }

.msg { display: flex; align-items: flex-end; gap: var(--avatar-gap); max-width: 100%; }
.msg + .msg.starts-run { margin-top: 12px; }

.avatar {
  flex: none;
  width: var(--avatar);
  height: var(--avatar);
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font: 700 12px/26px var(--display);
  text-align: center;
}
/* Only the first bubble of a run is announced; the rest keep the indent. */
.msg.ria:not(.starts-run) .avatar { background: transparent; }

.bubble {
  max-width: 70%;
  padding: 10px 14px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 15.5px;
}

/* Asymmetric, with the corner nearest the speaker squared off into a tail.
 *
 * A fill and no border. These were white on near-white with a hairline round
 * them, sitting on a surface of very nearly the same colour, so a bubble read
 * as a drawing of a bubble rather than a thing on a page. The candidate's own
 * bubbles have always been a solid fill; Ria's are the quiet version of the
 * same idea.
 *
 * The 1px is still declared, in the fill's own colour, so that a line from Ria
 * and a line from the candidate — whose bubble does carry an accent border —
 * are the same height. */
.msg.ria .bubble {
  background: var(--bubble);
  border: 1px solid var(--bubble);
  border-radius: 16px 16px 16px 4px;
}
.msg.ria:not(.starts-run) .bubble { border-radius: 16px; }

/* `justify-content` and nothing else. `margin-left: auto` here shrank the row
   to its content, and the bubble's `max-width: 70%` was then 70% of that —
   which turned a filename into two letters and a line break. */
.msg.candidate { justify-content: flex-end; }
.msg.candidate .bubble {
  background: var(--accent);
  color: var(--accent-ink);
  border: 1px solid var(--accent);
  border-radius: 16px 16px 4px 16px;
}

.bubble.flash {
  background: var(--warn-soft);
  border-color: var(--warn);
  color: var(--ink);
}

/* Hidden only once the script has taken responsibility for showing them. */
.revealing .thread [data-reveal] { opacity: 0; transform: translateY(6px); }
.thread [data-reveal].shown {
  opacity: 1;
  transform: none;
  transition: opacity 180ms ease-out, transform 180ms ease-out;
}

.typing .bubble { display: flex; gap: 4px; align-items: center; padding: 14px; }
.typing i {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--muted); opacity: 0.5;
  animation: blip 1s infinite ease-in-out;
}
.typing i:nth-child(2) { animation-delay: 0.15s; }
.typing i:nth-child(3) { animation-delay: 0.3s; }
@keyframes blip {
  0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
  30% { opacity: 0.9; transform: translateY(-3px); }
}
@media (prefers-reduced-motion: reduce) {
  .typing i { animation: none; }
  .thread [data-reveal].shown { transition: none; }
}

.anchor { display: block; height: 1px; scroll-margin-bottom: 24px; }

/* "↓ More below": a long reply is shown from its first line, and this sits
   just above the dock until its last line has been scrolled into view. It is
   positioned by the script against the dock's top edge, because the dock's
   height changes from turn to turn. */
.more-below {
  position: absolute;
  left: 50%;
  bottom: calc(var(--dock-h, 80px) + 10px);
  transform: translateX(-50%);
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 34px;
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--paper);
  color: var(--accent);
  font: 600 13.5px/1 var(--body);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
  cursor: pointer;
}
.more-below:hover { background: var(--accent-soft); }

/* --- the controls ------------------------------------------------------- */

/* The cards sit in the same column the bubbles do.
   They used to start at the edge of the shell while every bubble started one
   avatar further in, so the upload card and the read-back card were each 34px
   wider than the widest thing said above them, and the "Attach my resume"
   button ran the full width of that. One left edge for everything anybody
   says or answers; the avatar gutter stays empty beside the cards, which is
   what it is for. */
/* Inset to the bubble column, as before: one left edge for everything anybody
   says or answers. The margin is on the inner cards now, because the dock
   itself is the full width of the frame and carries the rule across it. */
.controls > * { margin-left: calc(var(--avatar) + var(--avatar-gap)); }
/* On a phone the inset is 34px of a 350px row of chips, which is the
   difference between three rows and four. The dock takes its full width. */
@media (max-width: 460px) {
  .controls > * { margin-left: 0; }
  .controls { padding-left: 14px; padding-right: 14px; }
  .choices label, .choices .chip { padding-left: 12px; padding-right: 12px; }
}
/* Held back until the messages that introduce them have appeared. */
.revealing .controls { opacity: 0; }
.controls.ready { opacity: 1; transition: opacity 200ms ease-out; }

/* No chrome of its own.
 *
 * This was a paper card with a rule round it, sitting on the dock's surface —
 * and it covered nearly all of that surface, which is why the half-step read
 * as a rumour in the light theme and plainly in the dark one. A card on a
 * surface is right when there is a surface left to see.
 *
 * The dock *is* the panel now: it has the top rule, the surface and the
 * padding, and this groups a turn's controls without drawing a second box
 * round them. The controls keep their own borders — an input, a chip and a
 * button are things you touch, and a thing you touch says where its edges
 * are. What goes is the box round the group, not the boxes round the parts.
 *
 * It is worth 34px on every turn, which on a 640px phone is not nothing. */
.panel {
  background: transparent;
  border: 0;
  padding: 0;
}
/* Two groups in one dock — the code and "Didn't get it?" — were told apart by
   their two borders. A rule does the same job without the boxes. */
.panel + .panel {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
}

/* One statement per thing: the card carries controls, and the sentence that
   introduces them is a message above it. What is left is the odd note that
   belongs beside a field rather than in the conversation. */
.card-note { margin: 0 0 12px; color: var(--muted); font-size: 14px; }
.panel .lede { margin: 0 0 12px; color: var(--muted); font-size: 15px; }
.hint { margin: 8px 0 0; color: var(--muted); font-size: 13.5px; }
.note { margin: 6px 0 0; color: var(--muted); font-size: 14px; }

.field { margin-bottom: 12px; }
.field:last-child { margin-bottom: 0; }
.field label { display: block; font-weight: 600; font-size: 14px; margin-bottom: 5px; }

input[type="text"],
input[type="email"],
input[type="tel"],
select,
textarea {
  width: 100%;
  font: inherit;
  /* 16px or larger, or iOS Safari zooms the page on focus and the candidate
     spends the rest of the funnel scrolled sideways. */
  font-size: 16px;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 11px 12px;
}

textarea { min-height: 80px; resize: vertical; }

input:focus-visible, select:focus-visible, textarea:focus-visible,
button:focus-visible, a:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
/* The last step: start now, or later, side by side and with no rule between
   them. The dock holds nothing else on this turn, so the row is the dock. */
.actions.final-row { align-items: center; margin-top: 0; gap: 6px 10px; }
.actions.final-row .later { margin: 0; }
.actions.final-row .btn.quiet { padding-left: 10px; padding-right: 10px; font-size: 15px; line-height: 1.3; }

/* The read-back's two answers.
   "That's all correct" was a filled button and "Something's not right" was an
   outline beside it, so the page had a preference about which one was true.
   It must not: a candidate whose years were read wrong is the whole reason
   the screen exists. Equal width, equal height, equal border — the fill is
   the only difference left, and it marks which one continues rather than
   which one is wanted. */
.actions.paired {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
/* Equal means equal.
   The first attempt at this kept the fill on "That's all correct" and gave
   "Something's not right" a tinted background, which is still a promoted
   answer and a demoted one — and the candidate this screen exists for is the
   one whose years were read wrong. Neither is filled now: same border, same
   background, same width, same weight. The order is the only thing left
   distinguishing them, and order is not an argument. */
.actions.paired .btn,
.actions.paired .btn.secondary {
  width: 100%;
  padding-left: 12px;
  padding-right: 12px;
  background: var(--paper);
  border-color: var(--accent);
  color: var(--accent);
}
.actions.paired .btn:hover,
.actions.paired .btn:focus-visible { background: var(--accent-soft); }
@media (max-width: 420px) {
  .actions.paired { grid-template-columns: 1fr; }
}
/* The way out, as a footer action of the dock.
 *
 * "No thanks, I've already applied" used to be the last thing on the page,
 * below the dock, on the page's ground — a control belonging to this turn,
 * floating underneath the panel that held the rest of the turn. It is inside
 * the frame and inside the dock now, under a rule that says it is the footer
 * of what is being asked rather than a second question. */
.exit {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
  text-align: center;
}

.btn {
  font: 600 16px/1 var(--body);
  padding: 13px 20px;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  text-align: center;
}
.btn.secondary { background: transparent; color: var(--accent); }
.btn.quiet { background: transparent; border-color: transparent; color: var(--muted); font-weight: 400; }
.btn.small { padding: 9px 14px; font-size: 14px; }
.btn:disabled { opacity: 0.5; cursor: default; }

/* The file input is the button. Choosing a file submits; see apply.js. It
   shares a row with the way out, and takes the whole row on a phone. */
.attach { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin-top: 12px; }
.attach .file-btn { flex: 1 1 220px; max-width: 320px; }
.attach .btn.quiet { padding-left: 10px; padding-right: 10px; font-size: 15px; }
[data-upload] > .hint { margin-top: 6px; font-size: 12.5px; }
.file-btn { position: relative; overflow: hidden; display: block; }
.file-btn input[type="file"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer;
}
.uploading .file-btn { opacity: 0.55; pointer-events: none; }

/* From the moment a file is chosen until the next page arrives. The step is a
   form post, so without this the candidate has nothing at all to look at while
   a resume is uploaded and read. */
.working {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  color: var(--muted);
  font-size: 14.5px;
}
.spinner {
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--rule);
  border-top-color: var(--accent);
  animation: spin 700ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2s; }
}

/* The file could not be read. Said where the picker is, not somewhere else. */
.upload-problem {
  margin: 0 0 12px;
  padding: 10px 12px;
  background: var(--warn-soft);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  font-size: 15px;
}

/* --- the read-back ------------------------------------------------------ */

/* Who the reading says they are. Three lines, read rather than typed: the
   contact used to be three labelled inputs in the dock, which made confirming
   the read-back and editing the contact the same act. */
/* A narrower label column than the receipt's, because these three labels are
   one word and the value is an email address. At 34% "priya.sharma@example.com"
   broke across two lines in the middle of "example.com", which is a poor thing
   to show somebody who is checking whether it is right. */
.contact-lines { margin-bottom: 4px; }
/* `ul.` for the specificity, not for the markup: `.receipt-list li` sets the
   same property further down the file, and two rules of equal weight are
   settled by which one is last. */
ul.contact-lines li { grid-template-columns: minmax(58px, 26%) 1fr auto; align-items: baseline; }
/* The email is the OTP target and the link destination. It gets the weight
   because getting it wrong is the one mistake here that cannot be undone. */
.contact-lines .rl-value.primary { font-weight: 600; }

/* A number of years is four characters at the outside. It was the same
   full-width box every sentence on this page uses, which says nothing about
   what goes in it. */
input.years-input {
  font: 600 20px/1.2 var(--body);
  text-align: center;
  width: 6em;
  max-width: 100%;
  padding: 12px;
}

/* One line per skill: the name on the left, the years and where they came
   from on the right. */
.skills { list-style: none; margin: 10px 0 0; padding: 0; }
.skills li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px 12px;
  flex-wrap: wrap;
  padding: 7px 0;
  border-top: 1px solid var(--rule);
  font-size: 15px;
}

/* A gating skill decides the outcome and a context skill does not, so they
   must not look identical. The threshold is still nowhere on this page, and
   neither is which of them is weak. */
.skills li.gating .name { font-weight: 600; }
.skills .val { display: inline-flex; align-items: baseline; gap: 8px; margin-left: auto; }
.skills .years { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The passage a skill was read from: the row's title on hover, and on a tap
   (a phone has no hover) or Enter, a line of its own under the row. */
.skills li[data-evidence-row] { cursor: pointer; }
.skills li[data-evidence-row]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.skills .evidence {
  flex-basis: 100%;
  font-size: 13px;
  font-style: italic;
  color: var(--muted);
}

/* "corrected", "from your resume": small, beside the value it describes. */
.tag {
  font-size: 12px;
  font-weight: 400;
  color: var(--muted);
  white-space: nowrap;
}
.tag.is-corrected { color: var(--accent); }

/* The question the dock's two buttons answer, as the card's last line. */
.card-ask {
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
  font-weight: 600;
}
.card-note.small { font-size: 12.5px; margin: 4px 0 0; }
/* "found, no dates" and "not found" are sentences, not durations: they are set
   in the running weight and muted, so a column of years stays a column of
   years. A bare em dash used to stand for both of them. */
.skills .years.said,
.recap-list .years.said {
  font-weight: 400;
  color: var(--muted);
  font-size: 13.5px;
  white-space: normal;
}

/* Not a sentence trailing off the end of the list: these are not gated on,
   and nothing about them should invite a correction that changes nothing. */
.also {
  margin: 12px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
  color: var(--muted);
  font-size: 14px;
}

/* --- the correction screen ---------------------------------------------- */

.recap-list { list-style: none; margin: 0; padding: 0; font-size: 14.5px; }
/* Ruled like the read-back's own list. Two rows 5px apart with no line
   between them read as one wrapped row, which is a poor thing to hand
   somebody who is about to tell you which row is wrong. */
.recap-list li {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 9px 0;
  border-top: 1px solid var(--rule);
}
.recap-list li:first-child { border-top: 0; padding-top: 2px; }
.recap-list .years { color: var(--muted); font-variant-numeric: tabular-nums; }
/* "you corrected this", on a compact row: the same words the full card and
   the receipt use, set small so the value stays the thing you read. */
.recap-mark { font-size: 12.5px; color: var(--accent); white-space: nowrap; }

/* A chip for something already put right carries its new value under the
   name, so the picker says what has been done as well as what is left. */
.choices .chip.is-corrected { flex-direction: column; gap: 1px; border-color: var(--accent); }
.chip-value { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* --- choices ------------------------------------------------------------ */

/* Two columns, always.
 *
 * It was `auto-fit, minmax(152px, 1fr)`, which on a 360px phone had room for
 * exactly one track — so every chip question was a single tall column and the
 * salary bands alone took 487px of a 640px screen. `auto-fit` decides how many
 * columns fit and then the chips wrap inside them; two fixed tracks decide it
 * the other way round, which is the right way round: the grid is the shape of
 * the question, and a long option is a chip that takes two lines rather than a
 * reason to restack the whole set.
 *
 * 46px minimum, because a chip is a touch target before it is a word. */
/* Flowing chips, each as wide as its words.
 *
 * Two fixed columns of 46px chips, a long option wrapping to two lines inside
 * its track, made "What's making you look?" three rows of 68px — most of a
 * 450px dock. Chips that wrap in a row take the width they need, so the same
 * six options are two rows on a desktop. */
.choices {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* A chip is a label wrapping a radio, or — on the correction picker — a plain
   link. The question there is "which one", so tapping the answer is the whole
   interaction and there is nothing to submit; the two must look identical, or
   the same shape means two things on two screens. */
.choices label,
.choices .chip {
  display: flex;
  align-items: center;
  justify-content: center;
  /* A touch target before it is a word: 40px is the floor. */
  min-height: 40px;
  text-align: center;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 7px 14px;
  cursor: pointer;
  font-size: 15px;
  line-height: 1.3;
  background: var(--paper);
}
.choices .chip { color: inherit; text-decoration: none; }
.choices .chip:hover { border-color: var(--accent); background: var(--accent-soft); }
.choices input { position: absolute; opacity: 0; width: 0; height: 0; }
.choices label:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}
.choices label:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

.consent { display: flex; gap: 10px; align-items: flex-start; font-size: 14.5px; line-height: 1.45; }
.consent input { margin-top: 2px; flex: none; width: 18px; height: 18px; }
/* The optional talent-database box sits under the required consent. */
.consent + .consent { margin-top: 8px; }
.consent .hint { display: block; margin-top: 1px; font-size: 12.5px; }

/* --- the code ----------------------------------------------------------- */

/* Six characters wide, because it takes six characters. It was a full-width
   box — the same box the four questions before it used for sentences — with
   its label hidden, so nothing about it said what went in it. The trailing
   letter-space is padding on the right only, or the text sits off-centre. */
/* The selector is "input.code-input" rather than ".code-input", because the
   base rule for a text field is an attribute selector and that outranks a
   bare class. This block had been in the file since the screen was written
   and none of it had ever applied: the code box was the same full-width 16px
   box as every question before it. */
input.code-input {
  font: 700 24px/1.2 var(--mono);
  letter-spacing: 0.4em;
  text-align: center;
  padding: 8px 6px 8px 18px;
  width: 7.5em;
  max-width: 100%;
}
.code-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.code-row .btn { min-height: 44px; }
.inline-form { display: inline-block; margin-bottom: 10px; }
.resend > summary {
  cursor: pointer;
  font-size: 14px;
  color: var(--accent);
  min-height: 40px;
  display: flex;
  align-items: center;
}
/* Opened, it takes the line under the count rather than squeezing beside it. */
.dock-foot.otp-foot { margin-top: 4px; }
.otp-foot .resend { margin-left: auto; }
.otp-foot .resend[open] { flex-basis: 100%; margin-left: 0; }
.otp-foot .resend[open] > summary { margin-bottom: 8px; }

/* --- the end of the conversation ---------------------------------------- */

/* Four short lines and no controls. It is the last thing on the page and the
   only thing in the dock, so it is allowed to breathe. */
.closing { text-align: center; }
.closing-line { margin: 0 0 6px; }
.closing-line:last-child { margin-bottom: 0; color: var(--muted); }

/* --- the dock's footer line ---------------------------------------------- */

/* Under the answer, one line: the count and the optional note on the left,
   Skip then Next on the right. It wraps — the right half drops under the left
   — only when a narrow screen has no room for both. */
.dock-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  margin-top: 12px;
}
.dock-meta { display: flex; align-items: center; gap: 4px 14px; flex-wrap: wrap; min-width: 0; }
.dock-meta .progress { margin: 0; }
.dock-go { display: flex; align-items: center; gap: 6px; margin-left: auto; }
/* Next is first in the markup, so Enter in a text field presses it; Skip is
   drawn first, so the way forward is on the outside. */
.dock-go .btn.quiet { order: -1; }
.dock-go .btn { padding: 11px 22px; min-height: 40px; font-size: 15px; }
.dock-go .btn.quiet { padding-left: 12px; padding-right: 12px; }

/* The free-text half of a chip question, folded away. The default shape of a
   turn is chips and Next; most people have nothing to add, and the ones who do
   go looking. The plus is in the copy — `copy.ts` owns every word a candidate
   reads. A button, not a disclosure: the field it opens goes under the whole
   line, not inside it. */
.add-note {
  font: inherit;
  font-size: 14px;
  color: var(--accent);
  background: none;
  border: 0;
  border-radius: 6px;
  padding: 0 4px;
  margin-left: -4px;
  min-height: 40px;
  cursor: pointer;
}
.note-field { margin: 10px 0 0; }

/* --- cards in the conversation ------------------------------------------ */

/* A card is a turn of Ria's that happens to be a table rather than a sentence,
   so it keeps her avatar and her left edge and simply runs wider than a
   bubble does. */
.card-turn { align-items: flex-start; }
/* Filled like a bubble, and for the same reason: the transcript is paper, so
   a paper card with a hairline round it is a box drawn on the surface rather
   than an object resting on it. The rules *inside* a card — between skills,
   between receipt rows — still do their job on the fill. */
.card-turn .card {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--bubble);
  border-radius: 12px;
  padding: 14px 16px;
}
.card-turn .card > :first-child { margin-top: 0; }
.card-turn .card > :last-child { margin-bottom: 0; }
/* No longer a shade of its own: every card in the transcript is one surface,
   and this one is distinguished by being the read-back said again, not by
   being a different grey. */

/* --- the closing receipt ------------------------------------------------ */

/* What was collected, and from where. A label, the value, and — on the lines
   that have one — where it came from, on its own row underneath so a long
   value and a long provenance never fight over the same line. */
.receipt-group {
  margin: 16px 0 0;
  font: 600 12px/1 var(--body);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.receipt-group:first-of-type { margin-top: 10px; }

.receipt-list { list-style: none; margin: 6px 0 0; padding: 0; }
.receipt-list li {
  display: grid;
  grid-template-columns: minmax(96px, 34%) 1fr;
  gap: 2px 12px;
  padding: 8px 0;
  border-top: 1px solid var(--rule);
  font-size: 14.5px;
}
.receipt-list li:first-child { border-top: 0; }
.rl-label { color: var(--muted); }
.rl-value { overflow-wrap: anywhere; }
/* Under the value, in the value's column: it describes the value, not the row. */
.rl-source {
  grid-column: 2;
  font-size: 12.5px;
  color: var(--muted);
}

/* --- requirements ------------------------------------------------------- */

.requirements { margin: 10px 0 0; padding-left: 20px; }
.requirements li { margin: 5px 0; }

/* --- the questions list ------------------------------------------------- */

/* The recruiter's own questions, tapped open. There is no free-text box and no
   matching, because matching answered the wrong question in their name.
 *
 * It is read-content, so it is a card in the transcript like every other piece
 * of read-content — at the bottom of the scroll region, below `#latest`, inset
 * to the same left edge as the bubbles and the cards. It used to be a bare
 * section under a hairline, sitting in the gap between the transcript and the
 * dock and reading as a third panel that belonged to neither. */
.questions {
  margin: 28px 0 0 calc(var(--avatar) + var(--avatar-gap));
  padding: 14px 16px;
  background: var(--bubble);
  border-radius: 12px;
}
.questions h2 {
  font: 600 13px/1.3 var(--body);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin: 0 0 8px;
}

.qa { border-bottom: 1px solid var(--rule); }
.qa:last-child { border-bottom: 0; }
.qa > summary {
  cursor: pointer;
  padding: 11px 0;
  font-size: 15px;
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
}
.qa > summary::-webkit-details-marker { display: none; }
.qa > summary::after {
  content: "+";
  color: var(--muted);
  font: 400 18px/1 var(--body);
  flex: none;
}
.qa[open] > summary::after { content: "–"; }

.qa-answer { padding: 0 0 14px; }
.qa-answer p { margin: 0 0 6px; font-size: 15px; }
/* An attribution line, not a message of its own. The answer is the company's
   words and says so quietly, rather than arriving in a bubble from Ria. */
.qa-by { margin: 0; color: var(--muted); font-size: 12.5px; }

/* The line that goes with the card above it, on the card's left edge. */
.support {
  margin: 14px 0 0 calc(var(--avatar) + var(--avatar-gap));
  color: var(--muted);
  font-size: 14px;
}

/* --- refusal page ------------------------------------------------------- */

.refused { max-width: 480px; margin: 0 auto; padding: 56px 20px; }
.refused h1 { font: 700 24px/1.25 var(--display); margin: 0 0 12px; }
.refused p { color: var(--muted); }

/* --- footer ------------------------------------------------------------- */

.legal {
  margin-top: 32px;
  padding-top: 16px;
  border-top: 1px solid var(--rule);
  color: var(--muted);
  font-size: 13px;
}
.legal p { margin: 0 0 4px; }
.legal a { color: var(--accent); }

/* A label a screen reader announces and a sighted user does not need, because
   the message above the field already says what it is. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}

/* "Question 3 of 9": where the candidate is, above the controls that answer
   it. Small and muted — a count, not a heading. */
.progress {
  margin: 0 0 8px;
  font-size: 12.5px;
  color: var(--muted);
  letter-spacing: 0.01em;
}

/* --- "About this role" --------------------------------------------------- */

/* A header control, reachable on every turn. The panel drops over the top of
   the conversation rather than pushing it down, and scrolls inside itself if
   the recruiter wrote a lot. */
.topbar { position: relative; }
.about { flex: none; }
.about > summary {
  list-style: none;
  cursor: pointer;
  font-size: 12.5px;
  color: var(--accent);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 4px 10px;
  white-space: nowrap;
}
.about > summary::-webkit-details-marker { display: none; }
.about[open] > summary { border-color: var(--accent); background: var(--accent-soft); }
.about-panel {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  max-height: 60vh;
  overflow-y: auto;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  box-shadow: 0 8px 18px rgba(0, 0, 0, 0.08);
  padding: 14px 20px 16px;
}
.about-text { margin: 0 0 12px; font-size: 15px; white-space: pre-line; }
.about-support { margin: 10px 0 0; font-size: 13px; color: var(--muted); }

/* The optional note grows with what is typed rather than scrolling inside
   itself: a box that scrolls is a second scroll region in a frame that has
   one. `field-sizing` where the browser has it, the script where it does not. */
textarea[data-autogrow] {
  field-sizing: content;
  min-height: 4.6em;
  overflow-y: hidden;
  resize: none;
}

/* --- scrollbars ---------------------------------------------------------- */

/* Thin, inside the frame, in the page's own rule and muted colours so they
   follow the theme. The browser default was a full-width grey gutter down the
   side of a 520px card. */
.stream,
.about-panel {
  scrollbar-width: thin;
  scrollbar-color: var(--muted) transparent;
}
.stream::-webkit-scrollbar,
.about-panel::-webkit-scrollbar { width: 6px; }
.stream::-webkit-scrollbar-track,
.about-panel::-webkit-scrollbar-track { background: transparent; }
.stream::-webkit-scrollbar-thumb,
.about-panel::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 3px; }
.stream::-webkit-scrollbar-thumb:hover,
.about-panel::-webkit-scrollbar-thumb:hover { background: var(--muted); }

/* --- the header at every width ------------------------------------------ */

/* The company, the disclosure under it and the role on the left; the three
   controls on the right, none of which may shrink or wrap its own label. The
   disclosure sat beside the name once, and in a 520px frame that cut the
   company's name to "Tricon Inf…". */
.topbar-text .bot { display: block; margin: 0; white-space: nowrap; }
.topbar-actions { flex: none; display: flex; align-items: center; gap: 8px; }
.topbar-actions > * { flex: none; }
.about > summary, .listen { white-space: nowrap; }

/* Narrow: the disclosure goes under the company's name, and the controls take
   a row of their own, right-aligned, at full size. At 360px the five things
   did not fit one row, and what gave way was the disclosure and the buttons. */
@media (max-width: 460px) {
  .topbar-inner { flex-wrap: wrap; row-gap: 6px; padding: 8px 14px; gap: 10px; }
  .topbar-text { flex: 1 1 0; }
  .topbar-actions { flex: 1 0 100%; justify-content: flex-end; }
}
/* A control the script has not switched on stays hidden, whatever display its
   class gives it. */
.listen[hidden], .sound[hidden] { display: none; }
