/* Attendee micro-frontend.
 *
 * Phone-first: the audience holds phones, so 390px is the design width and the desktop rendering is
 * the same column, centred. There is no second layout.
 *
 * The design source is a Figma export of a 1400px desktop screen wearing the Rails admin's chrome;
 * the tokens below are lifted from it, the layout is not. See the run's 02-ui-plan.md.
 */

/* Source Sans 3 is the current release of the design's "Source Sans Pro" — same typeface, renamed
 * upstream. One variable file covers the 400 and 600 the design uses.
 *
 * Self-hosted on purpose: a webfont CDN in front of six hundred phones on conference wifi is a
 * single point of failure, and swap means the system stack paints immediately either way. */
@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/source-sans-3-variable.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-display: swap;
}

/* New Order, the design's heading face, copied from the tms-hub admin so both apps set a heading in
 * the same type. Two weights of the five tms-hub carries: 400 and 600 are what this design uses, and
 * an unused weight is a file in the image nothing ever asks for. */
@font-face {
  font-family: "New Order";
  src: url("fonts/NewOrder-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "New Order";
  src: url("fonts/NewOrder-SemiBold.ttf") format("truetype");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

:root {
  --purple-700: #453fad;
  --purple-400: #9590f7;
  --purple-100: #ceccfb;
  --purple-050: #efeefe;
  --purple-900: #353186;

  --ink-900: #171717;
  --ink-700: #404040;
  --ink-500: #737373;

  /* The design's heading/label ink — a navy, not the near-black the body copy uses. */
  --navy-900: #1a1c43;

  /* Two greys a shade apart, and the design puts them side by side: --grey-025 fills the consult
   * surface, --grey-050 fills the step pills sitting just above it. Neither can stand in for the
   * other without the pills disappearing into the panel. */
  --grey-025: #fafafa;
  --grey-050: #f5f5f5;
  --grey-200: #e5e5e5;
  --grey-400: #a3a3a3;

  /* Read from the screenshots — the CSS export covered only the breadcrumb, chips and DDx card. */
  --amber-050: #fef7e0;
  --amber-400: #e9a23b;
  /* The low-time palette, stated by the design. --amber-900 is sampled from its countdown glyph. */
  --amber-100: #fef3c7;
  --amber-200: #fde3a0;
  --amber-500: #f59e0b;
  --amber-900: #8a5a05;
  /* The ring's spent arc and the selected option's edge, both stated by the design. */
  --purple-600: #6259fe;
  --purple-025: #f6f6ff;
  /* The option badge's letter. */
  --indigo-900: #292566;
  /* The exceptions: the poll designs state these two outright, so they are the design's values
   * rather than a reading of a screenshot. They dress the time-is-up surface. */
  --peach-050: #fff4eb;
  --peach-400: #ff8e32;
  /* Sampled from the time-is-up design: the headline is a deep brown, not the border's orange. */
  --peach-900: #653e1d;
  --green-050: #eaf7ec;
  --green-600: #2e9e4f;
  /* The correct-answer row, stated by the design: fill, edge, glyph and ink. --mint-500 is the
   * filled CheckCircle inside the row, a shade brighter than the row's own edge. */
  --mint-050: #d1fae5;
  /* The toast's Info disc, stated by its design. A rung brighter than --mint-500, which is why it
   * cannot reuse it: side by side the darker green reads as a different, duller mark. */
  --mint-400: #10b981;
  --mint-500: #0ea674;
  --mint-600: #0c9267;
  --mint-900: #054634;
  /* Red is for genuine failure only — an error toast, an over-limit counter. The missed-answer
   * screens are amber: a wrong pick is a near miss, not a fault, so the tint that dressed them went
   * with them. */
  --red-600: #d64545;

  /* The ddx result's missed row, stated by its design: fill, edge, glyph and tally. It is the one
   * place a miss is drawn in red rather than the amber above, and the split is deliberate — the ddx
   * result card was designed on its own and asks for red, while the poll's own result keeps the
   * amber it was designed with. Two tokens rather than reusing --red-600 because the design states
   * a different red, and because --red-600 means "something went wrong", which a wrong diagnosis
   * did not. */
  --rose-050: #fff1f1;
  --rose-500: #ef4444;

  --radius-pill: 9999px;
  --radius-panel: 24px;
  --radius-card: 16px;
  --radius-surface: 24px;
  --radius-input: 8px;

  /* Both shadows carry `inset` explicitly: appending the keyword to the token at the use site would
   * attach it to the last entry only, leaving the first as an invisible outer shadow. */
  --shadow-active: inset -2px -2px 8px #f6f6ff, inset 2px 2px 8px #f6f6ff;

  /* The ddx dropdown's lift. Same two-corner shape as --shadow-active and the same colour, but cast
   * OUTWARD and a third of a pixel softer, which is what the design states — so it cannot reuse
   * that token however alike the two read written down. */
  --shadow-dropdown: 2px -2px 8.3px var(--purple-025), -2px 2px 8.3px var(--purple-025);

  --font: "Source Sans 3", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  /* Headings are set in New Order, the body in Source Sans — which stays the fallback, so a heading
   * paints in the right size and weight while the display face is still in flight. */
  --font-display: "New Order", var(--font);

  --gutter: 16px;
  --column: 480px;

  /* The stage subtracts this from the viewport, so the bar and the screen below it are never both
   * asking for the full height — which is what would put a scrollbar on a screen that fits. */
  --topbar-height: 60px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
}

body {
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.3;
  color: var(--ink-900);
  background: #fff;
  -webkit-text-size-adjust: 100%;
}

/* Any class here that sets `display` outbeats the user-agent's `[hidden] { display: none }`, so a
 * hidden element keeps rendering. Everything in this file toggles visibility with the attribute, so
 * the attribute has to win. */
[hidden] {
  display: none !important;
}

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

/* ---- topbar: the wordmark, and nothing else ---------------------------------------------------- */

.topbar {
  display: flex;
  align-items: center;
  height: var(--topbar-height);
  padding-inline: 20px;
  background: #fff;
  border-bottom: 1px solid var(--purple-100);
}

/* Height is the fixed dimension and width follows the artwork: the two marks have different aspect
 * ratios, and pinning both dimensions would squash one of them. */
.topbar__logo {
  display: block;
  width: auto;
}
.topbar__logo--full {
  height: 22px;
}
.topbar__logo--mark {
  display: none;
  height: 32px;
}

/* Below the column width the wordmark gives up its lettering and keeps the monogram. The padding
 * drops to the gutter at the same point, so the logo lines up with the edge of the card beneath it. */
@media (max-width: 480px) {
  .topbar {
    padding-inline: var(--gutter);
  }
  .topbar__logo--full {
    display: none;
  }
  .topbar__logo--mark {
    display: block;
  }
}

/* ---- the stage: one surface whose colour IS the state ------------------------------------------ */

.stage {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - var(--topbar-height));
  min-height: calc(100dvh - var(--topbar-height));
  padding: var(--gutter);
  gap: var(--gutter);
  background: #fff;
  transition: background-color 240ms ease;
}

/* The page stays white throughout. Every poll design carries the state's colour on the surface
 * inside the panel, so the stage itself never tints. */

/* The column is the whole app on a phone and a centred card on a laptop. Someone opening this on a
 * desktop gets a phone-shaped app, which is honest — it is one. */
.stage > * {
  width: 100%;
  max-width: var(--column);
  margin-inline: auto;
}

/* The presentation phases are the exception, and the design draws them as one: a panel that takes
 * the width it is given, because there is nothing on it to read in a column — an illustration, a
 * line of copy, a row of steps. The phases holding a question keep the column, where a phone-width
 * measure is the point.
 *
 * The masthead widens with them, or it would sit in a 480px column above a panel three times its
 * width.
 *
 * 30px of side padding is the design's, and it is what those panels measure themselves against: the
 * panel is the viewport less those two margins, at any width. */
@media (min-width: 900px) {
  .stage {
    padding-inline: 30px;
  }
  .stage--panelled > * {
    max-width: none;
  }

  /* The breadcrumb runs the width at any phase. Held to the column it would truncate a case title
   * with half the screen empty beside it. The content below it keeps the column. */
  .masthead {
    max-width: none;
  }
}

/* ---- masthead: the breadcrumb --------------------------------------------------------------------- */

.masthead {
  display: flex;
  align-items: center;
}

.crumbs {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  margin: 0;
}

/* Crumbs hold their size by default, so a row that has to give up width gives it up in a chosen
 * order rather than shaving every crumb at once — which is what shortened the patient's name while
 * the case title still had room. */
.crumbs__step {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  flex-shrink: 0;

  font-size: 14px;
  font-weight: 600;
  color: var(--ink-500);
}

/* The case title leads at the design's larger size, and it is the crumb that gives way: it is the
 * longest, the least urgent, and the one thing an attendee already knows — they scanned into it. */
.crumbs__step--case {
  font-size: 16px;
  flex-shrink: 999;
}

/* Capped at the width the design gives this crumb, so a long title ellipses at a consistent point
 * instead of running as far as the screen allows and setting the position further right on every
 * case. It still shrinks below the cap when the row is tight — the cap is a ceiling, not a width. */
.crumbs__step--case .crumbs__text {
  max-width: 280px;
}

/* Truncates only once the title has nothing left to give, which on a phone is immediately: the title
 * is not rendered there at all. */
.crumbs__step--current {
  flex-shrink: 1;
  color: var(--ink-900);
}

.crumbs__text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.crumbs__caret {
  flex: none;
  width: 12px;
  height: 12px;
  color: var(--purple-400);
}

/* A phone has no room for the case title, and it is the crumb an attendee needs least — they know
 * which case they scanned into. The patient and the name stay. */
@media (max-width: 480px) {
  .crumbs__step--case {
    display: none;
  }
}

/* ---- step chips -------------------------------------------------------------------------------- */

/* The steps and the report control, as one row of panel chrome above the surface. Hidden as a unit:
 * see the markup for why a report button over no steps is not a state worth having. */
.panel__head {
  display: flex;
  /* Reversed, so the report reads above the steps on a phone exactly as the design draws it, while
   * the markup keeps the steps first. Safe to invert here and nowhere else in this file: neither
   * the pills nor a disabled button can take focus, so there is no tab order to contradict. */
  flex-direction: column-reverse;
  align-items: flex-start;
  gap: 12px;
}

@media (min-width: 900px) {
  .panel__head {
    flex-direction: row;
    align-items: center;
    gap: 16px;
  }
  .report {
    margin-inline-start: auto;
  }
}

/* Nothing is wired to it yet, so it is disabled — which is exactly why the colour is stated. A
 * disabled button is painted `graytext` by the browser, and the design draws it in full ink. */
.report {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-900);
  white-space: nowrap;
}
.report:disabled {
  cursor: default;
}

