/*
 * One stylesheet for every page, in the Sway app's wireframe language.
 *
 * The grammar, taken from `Sway/static/css/wire.css` and the tokens beside it:
 *
 *   - paper rather than a tinted card; a 1.5px ruled edge and a hard offset
 *     cast — never a blur, never a 16px radius
 *   - mono (Roboto Mono) for anything that labels; IBM Plex Sans speaks
 *   - ink plus one accent. The app stops there, and so does everything on these
 *     pages that is merely structural: the accent points at things and never
 *     labels them.
 *
 * Two functional exceptions, and only two. The app can refuse a danger colour
 * because weight and position tell a signed-in user which button is the
 * destructive one. Here they cannot, for two specific reasons:
 *
 *   - On the participant form the accent is already spoken for. A chosen answer
 *     is an accent-filled chip, so an accent-ruled error would be the same
 *     colour saying "this is your answer" and "this one is missing" on the same
 *     screen, eighteen inches from somebody's face, in a dark room.
 *   - On the organizer page one button deletes every answer an event collected.
 *     It is pressed once, under time pressure, next to a button that downloads
 *     them — and a rule in strong ink is not a different enough thing.
 *
 * So `--sw-danger` and `--sw-alert` exist, declared in both themes and used
 * only as an edge, a tint and a word — never as a filled button, which would
 * put a red block where the language expects paper. A test holds the palette to
 * the app's values plus exactly these two.
 *   - hover deepens the cast and lifts the sheet; pressed flattens it
 *   - heads are hatched, never filled; dividers inside a plate are dashed
 *
 * No hand-drawn ink, for the same reason the app has none: this is a live
 * surface whose boxes move under the reader, and an outline regenerated on
 * every change is not one anybody wants. Ruled edges throughout.
 *
 * The values are restated here rather than imported, which is how the family
 * works — but they are the same numbers. A sixth sheet is fine; a sixth sheet
 * with different numbers is not.
 *
 * Two departures from the app, both because this is a survey on a phone rather
 * than an application with chrome:
 *
 *   - The theme follows the operating system. The app has a toggle in its
 *     navigation bar; there is no navigation bar here to put one in, and a
 *     control that only changes the colours would be the second most prominent
 *     thing on a screen whose whole job is a few questions.
 *   - The fluid root is expressed in rem rather than px, so it scales from
 *     whatever the reader has set as their browser's text size instead of
 *     overriding it. Same formula, same result at the default.
 */

/* ============================================================
   0. The two faces
   ============================================================ */
/*
 * Self-hosted rather than fetched from Google, which the app does. Two hundred
 * people on one venue's wifi should not each be waiting on a third party for
 * the type — and should not each be handing that third party their address, on
 * a survey whose first promise is that nothing identifying is collected.
 * Both are variable fonts, so the whole weight range is one file.
 */
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-sans-latin.woff2') format('woff2');
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
    U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Roboto Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/roboto-mono-latin.woff2') format('woff2');
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
    U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================================
   1. The language
   ============================================================ */
