/* 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;
}

: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;

  --grey-050: #f5f5f5;
  --grey-400: #a3a3a3;

  /* Read from the screenshots — the CSS export covered only the breadcrumb, chips and DDx card. */
  --amber-050: #fef7e0;
  --amber-400: #e9a23b;
  --peach-050: #fdf1e7;
  --peach-400: #de8034;
  --green-050: #eaf7ec;
  --green-600: #2e9e4f;
  --red-050: #fdecec;
  --red-600: #d64545;

  --radius-pill: 9999px;
  --radius-panel: 24px;
  --radius-card: 16px;
  --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;

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

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

*,
*::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;
}

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

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

.stage--staged,
.stage--opening,
.stage--result {
  background: var(--purple-050);
}
.stage--opening {
  box-shadow: var(--shadow-active);
}
.stage--active {
  background: var(--amber-050);
}
.stage--locked {
  background: var(--peach-050);
}

/* 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;
}

/* ---- masthead ---------------------------------------------------------------------------------- */

.masthead {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

/* The title and position, as a column, beside the ask trigger.
 *
 * min-width: 0 is load-bearing, not defensive: a flex child defaults to min-width: auto, which
 * refuses to shrink below its content, so without this a long case title pushes the Ask button off
 * the right edge of a phone and the title's own line-clamp never engages. Invisible with a short
 * title, unmistakable with a real one. */
.masthead__main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* A compact pill wearing .chip--current's palette, so it reads as chrome that always belonged rather
 * than a button bolted on. It is NOT .btn: that is the 55px primary CTA at the foot of a card, and
 * borrowing it here would put a second full-width call to action above the question.
 *
 * Every scrap of UA chrome has to go — same reason as .option below. */
.masthead__ask {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  font: inherit;
  cursor: pointer;

  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  /* 44px of touch target on a phone held one-handed, without making the pill look oversized. */
  min-height: 44px;

  border-radius: var(--radius-pill);
  background: var(--purple-050);
  color: var(--purple-900);
  box-shadow: 0 0 0 1px var(--purple-400);
  font-size: 14px;
  font-weight: 600;
}

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

.masthead__ask-icon {
  flex: none;
  width: 18px;
  height: 18px;
}

.masthead__title {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-500);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

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

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

/* Changes up to once a second. Deliberately NOT a live region — announcing it would make the page
 * unusable with a screen reader. */
.masthead__people {
  font-size: 14px;
  color: var(--ink-500);
  white-space: nowrap;
}

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

.chips {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  /* The current chip draws a ring outside its box; without the padding an overflow container
   * shears it, and overflow-x also makes the browser clip the y-axis.
   *
   * The horizontal margin must stay `auto`: a `margin: -4px 0` shorthand here silently overrides the
   * centring `.stage > *` sets, which is invisible on a phone and unmistakable on a desktop. */
  padding: 4px 0;
  margin: -4px auto;
}
.chips::-webkit-scrollbar {
  display: none;
}

.chip {
  flex: none;
  scroll-snap-align: start;
  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;
}

.chip--current {
  background: var(--purple-050);
  color: var(--purple-900);
  box-shadow: 0 0 0 1px var(--purple-400);
}

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

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

.card {
  background: #fff;
  border-radius: var(--radius-card);
  padding: 24px 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.card__count {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-900);
}

.card__question {
  font-size: 16px;
  font-weight: 400;
  color: var(--ink-900);
  margin: 0;
}
.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;
}

/* A ddx offers the whole diagnosis catalog, so its list scrolls inside the card rather than pushing
 * the search field and Submit off the screen. ~6 rows at 390x844. */
.options--scroll {
  max-height: 46vh;
  overflow-y: auto;
  /* overflow-y also clips the x-axis, so the ring needs horizontal room too — the padding above
   * provides it, which is why it must not be removed. */
  overscroll-behavior: contain;
}

/* ---- the diagnosis search ---------------------------------------------------------------------- */

.search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border: 1px solid var(--purple-100);
  border-radius: var(--radius-input);
  background: #fff;
}
.search:focus-within {
  border-color: var(--purple-700);
  box-shadow: 0 0 0 3px var(--purple-100);
}

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