/* Wrapped rather than scrolled: the design shows every step at once, which on a phone is two or
 * three rows. Nothing here scrolls, which is why renderChips no longer scrolls the current chip
 * into view — with no scroll container left, that call would have moved the page instead. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  min-width: 0;
}

/* The recap's two pills are this pill — the design draws its step heading as the current chip and
 * its question count as a plain one — so they share the rule rather than restating it and drifting
 * the next time the chip changes. They differ only in being block-level children of a flex column,
 * which is what .review__step / .review__count add on top. */
.chip,
.review__step,
.review__count {
  flex: none;
  /* A step title longer than the row would otherwise push the panel wider than the phone. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;

  padding: 3px 12px;
  border-radius: var(--radius-pill);
  background: var(--grey-050);
  color: var(--ink-700);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
}

/* Fill and ink alone, no ring — the design distinguishes the current step by colour, and the ring
 * this used to draw was the only reason the row needed padding to keep from shearing it. */
.chip--current,
.review__step {
  background: var(--purple-050);
  color: var(--purple-900);
}

/* ---- the content card -------------------------------------------------------------------------- */

.body {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* The question, floating on the phase's surface at the design's 524px. It does not stretch: a poll
 * read at arm's length is a column, not a banner, however wide the panel behind it gets. */
.card {
  width: 100%;
  max-width: 524px;
  background: #fff;
  border-radius: var(--radius-surface);
  padding: 16px 24px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.card__count {
  font-size: 14px;
  font-weight: 600;
  color: #000;
}

.card__question {
  font-size: 16px;
  font-weight: 400;
  color: #000;
  margin: 0;
}

/* The ask trigger floats over the surface's top-right corner, and a long enough question fills the
 * surface and reaches it. A float rather than padding, so only the lines actually beside the button
 * are shortened — padding would narrow the whole stem, including the lines well below it, and a ddx
 * stem is long. Sized to clear the button: 55px wide plus its 23px inset, less the card's padding. */
@media (max-width: 899px) {
  .card__question::before {
    content: "";
    float: right;
    width: 70px;
    height: 56px;
  }
}
.card__question:focus {
  outline: none;
}

/* ---- options ----------------------------------------------------------------------------------- */

.options {
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* Room for the selected ring, which is drawn outside the row. Load-bearing for `--scroll` below:
   * an overflow container clips that ring, and this padding is what keeps it on screen. */
  padding: 3px;
  margin: -3px;
}

/* The poll's submit is the design's 176px button, sitting at the foot of the card rather than
 * spanning it — this card is not the join screen, whose CTA is the whole width. */
.card .btn {
  align-self: flex-start;
  min-width: 176px;
  min-height: 45px;
  padding: 12px 24px;
  font-size: 16px;
}

/* A <button>, so the radiogroup keyboard pattern works — which means every scrap of UA chrome has to
 * go, or it ships looking like a form control from 1998. */
.option {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  text-align: left;
  cursor: pointer;

  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px;
  min-height: 48px;

  background: #fff;
  border: 1px solid var(--purple-400);
  border-radius: 6px;
  color: var(--ink-900);
  transition: border-color 120ms ease, box-shadow 120ms ease, background-color 120ms ease;
}

/* The design's badge: a square tile, not a lozenge, and the letter inside it is the same weight as
 * the option's own text — it labels the row rather than shouting over it. */
.option__letter {
  flex: none;
  align-self: flex-start;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  background: var(--purple-025);
  color: var(--indigo-900);
  font-size: 16px;
  font-weight: 400;
}

.option__text {
  flex: 1;
  font-size: 16px;
}

.option:hover:not(:disabled) {
  border-color: var(--purple-400);
}

.option:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--purple-100);
  border-color: var(--purple-400);
}

/* A heavier edge rather than a ring: the design thickens the border to 2px, and border-box keeps the
 * row exactly as tall as its unselected neighbours. */
.option--selected {
  border: 2px solid var(--purple-600);
  box-shadow: none;
}

.option:disabled {
  cursor: default;
  color: var(--grey-400);
  border-color: var(--grey-050);
}
.option:disabled .option__letter {
  background: var(--grey-050);
  color: var(--grey-400);
}
/* A disabled row that the attendee actually chose must still read as theirs. */
.option--selected:disabled {
  color: var(--ink-900);
  border: 2px solid var(--purple-600);
}
.option--selected:disabled .option__letter {
  background: var(--purple-025);
  color: var(--indigo-900);
}

/* The design's answer row: an 8px radius and the padding that makes it 45px tall, rather than the
 * poll row's 6px and 48px. Scoped to the verdict card so the poll's own rows keep their geometry. */
.verdict .option,
.verdict .option:disabled {
  padding: 12px 16px;
  min-height: 45px;
  border-radius: var(--radius-input);
}

/* Result rows are always disabled, so every one of these has to out-specify the disabled treatment
 * above — otherwise the row turns green and its glyph stays grey.
 *
 * The right answer is green on EVERY outcome, not only the celebration: it is the answer either way,
 * and on the missed screen the green is what the amber row above it is read against. */
.option--correct,
.option--correct:disabled {
  background: var(--mint-050);
  border-color: var(--mint-600);
  color: var(--mint-900);
}
.option--correct .option__icon,
.option--correct:disabled .option__icon {
  color: var(--mint-500);
}

/* The attendee's own miss. Amber rather than red: the right answer sits beside it in green, so the
 * contrast between the two rows is what carries the message — and a red row reads as an error they
 * committed rather than an answer they gave. Same pair as the low-time surface, deliberately: it is
 * the palette this design uses for "not wrong, but not it". */
.option--wrong,
.option--wrong:disabled {
  background: var(--amber-100);
  border-color: var(--amber-500);
  color: var(--amber-900);
}
.option--wrong .option__icon,
.option--wrong:disabled .option__icon {
  color: var(--amber-500);
}

/* The status glyph, at the label's own size — the design sets both at 16px, so the row reads as one
 * line rather than an icon with a caption. */
.option__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
}
.option__icon svg {
  width: 100%;
  height: 100%;
}