:root {
  color-scheme: light dark;

  --sw-page: #faf9f6;
  --sw-paper: #fffefb;
  --sw-sunken: #f0eeea;
  --sw-sunken-2: #e8e7e2;
  --sw-ink: #55555f;
  --sw-strong: #111118;
  --sw-text: #212529;
  --sw-soft: #3f3f46;
  --sw-muted: #6f6d68;
  --sw-faint: #bab7b0;
  --sw-cast: rgba(60, 50, 40, 0.16);
  --sw-hatch: rgba(60, 50, 40, 0.07);
  --sw-accent: #5a5ad1;
  --sw-accent-deep: #3a3ab1;
  --sw-accent-soft: rgba(90, 90, 209, 0.1);
  /* The accent as a hard cast: the guide's voice is a violet shadow, never a bar. */
  --sw-accent-cast: rgba(90, 90, 209, 0.35);
  /* The logo's own pale blue, for the mark's shading while a participant joins, and nowhere else. */
  --sw-mark-field: #c8e3f5;
  /* Legible on an accent fill: paper in light, ink in dark. */
  --sw-on-accent: #fffefb;

  /* The two functional hues. See the note at the top of this file. */
  --sw-danger: #a8322a;
  --sw-danger-soft: rgba(168, 50, 42, 0.09);
  --sw-danger-cast: rgba(168, 50, 42, 0.3);
  --sw-alert: #96600f;
  --sw-alert-soft: rgba(150, 96, 15, 0.1);

  --sw-mono: 'Roboto Mono', ui-monospace, Menlo, Consolas, monospace;
  --sw-body: 'IBM Plex Sans', Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --radius: 5px;
  --gap: 1rem;
  /* Every control is at least this tall, because that is the size of a thumb. */
  --tap: 2.75rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* True black page, violet-ink — the app's dark values, unchanged. */
    --sw-page: #000000;
    --sw-paper: #15151e;
    --sw-sunken: #0e0e15;
    --sw-sunken-2: #07070c;
    --sw-ink: #a8a8b4;
    --sw-strong: #f1f1f5;
    --sw-text: #dcdce1;
    --sw-soft: #b3b3bc;
    --sw-muted: #8b8b92;
    --sw-faint: #4b4b5a;
    --sw-cast: rgba(226, 226, 255, 0.09);
    --sw-hatch: rgba(226, 226, 255, 0.05);
    --sw-accent: #8a8aff;
    --sw-accent-deep: #b3b3ff;
    --sw-accent-soft: rgba(138, 138, 255, 0.13);
    --sw-accent-cast: rgba(138, 138, 255, 0.4);
    --sw-mark-field: #3d5a73;
    --sw-on-accent: #15151e;

    /* Lifted for a black page, and desaturated so neither shouts. */
    --sw-danger: #f0938a;
    --sw-danger-soft: rgba(240, 147, 138, 0.12);
    --sw-danger-cast: rgba(240, 147, 138, 0.35);
    --sw-alert: #e8b25c;
    --sw-alert-soft: rgba(232, 178, 92, 0.13);
  }
}

* {
  box-sizing: border-box;
}

/*
 * The `hidden` attribute is how every screen here is shown and hidden, and the
 * user-agent rule behind it is only `display: none` at the lowest possible
 * specificity. Any class that sets `display` — `.option` and `.scale` are flex,
 * `.button` is inline-flex — silently beats it, and the element stays on screen
 * with `hidden` set. That is how a "Continue" link meant for the final screen
 * turned up on the waiting screen instead.
 */
[hidden] {
  display: none !important;
}

html {
  /* The page paints its own ground; a short body must not show the canvas. */
  background: var(--sw-page);
  /*
   * The app's fluid root, restated in rem: type grows with the window so a
   * large display reads without zooming, and the floor is whatever the reader
   * has set rather than a fixed 16px. 1rem here is the browser default, so at
   * the default setting this is the app's formula exactly.
   */
  font-size: clamp(1rem, 0.6875rem + 0.55vw, 1.34rem);
  -webkit-text-size-adjust: 100%;
}

body.page {
  margin: 0;
  /*
   * Even on all four sides. It used to carry extra at the bottom to keep the
   * submit button clear of a home indicator; now that the sheet is centred,
   * uneven padding simply shows as a sheet sitting off-centre. The insets add
   * back whatever a notch or an indicator actually needs, on the devices that
   * actually have one.
   */
  padding: max(1.25rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right))
    max(1.25rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));
  background-color: var(--sw-page);
  color: var(--sw-text);
  font-family: var(--sw-body);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  /*
   * A column that fills the window, so a short sheet can sit in the middle of
   * it rather than clinging to the top of an otherwise empty page. `svh`
   * because the dynamic viewport unit moves as mobile browser chrome hides and
   * shows, which would nudge the sheet while somebody is reading it; `vh` first
   * for anything that has not heard of `svh`.
   */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh;
}

/* A thing you read: paper, ruled, cast. */
.card {
  position: relative;
  max-width: 34rem;
  width: 100%;
  /*
   * `auto` on all four sides rather than `justify-content: center` on the
   * column. Auto margins collapse to nothing once the sheet is taller than the
   * window, so a long form scrolls from its first line; centring the column
   * would push the top of an overflowing sheet above the scrollable area and
   * put the first statement out of reach.
   */
  margin: auto;
  padding: 1.5rem 1.6rem 1.4rem;
  background: var(--sw-paper);
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  box-shadow: 3px 3px 0 var(--sw-cast);
}

.page--wide .card {
  max-width: 48rem;
}