.search__input {
  /* iOS Safari draws an inner shadow and a native clear affordance otherwise. */
  appearance: none;
  -webkit-appearance: none;
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  padding: 12px 0;
  font-family: var(--font);
  font-size: 16px; /* below 16px, iOS zooms the page on focus */
  color: var(--ink-900);
  background: transparent;
}
.search__input::placeholder {
  /* --grey-400 would be 2.5:1 on white; placeholders still have to be readable. */
  color: var(--ink-500);
}
.search__input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.search__clear {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  border: 0;
  background: transparent;
  padding: 6px;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  color: var(--ink-500);
  cursor: pointer;
}
.search__clear:hover {
  color: var(--ink-900);
}

.search__count {
  font-size: 13px;
  color: var(--ink-500);
  margin: 0;
}

.empty {
  font-size: 14px;
  color: var(--ink-700);
  margin: 0;
}

/* What the attendee is about to submit. With 106 options and a filter that can hide the chosen row,
 * an enabled Submit button with nothing visible to explain it is a trap rather than a filter. */
.card__picked {
  font-size: 14px;
  font-weight: 600;
  color: var(--purple-700);
  margin: 0;
}

/* 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;
  /* Wraps only when a result screen unhides `.option__note`, which claims a full row of its own. */
  flex-wrap: wrap;
  gap: 12px;
  width: 100%;
  padding: 12px;
  min-height: 55px;

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

.option__letter {
  flex: none;
  align-self: flex-start;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  background: var(--purple-050);
  color: var(--purple-900);
  font-size: 14px;
  font-weight: 600;
}

.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);
}

.option--selected {
  border-color: var(--purple-700);
  box-shadow: 0 0 0 1px var(--purple-700);
}

.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-color: var(--purple-700);
}
.option--selected:disabled .option__letter {
  background: var(--purple-050);
  color: var(--purple-900);
}

/* 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 badge stays grey. */
.option--correct,
.option--correct:disabled {
  background: var(--green-050);
  border-color: var(--green-600);
  color: var(--ink-900);
}
.option--correct .option__letter,
.option--correct:disabled .option__letter {
  background: var(--green-600);
  color: #fff;
}

.option--wrong,
.option--wrong:disabled {
  background: var(--red-050);
  border-color: var(--red-600);
  color: var(--ink-900);
}
.option--wrong .option__letter,
.option--wrong:disabled .option__letter {
  background: var(--red-600);
  color: #fff;
}

/* The phrase a dialogue's presenter revealed — the one the case continues with. Purple, not green:
 * a dialogue has no right answer, so a correctness colour here would be the defect this screen was
 * built to remove. Declared LAST on purpose — `.option--selected:disabled .option__letter` and
 * `.option--chosen:disabled .option__letter` are both (0,3,0), so a row that is BOTH the presenter's
 * pick and the attendee's own resolves on source order. */
.option--chosen,
.option--chosen:disabled {
  background: var(--purple-050);
  border-color: var(--purple-700);
  color: var(--ink-900);
}
.option--chosen .option__letter,
.option--chosen:disabled .option__letter {
  background: var(--purple-700);
  color: #fff;
}

/* The dialogue result lists EVERY phrase, so for the first time rows ship with no state modifier at
 * all and inherit `.option:disabled`'s grey — 2.5:1 on white, below AA. On the select result this
 * never showed, because only correct/wrong rows are drawn and both restore their own colour. These
 * rows are data the room produced, not controls that are unavailable: a phrase somebody voted for
 * must not look as dead as one nobody chose.
 *
 * The marked rows are excluded by :not() rather than out-specified: this selector is (0,5,0) and
 * would otherwise clobber `.option--chosen`'s purple badge (0,3,0) — the same specificity trap,
 * running the other way. */
.verdict--room .option:disabled:not(.option--chosen):not(.option--selected) {
  color: var(--ink-700);
}
.verdict--room .option:disabled:not(.option--chosen):not(.option--selected) .option__letter {
  background: var(--grey-050);
  color: var(--ink-700);
}

/* Filled by the dialogue result only. `flex: none` keeps a four-digit count from squeezing the
 * phrase, and the note takes a full row of its own so a long phrase never fights it for width. */