/* ---- buttons ----------------------------------------------------------------------------------- */

.btn {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  font-size: 18px;
  font-weight: 600;
  cursor: pointer;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 24px;
  min-height: 55px;

  border: none;
  border-radius: var(--radius-pill);
  background: var(--purple-700);
  color: #fff;
  transition: background-color 120ms ease;
}

.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--purple-100);
}

.btn:disabled {
  background: var(--grey-400);
  cursor: default;
}

.btn--submitted:disabled {
  background: var(--purple-900);
  color: #fff;
}

.btn__icon {
  flex: none;
  width: 20px;
  height: 20px;
}

.hint {
  font-size: 14px;
  color: var(--ink-500);
  text-align: center;
  margin: 0;
}

/* ---- the centred beat: illustration + headline -------------------------------------------------- */

.beat {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 12px;
  padding: 24px 8px;
}

/* Placeholder glyphs, not the design's illustrations. The torsos run to the foot of the box, so the
 * extra margin is what stops them butting into the headline. */
.beat__art {
  flex: none;
  width: 96px;
  height: 96px;
  margin-bottom: 12px;
}

.beat__art > svg {
  display: block;
  width: 100%;
  height: 100%;
}

.beat__headline {
  font-size: 18px;
  font-weight: 600;
  color: var(--ink-900);
  margin: 0;
}