/* Headings label a screen, so mono. */
h1,
h2,
h3 {
  font-family: var(--sw-mono);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--sw-strong);
  text-wrap: balance;
}

h1 {
  margin: 0 0 0.75rem;
  font-size: 1.15rem;
  line-height: 1.35;
}

h2 {
  margin: 2rem 0 0.6rem;
  font-size: 0.92rem;
  /* A section head is separated by a dashed rule, never a second box. */
  border-top: 1px dashed var(--sw-faint);
  padding-top: 1.1rem;
}

h3 {
  margin: 0 0 0.3rem;
  font-size: 0.82rem;
}

p {
  margin: 0 0 var(--gap);
  color: var(--sw-soft);
}

.muted {
  color: var(--sw-muted);
  font-size: 0.86rem;
}

.privacy {
  margin-top: 1.4rem;
}

a,
.link {
  color: var(--sw-accent);
  overflow-wrap: anywhere;
}

a:hover {
  color: var(--sw-accent-deep);
}

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

::selection {
  background: var(--sw-accent-soft);
}

/*
 * The bridge message. The one paragraph a participant must actually read, and
 * the methodological content of the second stage — so it is set apart the way
 * the guide speaks in the app: a hatched sheet under a violet cast, rather than
 * a coloured bar. The accent points at it; it does not label it.
 */
.callout {
  padding: 1rem 1.1rem;
  background: var(--sw-hatch);
  color: var(--sw-strong);
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  box-shadow: 3px 3px 0 var(--sw-accent-cast);
  font-size: 0.95rem;
  line-height: 1.6;
}

/* Says what state something is in. Ruled and hatched, never coloured. */
.banner {
  max-width: 34rem;
  margin: 0 auto 0.85rem;
  padding: 0.6rem 0.85rem;
  background: var(--sw-alert-soft);
  color: var(--sw-strong);
  border: 1.5px solid var(--sw-alert);
  border-radius: var(--radius);
  font-family: var(--sw-mono);
  font-size: 0.74rem;
  line-height: 1.5;
}

/*
 * Something the organizer probably did not mean to do. Nothing is broken and
 * nothing is lost, so this is the alert hue rather than the danger one — a
 * tint and an edge, with the text left in ink so it stays legible in a room
 * with the lights down.
 */
.warning {
  padding: 0.8rem 0.95rem;
  background: var(--sw-alert-soft);
  color: var(--sw-strong);
  border: 1.5px solid var(--sw-alert);
  border-radius: var(--radius);
  font-size: 0.9rem;
  font-weight: 600;
}

.notice {
  padding: 0.65rem 0.9rem;
  background: var(--sw-sunken);
  color: var(--sw-strong);
  border: 1px dashed var(--sw-faint);
  border-radius: var(--radius);
  font-size: 0.88rem;
}

/*
 * Something is wrong and the reader has to act. Not the accent: on the
 * participant form the accent is the chosen answer, and one colour cannot mean
 * both "this is what you picked" and "this one is missing". Mono, because it
 * labels a state rather than saying anything.
 */
.error {
  padding: 0.65rem 0.9rem;
  background: var(--sw-danger-soft);
  color: var(--sw-strong);
  border: 1.5px solid var(--sw-danger);
  border-radius: var(--radius);
  box-shadow: 2px 2px 0 var(--sw-danger-cast);
  font-family: var(--sw-mono);
  font-size: 0.78rem;
  font-weight: 500;
  line-height: 1.5;
}

/* ============================================================
   2. The questions
   ============================================================ */

/*
 * A fieldset is the wrong box to rule, twice over. Its own `border-top` is
 * carved into a notch for the legend, and a legend at `width: 100%` carves the
 * whole of it away — the rule is in the stylesheet, the element computes it,
 * and nothing is drawn. An absolutely positioned pseudo-element does not help
 * either: a fieldset resolves one against the anonymous box *below* its legend,
 * so the rule lands between the statement and its own options rather than
 * between one question and the next. (The same two faults are recorded in the
 * family's own notes on this.)
 *
 * So the divider belongs to the legend, which is an ordinary box and takes an
 * ordinary border. It also reads correctly: the rule introduces the statement.
 */
.question {
  margin: 0 0 1.4rem;
  padding: 0;
  border: 0;
}