.option__count {
  flex: none;
  align-self: flex-start;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-500);
  font-variant-numeric: tabular-nums;
}

.option__note {
  flex-basis: 100%;
  /* The letter badge is 28px and the row gap is 12px, so the note lines up under the phrase. */
  margin-left: 40px;
  font-size: 13px;
  font-weight: 600;
  color: var(--purple-700);
}

/* ---- 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,
.beat__art > .bars {
  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;
}

.beat--opening .beat__headline {
  color: var(--purple-900);
}
.beat--locked .beat__headline {
  color: var(--peach-400);
}

/* The "poll is starting" bars. Purely decorative, so it is hidden from assistive tech in the markup. */
.bars {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  height: 96px;
}
.bars span {
  width: 24px;
  border-radius: 4px;
  background: var(--purple-400);
  animation: bar 1.2s ease-in-out infinite;
}
.bars span:nth-child(1) {
  height: 60%;
  background: var(--purple-400);
}
.bars span:nth-child(2) {
  height: 100%;
  background: var(--purple-700);
  animation-delay: 0.15s;
}
.bars span:nth-child(3) {
  height: 76%;
  background: var(--purple-900);
  animation-delay: 0.3s;
}

@keyframes bar {
  0%,
  100% {
    transform: scaleY(1);
  }
  50% {
    transform: scaleY(0.72);
  }
}

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

.verdict {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.verdict__head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.verdict__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 12px;
}
.verdict--correct .verdict__mark {
  background: var(--green-050);
  color: var(--green-600);
}
.verdict--wrong .verdict__mark {
  background: var(--red-050);
  color: var(--red-600);
}
.verdict--none .verdict__mark {
  background: var(--grey-050);
  color: var(--ink-500);
}
/* A dialogue's result. Brand purple, because this card reports what happens next rather than
 * whether anyone was right. */
.verdict--room .verdict__mark {
  background: var(--purple-050);
  color: var(--purple-700);
}
.verdict__mark svg {
  width: 32px;
  height: 32px;
}

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

.verdict__tally {
  font-size: 14px;
  font-weight: 600;
  color: var(--green-600);
  margin: 0;
}
/* Green is a correctness colour and this card has no correct answer. Must be scoped to the card
 * root: on an inner node it stops out-specifying the rule above and the tally silently goes green
 * again, which would have the screen claiming a right answer after all. */
.verdict--room .verdict__tally {
  color: var(--ink-500);
}

.verdict__label {
  font-size: 14px;
  color: var(--ink-500);
  margin: 0;
}

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

/* ---- 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;
}

.toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 10;

  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  max-width: calc(100vw - 32px);

  background: #fff;
  border-radius: var(--radius-pill);
  box-shadow: 0 4px 16px rgb(0 0 0 / 12%);
  font-size: 16px;
  font-weight: 600;

  animation: toast-in 200ms ease;
}

/* Without this the span shrinks as a flex item and "Answer Submitted" breaks across two lines. */
.toast__text {
  white-space: nowrap;
}

.toast__dot {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--green-600);
}
.toast--error .toast__dot {
  background: var(--red-600);
}

.toast__close {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  background: none;
  font: inherit;
  cursor: pointer;
  padding: 0 0 0 4px;
  color: var(--ink-500);
  line-height: 1;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translate(-50%, 12px);
  }
}

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

.join {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  text-align: center;
}

.join__brand {
  font-size: 18px;
  font-weight: 600;
  color: var(--purple-700);
}

.join__pin {
  /* iOS Safari draws an inner shadow on text inputs otherwise. */
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--font);
  font-size: 36px;
  font-weight: 600;
  letter-spacing: 0.35em;
  text-indent: 0.35em;
  text-align: center;
  width: 100%;
  max-width: 260px;
  padding: 16px;
  border: 1px solid var(--purple-400);
  border-radius: var(--radius-input);
  color: var(--ink-900);
  background: #fff;
}
.join__pin:focus {
  outline: none;
  border-color: var(--purple-700);
  box-shadow: 0 0 0 3px var(--purple-100);
}

.join__error {
  font-size: 14px;
  color: var(--red-600);
  min-height: 18px;
  margin: 0;
}

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

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