.beat__detail {
  font-size: 16px;
  color: var(--ink-500);
  margin: 0;
  max-width: 34ch;
}

/* The design's middle panel: an outline and nothing else — the fill belongs to the empty-state nested
 * inside it, which is why these are two elements rather than one box wearing both. A single element
 * cannot carry a 24px outline and an 8px filled edge inset from it.
 *
 * Bare on a phone, where the outline would only draw a second line 16px outside the first. */
.panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* Only the phases whose design draws the outline get it — the rest of the app is still a card on
 * white, and the join screen in particular would gain a second frame around its own. */
@media (min-width: 900px) {
  .stage--panelled .panel {
    padding: 16px;
    border: 1px solid var(--purple-100);
    border-radius: var(--radius-panel);
  }
}

/* The region the surface fills, and what the ask trigger is positioned against. Anchoring the
 * trigger to the panel would mean clearing a header whose height changes with the number of step
 * rows; against this it is a fixed inset from the surface at every width and in every phase. */
.panel__stage {
  position: relative;
  flex: 1;
  display: flex;
}

/* The design's ask trigger: a circle on the surface, top right, over the content rather than beside
 * it. Deliberately not the bottom corner — that is where the Submit button and the toast live. */
.ask-open {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  cursor: pointer;

  /* Higher than the 23px it is inset from the right: at 23px its foot landed exactly on the top edge
   * of the question card, which read as the button sitting on the card rather than above it. */
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 1;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 55px;
  height: 55px;

  border-radius: var(--radius-pill);
  background: var(--purple-700);
  color: #fff;
  transition: background-color 120ms ease;
}

.ask-open:hover {
  background: var(--purple-900);
}

.ask-open:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--purple-100);
}

.ask-open__icon {
  width: 20px;
  height: 20px;
}

/* The design's empty-state, shared by every phase that draws one. The shape is identical across
 * them — a centred column on a hairline-bordered panel — so only the fill, the artwork and the copy
 * change, and those are the phase's business rather than this rule's. */
.surface {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 24px 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-surface);
}

@media (min-width: 900px) {
  .ask-open {
    top: 23px;
    right: 23px;
  }
  .surface {
    padding: 48px 64px;
  }
}

/* The state's colour lives on the surface, not on the page. Every poll design shows a white page
 * with a coloured panel inside it, which is why the stage no longer tints itself for these phases. */
.stage--idle .surface,
.stage--staged .surface {
  background: var(--grey-025);
  border-color: var(--grey-200);
}

.stage--opening .surface {
  background: var(--purple-050);
  border-color: var(--purple-400);
  /* The design's "Active Drop Shadow" — on this panel, where it reads as the poll waking up. */
  box-shadow: var(--shadow-active);
}

.stage--locked .surface {
  background: var(--peach-050);
  border-color: var(--peach-400);
}

.stage--active .surface,
.stage--result .surface {
  background: var(--purple-050);
  border-color: var(--purple-400);
}

/* The last stretch of the poll. The surface turns amber rather than anything on the card, so the
 * urgency is the room's and the question stays exactly as readable as it was. No clock is drawn:
 * the colour is the only thing the attendee gets, and it is the room's cue, not a personal one. */
.stage--lowtime .surface {
  background: var(--amber-100);
  border-color: var(--amber-500);
}

/* The quiet treatment the two grey screens share: a caption under an illustration rather than a
 * headline over one. */
.beat--quiet .beat__headline {
  font-size: 14px;
  color: var(--ink-500);
}

.beat--quiet .beat__detail {
  font-size: 14px;
  text-align: center;
  max-width: none;
}

/* Announcement screens: one line, sized to be read across a room rather than in the hand. */
.beat--announce .beat__headline {
  font-size: 18px;
}

@media (min-width: 900px) {
  .beat--announce .beat__headline {
    font-size: 32px;
  }
}

.stage--opening .beat__headline {
  color: var(--purple-900);
}

.stage--locked .beat__headline {
  color: var(--peach-900);
}

/* The design sizes the artwork per screen — 274x160 for the consult, 96x96 for the poll
 * placeholder — so the size is stated with the screen, not here. This only stops it overflowing. */
.beat__illustration {
  flex: none;
  width: 100%;
  height: auto;
}

/* Four screens share this artwork box — the consult placeholder, the Consult Done beat, the
 * open-questions placeholder and the wrap screen. All four draw a 274x160 export — the greyscale
 * one or the colour one — so they take one rule rather than four copies of the same two numbers. */
.beat--consult .beat__illustration,
.beat--consult-done .beat__illustration,
.beat--open-questions .beat__illustration,
.beat--wrap .beat__illustration {
  max-width: 200px;
}

@media (min-width: 900px) {
  .beat--consult .beat__illustration,
  .beat--consult-done .beat__illustration,
  .beat--open-questions .beat__illustration,
  .beat--wrap .beat__illustration {
    max-width: 274px;
  }
}

/* The one place a quiet screen's caption is NOT quiet. The design draws Consult Done as a
 * statement — full ink — where the placeholder screens either side of it are grey. */
.beat--consult-done .beat__headline {
  color: var(--ink-900);
}

.beat--poll-idle .beat__illustration {
  max-width: 96px;
}

/* The title and its sentence read as one block, tighter to each other than to the illustration. */
.beat__copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: 288px;
}

/* ---- the lobby clip ----------------------------------------------------------------------------- */

/* The design's empty-state: a filled panel that takes the room the phase has nothing else to do with,
 * rather than a small centred cluster floating on the stage. */
.beat--clip {
  flex: 1;
  justify-content: center;
  gap: 24px;
  padding: 24px 16px;
  background: var(--purple-050);
  border: 1px solid var(--grey-200);
  border-radius: var(--radius-input);
}

@media (min-width: 900px) {
  .beat--clip {
    padding: 48px 64px;
  }
}

.beat--clip .beat__headline {
  font-family: var(--font-display);
  font-size: 24px;
  line-height: 1.3;
}

@media (min-width: 900px) {
  .beat--clip .beat__headline {
    font-size: 30px;
  }
}