/*
 * A statement speaks a sentence rather than labelling a region, so it takes the
 * body face at weight — mono is for labels.
 */
.question__text {
  display: block;
  width: 100%;
  padding: 1.1rem 0 0;
  margin-bottom: 0.9rem;
  /* A divider inside a plate is dashed. */
  border-top: 1px dashed var(--sw-faint);
  font-family: var(--sw-body);
  font-weight: 600;
  font-size: 1rem;
  line-height: 1.45;
  letter-spacing: -0.01em;
  color: var(--sw-strong);
  text-wrap: pretty;
}

/* Points at the statement that still needs an answer, in the same hue as the
   message under the button, so the two read as one thing. */
.question--missing .question__text {
  border-top: 1.5px solid var(--sw-danger);
}

.question__error {
  margin: 0.6rem 0 0;
  font-family: var(--sw-mono);
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--sw-danger);
}

.scale {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

/*
 * An answer is a chip: paper, a ruled edge, a small cast, and mono because it
 * labels a position rather than saying anything.
 */
.option {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  min-height: var(--tap);
  padding: 0.45rem 0.85rem;
  background: var(--sw-paper);
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  box-shadow: 2px 2px 0 var(--sw-cast);
  color: var(--sw-strong);
  font-family: var(--sw-mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1.35;
  cursor: pointer;
  transition:
    box-shadow 0.18s ease,
    transform 0.18s ease;
}

/* Hover deepens the cast and lifts the sheet. */
@media (hover: hover) {
  .option:hover {
    box-shadow: 4px 4px 0 var(--sw-cast);
    transform: translate(-1px, -1px);
  }
}

/* Pressed, the sheet goes flat onto the page rather than lifting off it. */
.option:active {
  box-shadow: 1px 1px 0 var(--sw-cast);
  transform: translate(1px, 1px);
}

/* The chosen one takes the accent as its fill, as a selected item does in the app. */
.option:has(.option__input:checked) {
  background: var(--sw-accent);
  border-color: var(--sw-accent);
  color: var(--sw-on-accent);
  font-weight: 700;
}

.option:has(.option__input:focus-visible) {
  outline: 2px solid var(--sw-accent);
  outline-offset: 2px;
}

.option__input {
  width: 1.1rem;
  height: 1.1rem;
  margin: 0;
  flex: none;
  accent-color: var(--sw-accent);
}

/* On the accent fill the native control needs a ground of its own to sit on. */
.option:has(.option__input:checked) .option__input {
  accent-color: var(--sw-on-accent);
}

/* ============================================================
   3. Controls
   ============================================================ */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45em;
  min-height: var(--tap);
  width: 100%;
  padding: 0.55rem 1.05rem;
  background: var(--sw-paper);
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  box-shadow: 2px 2px 0 var(--sw-cast);
  color: var(--sw-strong);
  font-family: var(--sw-mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  line-height: 1.3;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition:
    box-shadow 0.18s ease,
    transform 0.18s ease;
}

.button + .button {
  margin-top: 0.5rem;
}

@media (hover: hover) {
  .button:not(:disabled):hover {
    box-shadow: 4px 4px 0 var(--sw-cast);
    transform: translate(-1px, -1px);
    text-decoration: none;
  }
}

.button:not(:disabled):active {
  box-shadow: 1px 1px 0 var(--sw-cast);
  transform: translate(1px, 1px);
}

/* The one ask on a screen takes the accent as its fill. */
.button--primary {
  background: var(--sw-accent);
  border-color: var(--sw-accent);
  color: var(--sw-on-accent);
  font-weight: 700;
}

/*
 * Destructive. Ruled and lettered in the danger hue, not filled with it: a red
 * block would put a shape on the page that the language has nowhere else, and
 * this button sits directly beneath one that merely downloads a file. Paper,
 * an unmistakable edge, and the weight.
 */
.button--danger {
  border-color: var(--sw-danger);
  border-width: 2px;
  color: var(--sw-danger);
  font-weight: 700;
}

@media (hover: hover) {
  .button--danger:not(:disabled):hover {
    box-shadow: 4px 4px 0 var(--sw-danger-cast);
  }
}

.button--danger:not(:disabled):active {
  box-shadow: 1px 1px 0 var(--sw-danger-cast);
}

.button--quiet {
  width: auto;
  min-height: 2.1rem;
  padding: 0.35rem 0.7rem;
  font-size: 0.7rem;
  box-shadow: none;
}

.button:disabled {
  border-color: var(--sw-faint);
  color: var(--sw-soft);
  opacity: 0.55;
  box-shadow: 2px 2px 0 var(--sw-cast);
  transform: none;
  cursor: default;
}

/*
 * In flight. The caption is left alone: it says what the button does, it is
 * derived from server state, and the moment a second thing writes to it the
 * two disagree — see `whileBusy` in `src/client/dom.ts`. So the wait is shown
 * beside the caption instead of on top of it. `gap` on `.button` spaces it.
 *
 * Static, not a spinner. Every one of these calls is a single round trip to
 * the same origin, and an animation would be the only moving thing on a page
 * whose whole language is drawn flat.
 */
.button[data-busy]::after {
  content: '···';
  letter-spacing: 0.1em;
}

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

.field {
  display: block;
  margin: 0 0 0.9rem;
}

/* A field label is a label, so mono. */
/* A line under a field saying what the answer is for. Speaks, so the body face. */
.field__hint {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  line-height: 1.5;
}

.field__label {
  display: block;
  margin-bottom: 0.35rem;
  font-family: var(--sw-mono);
  font-weight: 700;
  font-size: 0.74rem;
  letter-spacing: 0.01em;
  color: var(--sw-strong);
}

input[type='text'],
input[type='password'],
input[type='url'],
input[type='email'],
textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 0.5rem 0.7rem;
  background-color: var(--sw-paper);
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  box-shadow: 2px 2px 0 var(--sw-cast);
  color: var(--sw-strong);
  font-family: var(--sw-body);
  font-size: 0.95rem;
  resize: vertical;
  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

input:focus,
textarea:focus {
  border-color: var(--sw-accent);
  outline: 2px solid var(--sw-accent-soft);
  outline-offset: 1px;
}

/*
 * Scoped to the fields that are drawn as paper, matching the block above. A
 * bare `input:disabled` would also reach the checkbox, which has no paper to
 * sink and dims through its own label instead — and would dim it twice.
 */
input[type='text']:disabled,
input[type='password']:disabled,
input[type='url']:disabled,
input[type='email']:disabled,
textarea:disabled {
  background-color: var(--sw-sunken);
  box-shadow: none;
  opacity: 0.7;
}

/*
 * A switch, not a chip. The participant form's `.option` is filled with the
 * accent when chosen, because there a choice is the answer and is the thing on
 * the screen; here it is one setting among several inside a ruled group, and a
 * filled block would make it the loudest thing on the page. So: the native box,
 * with the accent only where the accent already goes — the control itself.
 */
.check {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  min-height: var(--tap);
  padding: 0.4rem 0;
  cursor: pointer;
}

.check input[type='checkbox'],
.check input[type='radio'] {
  width: 1.1rem;
  height: 1.1rem;
  /* Aligns the box with the first line of the label rather than its middle. */
  margin: 0.28rem 0 0;
  flex: none;
  accent-color: var(--sw-accent);
}

.check__text {
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--sw-soft);
  text-wrap: pretty;
}

.check:has(input:disabled) {
  cursor: default;
  opacity: 0.7;
}

.fieldgroup {
  margin: 0 0 1.3rem;
  padding: 1rem;
  border: 1px dashed var(--sw-faint);
  border-radius: var(--radius);
}

/* A legend labels the region under it, so mono, tracked, in the accent. */
.fieldgroup legend {
  padding: 0 0.4rem;
  font-family: var(--sw-mono);
  font-weight: 500;
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sw-accent);
}