.beat--clip .beat__detail {
  font-weight: 600;
  color: var(--ink-900);
}

/* The clip is 16:9 at its design size and shrinks with the column. The ratio is declared rather than
 * left to the file so the panel does not resize once the first frame decodes.
 *
 * The edges are feathered because the clip's background and this panel's fill are the same colour on
 * paper and not always on screen: a video is colour-managed down a different path from a CSS fill, so
 * on a wide-gamut display the two flat lilacs can land a few levels apart and the clip's box appears
 * as a rectangle. A gradient boundary has no step for the eye to catch.
 *
 * The two axes fade by different amounts because the artwork is not centred in the frame: it clears
 * the sides by 27% and the top and bottom by under 4%. */
.beat__clip {
  width: 100%;
  max-width: 400px;
  aspect-ratio: 16 / 9;

  --feather-x: 12%;
  --feather-y: 3%;
  -webkit-mask-image: linear-gradient(
      to right,
      transparent,
      #000 var(--feather-x),
      #000 calc(100% - var(--feather-x)),
      transparent
    ),
    linear-gradient(
      to bottom,
      transparent,
      #000 var(--feather-y),
      #000 calc(100% - var(--feather-y)),
      transparent
    );
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(
      to right,
      transparent,
      #000 var(--feather-x),
      #000 calc(100% - var(--feather-x)),
      transparent
    ),
    linear-gradient(
      to bottom,
      transparent,
      #000 var(--feather-y),
      #000 calc(100% - var(--feather-y)),
      transparent
    );
  mask-composite: intersect;
}

/* The square clips — poll_starting and poll_end are 800x800, where the lobby's is 16:9. */
.beat__clip--square {
  aspect-ratio: 1 / 1;
  max-width: 160px;
}

@media (min-width: 900px) {
  .beat__clip--square {
    max-width: 200px;
  }
}

/* ---- the result verdict ------------------------------------------------------------------------ */

/* The design's card, and the SAME card for every outcome and every poll kind: a 112px tile beside a
 * column carrying the headline, the answer row(s) and the count. */
.verdict {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 32px;
  padding: 24px;
}

.verdict__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* The tile. The celebration fills it with the clip; every other outcome fills the same square with a
 * glyph, which is what keeps the card from changing shape with the verdict. */
.verdict__clip,
.verdict__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 112px;
  height: 112px;
  border-radius: var(--radius-card);
}
.verdict__clip {
  background: var(--green-050);
}
.verdict__mark svg {
  width: 48px;
  height: 48px;
}

/* Amber, not red, for the same reason the missed row below it is amber: this is a near miss, not an
 * error. */
.verdict--wrong .verdict__mark {
  background: var(--amber-100);
  color: var(--amber-900);
}
.verdict--none .verdict__mark {
  background: var(--grey-050);
  color: var(--ink-500);
}

/* The clip stands where the mark would, so only one of the two is ever drawn. */
.verdict--correct .verdict__mark {
  display: none;
}

/* A phone has no room for a 112px tile beside the answer. */
@media (max-width: 480px) {
  .verdict {
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
  }
  .verdict__clip,
  .verdict__mark {
    align-self: center;
  }
}

.verdict__headline {
  font-size: 16px;
  font-weight: 600;
  margin: 0;
}

/* The design's green: this line counts the room that reached the GREEN row directly above it, so it
 * takes that row's colour. */
.verdict__tally {
  font-size: 14px;
  font-weight: 600;
  color: var(--mint-600);
  margin: 0;
}

/* ---- the Review recap ---------------------------------------------------------------------------- */

/* The one surface that is not a centred cluster: it holds a rail and a card side by side, and the
 * design left-aligns the pair rather than centring them in the panel.
 *
 * `justify-content: flex-start` is LOAD-BEARING, not a preference. .surface centres its flex line,
 * and content taller than the line overflows it in BOTH directions — the overflow above the
 * container cannot be scrolled to, so a recap longer than the viewport would lose its first
 * questions with no way to reach them. This card is expected to be long; the design's own mockup is
 * cut off mid-card. */
.surface--review {
  align-items: stretch;
  justify-content: flex-start;
  gap: 16px;
}

@media (min-width: 900px) {
  .surface--review {
    flex-direction: row;
    align-items: flex-start;
    gap: 32px;
    /* The design's own side padding here, narrower than the 64px the placeholder screens take —
     * this surface has a 630px card to fit and they have an illustration. */
    padding: 48px 32px;
  }
}

/* Stacked beside the card on a laptop; one row above it on a phone, score left and the link right,
 * exactly as the mobile design draws it. */
.review__rail {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

@media (min-width: 900px) {
  .review__rail {
    flex: none;
    width: 280px;
    flex-direction: column;
    align-items: flex-start;
    gap: 24px;
  }
}

/* A pill, but not `.chip`: the design sets this at 18px on --grey-200 where a step chip is 14px on
 * --grey-050, and widening the chip to cover both would drag every step pill with it. */
.review__score {
  flex: none;
  margin: 0;
  padding: 10px 16px;
  border-radius: var(--radius-pill);
  background: var(--grey-200);
  color: var(--navy-900);
  font-size: 18px;
  font-weight: 600;
  white-space: nowrap;
}

/* Nothing is wired to it yet, so it is disabled — which is exactly why the colour is stated, the
 * same arrangement (and the same reason) as .report: a disabled button is painted `graytext` by the
 * browser, and the design draws it in full purple. */
.review__email {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 16px;
  font-weight: 600;

  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--purple-700);
  white-space: nowrap;
}
.review__email:disabled {
  cursor: default;
}

.review__email-icon {
  flex: none;
  width: 20px;
  height: 20px;
}

/* The white card the questions sit on. Wider than the poll's 524px — this one is read as a list
 * rather than a question at arm's length — and it carries no border: white on the grey surface is
 * the whole edge the design gives it. */
.card.review__card {
  max-width: 630px;
  padding: 24px;
  gap: 32px;
}

/* 24px of side padding on a 390px phone leaves the answer rows narrower than the design's own, so
 * the card keeps its shape and gives the padding back — the same trade .join and .ddx-result make. */