/* ============================================================
   4. The admin pages
   ============================================================ */

.dashboard__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gap);
  flex-wrap: wrap;
}

/*
 * Whose session this is. Set in the mono face because it is a machine-known
 * fact rather than something the page is saying — the same reason the stage
 * states and the counts are lettered that way — and pushed to the right so the
 * heading keeps the left edge it shares with every other page.
 */
.dashboard__identity {
  margin: 0;
  margin-inline-start: auto;
  color: var(--sw-soft);
  font-family: var(--sw-mono);
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  overflow-wrap: anywhere;
}

.counts {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.65rem;
  margin: 0 0 0.85rem;
}

.counts__item {
  padding: 0.7rem 0.8rem;
  background: var(--sw-paper);
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  box-shadow: 2px 2px 0 var(--sw-cast);
}

.counts dt {
  font-family: var(--sw-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sw-muted);
}

.counts dd {
  margin: 0.15rem 0 0;
  font-family: var(--sw-mono);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--sw-strong);
  font-variant-numeric: tabular-nums;
}

/* The evening's one control: a ruled sheet with the next step on it. */
.step {
  padding: 1rem 1.1rem 1.1rem;
  margin-bottom: 0.85rem;
  background: var(--sw-paper);
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  box-shadow: 3px 3px 0 var(--sw-cast);
}

/* "Step 2 of 3" labels, so mono, tracked, in the accent. */
.step__label {
  margin: 0 0 0.25rem;
  font-family: var(--sw-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sw-accent);
}

.step__title {
  margin: 0 0 0.5rem;
}

.step__detail {
  margin: 0 0 0.9rem;
  color: var(--sw-soft);
  text-wrap: pretty;
}

.step .button--primary {
  width: 100%;
}

.step__aside {
  margin: 0.6rem 0 0;
}

/*
 * Tabs, so the night's screen is the counts and the one button, and the rest
 * is one press away rather than a scroll. Ruled like everything else: the
 * chosen tab is joined to its panel by losing the rule beneath it.
 */
.tabs {
  margin-top: 1.4rem;
}

.tabs__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  border-bottom: 1.5px solid var(--sw-ink);
}

.tabs__tab {
  margin-bottom: -1.5px;
  padding: 0.55rem 0.9rem;
  background: transparent;
  border: 1.5px solid transparent;
  border-bottom-color: var(--sw-ink);
  border-radius: var(--radius) var(--radius) 0 0;
  font-family: var(--sw-mono);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--sw-muted);
  cursor: pointer;
}

.tabs__tab:hover {
  color: var(--sw-strong);
}

.tabs__tab[aria-selected='true'] {
  background: var(--sw-paper);
  border-color: var(--sw-ink);
  border-bottom-color: var(--sw-paper);
  color: var(--sw-accent);
}

.tabs__tab:focus-visible {
  outline: 2px solid var(--sw-accent);
  outline-offset: 2px;
}

.tabs__panel {
  padding-top: 0.6rem;
}

.tabs__panel > h2:first-child {
  margin-top: 0.6rem;
}

/* Where each stage stands, under the step, for the whole picture at a glance. */
.stage-status {
  display: grid;
  gap: 0.35rem;
  margin: 0 0 0.85rem;
}

.stage-status__row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.75rem;
  align-items: baseline;
}

.stage-status dt {
  font-family: var(--sw-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sw-muted);
}

.stage-status dd,
.stage__state {
  margin: 0;
  font-family: var(--sw-mono);
  font-size: 0.72rem;
  color: var(--sw-muted);
}

/* Open is the state that matters, so the accent points at it. */
.stage__state--open {
  color: var(--sw-accent);
  font-weight: 700;
}

/* The organizer's editable lists: the statements and the response labels. */
.entries {
  display: grid;
  gap: 0.5rem;
  margin-bottom: 0.7rem;
}

.entries__row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.entries__index {
  flex: none;
  width: 1.5rem;
  text-align: right;
  font-family: var(--sw-mono);
  font-size: 0.78rem;
  color: var(--sw-muted);
  font-variant-numeric: tabular-nums;
}

.entries__remove {
  flex: none;
  width: var(--tap);
  min-height: var(--tap);
  padding: 0;
  font-size: 1rem;
}

/*
 * The code stays on white in both themes. It is read by a camera, and inverting
 * it for dark mode is the one change here that would stop the app working.
 */
.qr {
  display: flex;
  justify-content: center;
  padding: 1rem;
  background: #ffffff;
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  box-shadow: 3px 3px 0 var(--sw-cast);
}

/* Large enough to scan from the back of a room when the page is projected. */
.qr svg {
  display: block;
  width: 100%;
  max-width: 24rem;
  height: auto;
}

.activity {
  margin: 0 0 var(--gap);
  padding: 0;
  list-style: none;
  font-family: var(--sw-mono);
  font-size: 0.72rem;
  color: var(--sw-muted);
}