@media (max-width: 480px) {
  .card.review__card {
    padding: 24px 16px;
  }
}

.review__group,
.review__questions,
.review__question {
  display: flex;
  flex-direction: column;
}
.review__group {
  gap: 24px;
}
.review__questions {
  gap: 32px;
}
.review__question {
  gap: 12px;
}

/* Both pills take the .chip rules above; these are only what a heading and a flex child need on top.
 * align-self keeps them shrink-wrapped rather than stretching the width of the card. */
.review__step,
.review__count {
  align-self: flex-start;
  margin: 0;
}

.review__stem {
  margin: 0;
  font-size: 16px;
  font-weight: 400;
  color: var(--navy-900);
}

/* A question the attendee saw resolve but never answered. The design draws no such row — it draws
 * the happy path — so this states the fact in words rather than inventing a fourth row colour. */
.review__unanswered {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-500);
}

/* The same 45px/8px row the verdict cards use: the results differ in structure, but a row is a row. */
.review__card .option,
.review__card .option:disabled {
  padding: 12px 16px;
  min-height: 45px;
  border-radius: var(--radius-input);
}

/* Red, and scoped. A missed answer is AMBER app-wide — a near miss, not a fault — and that stays
 * true for the live poll result. This card was drawn on its own and states red, exactly as the ddx
 * result does, so the red is scoped here rather than replacing the rule everywhere it is used. */
.review__card .option--wrong,
.review__card .option--wrong:disabled {
  background: var(--rose-050);
  border-color: var(--rose-500);
  color: var(--ink-900);
}
.review__card .option--wrong .option__icon,
.review__card .option--wrong:disabled .option__icon {
  color: var(--rose-500);
}

/* ---- DDx mode ----------------------------------------------------------------------------------- */

/* The picker's card. The same white panel on the phase's surface every other screen uses, at the
 * design's own measurements — and at the 16px radius the card wore before the poll's grew to 24.
 *
 * Both gaps either side of the prompt column are 24, so the column groups rather than spaces. That
 * is what makes the card come out at the design's 293px: 24 + 166 + 24 + 55 + 24. */
.card.ddx {
  max-width: 404px;
  padding: 24px 16px;
  border-radius: var(--radius-card);
}

.ddx__prompt {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.ddx__title {
  font-size: 16px;
  font-weight: 600;
  color: var(--ink-900);
  margin: 0;
}

.ddx__stem {
  font-size: 16px;
  font-weight: 400;
  color: var(--ink-900);
  margin: 0;
}

/* Both carry tabindex="-1" like every other screen's heading here, so they can be focused to move a
 * screen reader to the new screen. Focused that way rather than by tabbing, a ring would be noise —
 * the same arrangement .card__question already has. */
.ddx__title:focus,
.ddx-result__headline:focus {
  outline: none;
}

/* The field. `relative` is load-bearing: the list below is positioned against it and deliberately
 * overhangs the card, so this is the box that anchors it. */
.ddx__field {
  position: relative;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 16px;
  min-height: 55px;
  border: 1px solid var(--purple-400);
  border-radius: var(--radius-input);
  background: #fff;
}

/* The design draws no focus treatment — Figma rarely does — but every other input in this app
 * darkens its edge and lifts a ring, and a field nobody can see the focus on is not a design
 * decision. Same pair as .join__pin and .ask__field. */
.ddx__field:focus-within {
  border-color: var(--purple-700);
  box-shadow: 0 0 0 3px var(--purple-100);
}

.ddx__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--ink-500);
}
.ddx__icon svg {
  width: 100%;
  height: 100%;
}

.ddx__input {
  /* iOS Safari draws an inner shadow and its own clear affordance otherwise. */
  appearance: none;
  -webkit-appearance: none;
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  /* No padding of its own, and the field centres it: the design's 55px is the OUTER height, and its
   * 16px of padding is measured on a box with no border. Adding both to a border-box field lands at
   * 57-60 depending on where the font's normal line-height falls. Centring a fixed 23px line inside
   * a 55px minimum is the same picture at exactly the stated size.
   *
   * 23px is the design's own reading of 130% at this size, stated rather than computed so the
   * dropdown's 177px — 6px, three 55px rows, 6px — comes out exact. */
  padding: 0;
  line-height: 23px;
  font-family: var(--font);
  /* The design's 18px, comfortably over the 16px floor below which iOS zooms the page on focus and
   * never zooms back. */
  font-size: 18px;
  color: var(--ink-900);
  background: transparent;
}
.ddx__input::placeholder {
  color: var(--ink-500);
}

/* A filled dark disc with the cross knocked out of it — which is exactly what the Phosphor XCircle
 * the result rows already use draws, so this reuses that glyph rather than shipping a seventh one.
 * The page shows through the cross, so on the white field it reads white, as the design has it.
 *
 * The padding/negative-margin pair is what makes a 20px glyph a 40px target without moving it: the
 * margin box stays 20px, so flex lays it out exactly where it was, and the 40px hit area grows
 * outward around it. */
.ddx__clear {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  display: grid;
  place-items: center;
  box-sizing: content-box;
  width: 20px;
  height: 20px;
  padding: 10px;
  margin: -10px;
  border: 0;
  background: transparent;
  color: var(--ink-900);
  cursor: pointer;
}
.ddx__clear svg {
  width: 20px;
  height: 20px;
}
.ddx__clear:focus-visible {
  outline: none;
  border-radius: var(--radius-pill);
  box-shadow: 0 0 0 3px var(--purple-100);
}

/* The dropdown. It hangs off the field and overhangs the card's bottom edge exactly as the design
 * draws it, which is why nothing from here up to the stage may take an `overflow` — the first
 * ancestor that does will shear it.
 *
 * 177px is the design's own panel, to the pixel: 6px of padding, three 55px rows, 6px more. Past
 * three it scrolls rather than grows, so a term matching eighty diagnoses cannot run off a phone. */
.ddx__list {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 2;

  margin: 0;
  padding: 6px;
  list-style: none;
  max-height: 177px;
  overflow-y: auto;
  overscroll-behavior: contain;

  background: #fff;
  border-radius: 6px;
  box-shadow: var(--shadow-dropdown);
}