.activity li {
  display: flex;
  gap: 0.7rem;
  padding: 0.35rem 0;
  border-bottom: 1px dashed var(--sw-faint);
}

.activity time {
  flex: none;
  font-variant-numeric: tabular-nums;
}

.danger__heading {
  border-top-color: var(--sw-danger);
  color: var(--sw-danger);
}

.danger {
  padding: 1rem;
  background: var(--sw-paper);
  border: 2px solid var(--sw-danger);
  border-radius: var(--radius);
  box-shadow: 3px 3px 0 var(--sw-danger-cast);
}

code {
  padding: 0.1rem 0.35rem;
  background: var(--sw-sunken);
  border-radius: 3px;
  font-family: var(--sw-mono);
  font-size: 0.85em;
  color: var(--sw-strong);
}

/* ============================================================
   5. Cards, the switch, and the admin forms
   ============================================================ */

.eyebrow {
  margin: 0 0 0.35rem;
  font-family: var(--sw-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sw-accent);
}

.reveal {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
}

.reveal input {
  flex: 1 1 auto;
  min-width: 0;
}

.reveal .button {
  min-height: var(--tap);
}

/* The host and path, set before the one segment the organizer may choose. */
.slug {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.slug__prefix {
  flex: none;
  font-family: var(--sw-mono);
  font-size: 0.78rem;
  color: var(--sw-muted);
}

/* The panic button, on a sheet ruled in the danger hue so it is found without looking. */
.kill {
  margin: 1rem 0 0;
  padding: 1rem;
  background: var(--sw-paper);
  border: 2px solid var(--sw-danger);
  border-radius: var(--radius);
  box-shadow: 3px 3px 0 var(--sw-danger-cast);
}

.kill__text {
  margin: 0 0 0.75rem;
  font-size: 0.9rem;
}

.demos {
  display: grid;
  gap: 0.85rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.demo {
  padding: 0.9rem 1rem 1rem;
  background: var(--sw-paper);
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  box-shadow: 3px 3px 0 var(--sw-cast);
}

/* A link that is on points at itself with the accent. */
.demo--on {
  box-shadow: 3px 3px 0 var(--sw-accent-cast);
}

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

.demo__state {
  flex: none;
  font-family: var(--sw-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sw-muted);
}

.demo--on .demo__state {
  color: var(--sw-accent);
}

.demo__address {
  margin: 0.35rem 0 0.2rem;
  font-family: var(--sw-mono);
  font-size: 0.9rem;
}

.demo__meta {
  margin: 0 0 0.7rem;
  font-size: 0.78rem;
}

.demo__password-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: flex-end;
  margin-top: 0.4rem;
}

.demo__password-field {
  flex: 1 1 12rem;
  margin: 0;
}

.demo__password-form .button {
  width: auto;
}

.demo__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.demo__actions .button {
  width: auto;
}

/* Facts about an event's Sway hookup: a term, and what it is set to. */
.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.35rem 1rem;
  margin: 0 0 var(--gap);
}

.facts dt {
  font-family: var(--sw-mono);
  font-size: 0.74rem;
  font-weight: 700;
  color: var(--sw-strong);
}

.facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

select {
  width: 100%;
  min-height: var(--tap);
  padding: 0.5rem 0.7rem;
  background-color: var(--sw-paper);
  border: 1.5px solid var(--sw-ink);
  border-radius: var(--radius);
  color: var(--sw-strong);
  font-family: var(--sw-body);
  font-size: 0.95rem;
}

.org__admins li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.org__admins .button {
  width: auto;
  min-height: 0;
}

/* ============================================================
   6. Wider than a phone
   ============================================================ */

@media (min-width: 40rem) {
  .counts {
    grid-template-columns: repeat(4, 1fr);
  }

  .stages {
    grid-template-columns: repeat(2, 1fr);
  }

  .button {
    width: auto;
  }

  .button + .button {
    margin-top: 0;
    margin-left: 0.5rem;
  }

  /* The participant's one button stays full width: it is the only thing on
     the screen at that moment and a thumb should not have to aim. */
  #submit,
  #join-button {
    width: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }

  .option:hover,
  .option:active,
  .button:hover,
  .button:active {
    transform: none;
  }
}

/* ============================================================
   The entrance: joining, between the form and Sway
   ============================================================ */
/*
 * The mark draws itself, then "Welcome to Sway" rises a word at a time and
 * "Sway" is underlined by the same hand. The page leaves for Sway when the
 * underline's second pass ends (`#enter-finale`), so the timings below are
 * the length of the moment.
 *
 * Each animated element's resting rule is its finished state, and the
 * animation fills `backwards` from its start. With motion reduced the
 * animations are removed and everything is simply there.
 */
.enter {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(1.4rem, 4.5svh, 2.6rem);
  padding: 1.5rem 16px;
  background: var(--sw-page);
  text-align: center;
  animation: enter-in 0.14s ease-out backwards;
}

@keyframes enter-in {
  from {
    opacity: 0;
  }
}

.mark {
  /* Where the shading's own order starts: the mark has 22 strokes of ink. */
  --ink: 22;
  display: block;
  width: min(52vw, 30svh, 13rem);
  height: auto;
  overflow: visible;
}

.mark path {
  fill: none;
  stroke: var(--sw-strong);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  animation: draw 0.42s cubic-bezier(0.4, 0.05, 0.25, 1) backwards;
  animation-delay: calc(0.08s + var(--i, 0) * 0.035s);
}

/* The second pass over the bubble, lighter, the way a hand goes over a line it means. */
.mark .mark__ink--2 {
  stroke-width: 2.3;
  stroke-opacity: 0.5;
}

/* The field goes on as hatch, after the outline and quicker than it. */
.mark .mark__shade {
  stroke: var(--sw-mark-field);
  stroke-width: 9;
  stroke-opacity: 0.9;
  stroke-linecap: butt;
  animation-duration: 0.28s;
  animation-delay: calc(0.08s + var(--ink) * 0.035s + (var(--i, 0) - var(--ink)) * 0.012s);
}

/*
 * A finished line carries no dash: `stroke-dasharray: 1` against
 * `pathLength="1"` can resolve a hair short and render the stroke as nothing.
 */
@keyframes draw {
  from {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
  }
  to {
    stroke-dasharray: 1;
    stroke-dashoffset: 0;
  }
}

.enter__words {
  margin: 0;
  font-family: var(--sw-mono);
  font-weight: 700;
  font-size: clamp(1.9rem, 8.4vw, 3.3rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--sw-strong);
}

/* Each word rises out of its own line, clipped at the baseline. */
.enter__word {
  position: relative;
  display: inline-block;
  padding-bottom: 0.08em;
  vertical-align: bottom;
  clip-path: inset(-0.2em -0.2em 0 -0.2em);
}

.enter__rise {
  display: inline-block;
  animation: rise 0.72s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
  animation-delay: 1.3s;
}

.enter__words > :nth-child(2) .enter__rise {
  animation-delay: 1.44s;
}

.enter__words > :nth-child(3) .enter__rise {
  animation-delay: 1.58s;
}

@keyframes rise {
  from {
    transform: translateY(105%);
  }
}

/* "Sway" in the accent, with the underline hung outside the word's clip. */
.enter__sway {
  position: relative;
  display: inline-block;
  vertical-align: bottom;
  color: var(--sw-accent);
}

/*
 * Sized in em, so it scales evenly with the word: a stretched SVG, or a
 * non-scaling stroke, makes Safari misplace the dash and show the line early.
 */
.enter__underline {
  position: absolute;
  left: -0.1em;
  bottom: -0.34em;
  width: 2.6em;
  height: 0.4em;
  overflow: visible;
}

.enter__underline path {
  fill: none;
  stroke: var(--sw-accent);
  stroke-width: 2.6;
  stroke-linecap: round;
  animation: draw 0.46s cubic-bezier(0.4, 0.05, 0.25, 1) backwards;
  animation-delay: 1.98s;
}

.enter__underline .enter__underline-2 {
  stroke-width: 1.8;
  stroke-opacity: 0.5;
  animation-delay: 2.16s;
}

/* Only if Sway is slower than the moment: said quietly, once it is over. */
.enter__wait {
  margin: 0;
  font-family: var(--sw-mono);
  font-size: 0.74rem;
  letter-spacing: 0.04em;
  color: var(--sw-muted);
  animation: enter-in 0.4s ease-out backwards;
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }

  .button:hover,
  .button:active {
    transform: none;
  }
}