/* Not enough room underneath — a short window, or the software keyboard taking half the screen —
 * and the panel opens upward instead of off the bottom of the world. */
.ddx__list--above {
  top: auto;
  bottom: calc(100% + 4px);
}

.ddx-option {
  display: flex;
  align-items: center;
  min-height: 55px;
  /* Horizontal only, and the row centres its line — same reason as the field above, and it is what
   * makes three rows measure the design's 177px to the pixel. A name long enough to wrap grows the
   * row past the minimum rather than being clipped by it. */
  padding: 8px 16px;
  border-radius: 5px;
  background: #fff;
  color: var(--ink-500);
  font-size: 18px;
  line-height: 23px;
  cursor: pointer;
}

/* The row under the pointer and the row the arrows are on get ONE treatment. Two different fills
 * would read as two selections, and only one of them can be committed. */
.ddx-option:hover,
.ddx-option.is-active {
  background: var(--purple-025);
}

/* The typed run, dark against the rest of the name. With a substring match this is what explains
 * why a row is in the list at all — "cancer" finds Vulvar Cancer, and the eye needs telling where. */
.ddx-option__match {
  color: var(--ink-900);
  font-weight: 600;
}

.ddx-option--empty,
.ddx-option--empty:hover {
  background: #fff;
  color: var(--ink-700);
  font-size: 16px;
  cursor: default;
}

/* The design's button here is the base pill at its natural width — so this UNDOES the poll card's
 * override rather than adding to it, and says so explicitly instead of relying on source order. */
.card.ddx .btn {
  align-self: flex-start;
  min-width: 0;
  min-height: 55px;
  padding: 16px 24px;
  /* 4, not the shared button's 8: the design sets this pair tighter, and the difference is the whole
   * 3px between its 129px button and the one that comes out of the default. */
  gap: 4px;
  font-size: 18px;
}

/* Submitted, in the design's own indigo. The poll's is a shade lighter — the two cards were drawn
 * apart, and this is the one the ddx states. */
.card.ddx .btn--submitted:disabled {
  background: var(--indigo-900);
  color: var(--purple-050);
}

.beat--ddx-idle .beat__illustration {
  max-width: 101px;
}

/* ---- the ddx result ------------------------------------------------------------------------------ */

/* A different card from the poll's verdict: no tile, no celebration clip, and BOTH tallies — the
 * design counts the room that reached the attendee's answer as well as the room that reached the
 * right one. */
.card.ddx-result {
  max-width: 436px;
  padding: 24px;
  border-radius: var(--radius-card);
  gap: 16px;
}

/* 24px of side padding on a 390px phone leaves the answer rows narrower than the design's own, so
 * the card keeps its shape and gives the padding back — the same trade .join already makes. */
@media (max-width: 480px) {
  .card.ddx-result {
    padding: 24px 16px;
  }
}

.ddx-result__headline,
.ddx-result__label {
  font-size: 16px;
  font-weight: 600;
  color: #000;
  margin: 0;
}

.ddx-result__answers,
.ddx-result__block {
  display: flex;
  flex-direction: column;
}
.ddx-result__answers {
  gap: 16px;
}
.ddx-result__block {
  gap: 12px;
}

/* The rule rides on the attendee's own block rather than sitting between the two, so an outcome
 * with no answer of its own — they got it right, or never voted — loses the divider with it. */
.ddx-result__block--yours {
  padding-bottom: 16px;
  border-bottom: 1px solid var(--purple-100);
}

.ddx-result__tally {
  font-size: 14px;
  font-weight: 600;
  color: var(--purple-700);
  margin: 0;
}

/* Their own line takes their own row's colour, where the correct one takes the purple the design
 * gives it. */
.ddx-result__tally--yours {
  color: var(--rose-500);
}

/* The same 45px/8px row the poll's verdict uses: the two results differ in colour and in structure,
 * but a row is a row. */
.ddx-result .option,
.ddx-result .option:disabled {
  padding: 12px 16px;
  min-height: 45px;
  border-radius: var(--radius-input);
}

/* Red here, and ONLY here. A missed answer is amber app-wide — a near miss, not a fault — and that
 * stays true for the poll. The ddx result was drawn on its own and states red, so the red is scoped
 * to this card instead of replacing the rule everywhere it is used. */
.ddx-result .option--wrong,
.ddx-result .option--wrong:disabled {
  background: var(--rose-050);
  border-color: var(--rose-500);
  color: var(--ink-900);
}
.ddx-result .option--wrong .option__icon,
.ddx-result .option--wrong:disabled .option__icon {
  color: var(--rose-500);
}

/* ---- the submitted toast ------------------------------------------------------------------------ */

/* The design's card is 209x45, and both numbers are the shrink-wrap of its auto-layout rather than a
 * constraint: 12 + 20 + 6 + 127 + 12 + 20 + 12 across, 12 + 21 + 12 down. So the card is sized by
 * its content here too, which is what the other two messages this toast carries need — "Question
 * sent" is shorter, and a server error is arbitrarily longer. */
.toast {
  position: fixed;
  bottom: 24px;
  z-index: 10;

  /* Centred by auto margins across the full width, NOT by `left: 50%` and a -50% translate.
   *
   * The translate trick lays the card out in the space to the right of the 50% mark and only slides
   * it back when painting, so its shrink-to-fit width is capped at half the viewport: on a 390px
   * phone the card got 195px, and "Answer Submitted" — 209px — wrapped to two lines. That is the
   * break the old `white-space: nowrap` was covering up, which is why a long error could not wrap
   * either. Spanning the viewport and centring with margins gives the card the whole width to
   * measure against, so the label wraps only when it genuinely does not fit.
   *
   * It also leaves `transform` free for the entrance keyframe. */
  left: 0;
  right: 0;
  width: fit-content;
  margin-inline: auto;

  display: flex;
  flex-direction: column;
  /* The design's gap, inert while the description line and the button below the row stay unbuilt —
   * both are display: none in it. Kept so adding either lands at the right distance. */
  gap: 16px;
  padding: 12px;
  max-width: calc(100vw - 32px);

  background: #fff;
  border-radius: var(--radius-input);
  /* The design states no effect, but a white card on this app's white stage would have no edge at
   * all — and the desktop corner is exactly where it floats clear of any tinted panel. */
  box-shadow: 0 4px 16px rgb(0 0 0 / 12%);

  animation: toast-in 200ms ease;
}

/* The design's bottom-right. 30px is the stage's own desktop side padding, so the card lines up with
 * the outer edge of the panel above it rather than sitting at an unrelated inset. */
@media (min-width: 900px) {
  .toast {
    left: auto;
    right: 30px;
    margin-inline: 0;
  }
}

/* flex-start, not centre: when a long error wraps to two lines the close stays on the first one,
 * which is the line it dismisses. */
.toast__row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

/* The design centres the glyph against the label, but it only ever draws one line. flex-start is
 * identical there — a 20px glyph in a 20.8px line box differs by 0.4px — and is what a wrapped
 * error needs: the glyph belongs on the first line, beside the X, not floating down the middle of
 * the block. */
.toast__message {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink-900);
}

.toast__icon {
  flex: none;
  display: block;
  width: 20px;
  height: 20px;
  color: var(--mint-400);
}

.toast__icon svg {
  display: block;
  width: 100%;
  height: 100%;
}

.toast--error .toast__icon {
  color: var(--red-600);
}

.toast__close {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  color: var(--ink-900);
}

/* The design's glyph is 20px, under the 24px a thumb needs. The box stays 20px so the 12px gap and
 * the card's width are the design's; only the hit area grows, and it costs no layout. */
.toast__close::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  width: 44px;
  height: 44px;
}

.toast__close:hover {
  background: var(--grey-050);
}

.toast__close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--purple-100);
}

.toast__close-icon {
  display: block;
  width: 100%;
  height: 100%;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
}

/* ---- the ask sheet ------------------------------------------------------------------------------ */

/* Above the toast's z-index: 10, so a toast raised before the sheet opened cannot sit over it. */
.ask {
  position: fixed;
  inset: 0;
  z-index: 20;

  display: flex;
  align-items: flex-end;
  background: rgb(0 0 0 / 40%);
}

.ask__panel {
  width: 100%;
  max-width: var(--column);
  margin-inline: auto;

  display: flex;
  flex-direction: column;
  gap: 12px;

  /* index.html sets viewport-fit=cover, so without the safe-area term Send sits underneath the
   * iPhone home indicator and cannot be tapped. */
  padding: 24px 16px calc(24px + env(safe-area-inset-bottom));
  background: #fff;
  border-radius: var(--radius-panel) var(--radius-panel) 0 0;
  animation: ask-in 200ms ease;
}

@keyframes ask-in {
  from {
    transform: translateY(16px);
  }
}

.ask__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.ask__title {
  font-size: 16px;
  font-weight: 600;
  color: var(--ink-900);
  margin: 0;
}

.ask__close {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  background: none;
  font: inherit;
  cursor: pointer;

  flex: none;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  color: var(--ink-500);
  font-size: 16px;
}

.ask__close:hover {
  background: var(--grey-050);
}

.ask__field {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  /* 16px MINIMUM. Below it iOS zooms the whole viewport on focus and never zooms back out, which is
   * the same trap the ddx search input documents. */
  font-size: 16px;
  resize: none;

  width: 100%;
  min-height: 120px;
  /* 500 characters is about seven lines in this column; scroll inside rather than push Send off. */
  max-height: 40vh;
  overflow-y: auto;
  padding: 12px;

  border: 1px solid var(--grey-400);
  border-radius: var(--radius-input);
  color: var(--ink-900);
  background: #fff;
}

.ask__field::placeholder {
  color: var(--ink-500);
}

.ask__field:focus {
  outline: none;
  border-color: var(--purple-400);
  box-shadow: 0 0 0 3px var(--purple-100);
}

.ask__counter {
  font-size: 14px;
  color: var(--ink-500);
  text-align: right;
  margin: 0;
}

.ask__counter--full {
  color: var(--red-600);
  font-weight: 600;
}

/* ---- join --------------------------------------------------------------------------------------- */

/* The card, centred by .body's justify-content. 454px is the design width; below that it is the
 * column itself, which is what a phone gets. */
.join {
  display: flex;
  flex-direction: column;
  gap: 48px;
  width: 100%;
  max-width: 454px;
  margin-inline: auto;
  padding: 40px 48px 48px;
  background: #fff;
  border: 1px solid var(--purple-100);
  border-radius: var(--radius-panel);
}

/* 48px of side padding leaves 262px of content on a 390px phone — narrower than the design's own
 * field — so the card keeps its shape and gives the padding back instead. */
@media (max-width: 480px) {
  .join {
    gap: 32px;
    padding: 32px 20px 36px;
  }
}

.join__title {
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  color: var(--navy-900);
  margin: 0;
}

.join__field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.join__label {
  font-size: 16px;
  color: var(--navy-900);
}

.join__pin {
  /* iOS Safari draws an inner shadow on text inputs otherwise. */
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--font);
  font-size: 16px; /* below 16px, iOS zooms the page on focus */
  width: 100%;
  padding: 12px;
  border: 1px solid var(--purple-400);
  border-radius: var(--radius-input);
  color: var(--ink-900);
  background: #fff;
}
.join__pin::placeholder {
  color: var(--ink-500);
}
.join__pin:focus {
  outline: none;
  border-color: var(--purple-700);
  box-shadow: 0 0 0 3px var(--purple-100);
}

/* Height is reserved whether or not there is an error, so a rejected pin does not shunt the button
 * down under the thumb that just pressed it. */
.join__error {
  font-size: 14px;
  color: var(--red-600);
  min-height: 18px;
  margin: 0;
}

/* The design's button is the shared pill at a smaller size: 45px rather than the 55px CTA that sits
 * at the foot of a question card. */
.join__submit {
  width: 100%;
  font-size: 16px;
  padding: 12px 24px;
  min-height: 45px;
}

/* ---- motion ------------------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
