/* theme: nocturne — The mecha palette. Dark-first, violet accent, amber for anything called out. */
:root {
  color-scheme: light dark;
  --font-sans: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --radius: 8px;
  --ground: #ffffff;
  --surface: #f7f7fa;
  --text: #1a1a22;
  --muted: #5a5a68;
  --line: #dcdce4;
  --accent: #5d5294;
  --on-accent: #ffffff;
  --ring: #9184d9;
  --signal: #96601f;
}
@media (prefers-color-scheme: dark) {
  :root {
    --ground: #12141f;
    --surface: #161826;
    --text: #e9e9ed;
    --muted: #9a9aa8;
    --line: #2b2741;
    --accent: #b5abfc;
    --on-accent: #12141f;
    --ring: #9184d9;
    --signal: #e8a24a;
  }
}
:root[data-theme="light"] {
  --ground: #ffffff;
  --surface: #f7f7fa;
  --text: #1a1a22;
  --muted: #5a5a68;
  --line: #dcdce4;
  --accent: #5d5294;
  --on-accent: #ffffff;
  --ring: #9184d9;
  --signal: #96601f;
}
:root[data-theme="dark"] {
  --ground: #12141f;
  --surface: #161826;
  --text: #e9e9ed;
  --muted: #9a9aa8;
  --line: #2b2741;
  --accent: #b5abfc;
  --on-accent: #12141f;
  --ring: #9184d9;
  --signal: #e8a24a;
}

/* mecha-manifest — the structural sheet. One column, one layout, every colour
   a token. Nothing here is a hue: a theme supplies the values and adds no
   rules, which is what makes changing the look a swap rather than a rewrite.

   No @import and no hosted font. An imported stylesheet is an external
   reference — the gate blocks it under `style-src 'self'`, and the publish
   gate fails a bundle for one. Vendoring a woff2 and serving it from our own
   origin is the supported route. */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

main {
  /* A readable measure, not a mobile one. Forms narrow themselves below;
     pages that carry tables widen themselves further down. */
  max-width: 46rem;
  margin: 0 auto;
  padding: 3rem 1.5rem 6rem;
}

/* A form is read one line at a time, and a long measure makes a column of
   inputs feel like a table — so the form element keeps the old narrow
   measure inside whatever page it is on. */
main > form { max-width: 34rem; }

/* The account page's ledgers are tables, and tables want room. `:has` is
   the page classifying itself: no markup change, no second stylesheet. */
main:has(table) { max-width: 72rem; }

/* Commands a person copies — the pairing instruction — wrap instead of
   walking off the edge of the screen. */
pre {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

h1 {
  font-size: 1.75rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.2;
  margin: 0 0 0.75rem;
}

.intro {
  color: var(--muted);
  margin: 0 0 2.5rem;
  font-size: 1.0625rem;
}

/* --- fields ------------------------------------------------------------- */

.field { margin: 0 0 1.75rem; }
.field[hidden] { display: none; }

label {
  display: block;
  /* Mono, deliberately. A field label names a value rather than describing
     one, which is nearer to code than to prose — and it is the single choice
     that stops a generated form looking generic. */
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  margin: 0 0 0.5rem;
}

.req {
  color: var(--muted);
  font-weight: 400;
}

.help {
  color: var(--muted);
  font-size: 0.875rem;
  margin: -0.25rem 0 0.5rem;
}

input[type=text], input[type=email], input[type=url], input[type=date],
input[type=number], input[type=tel], select, textarea {
  display: block;
  width: 100%;
  padding: 0.625rem 0.75rem;
  font: inherit;
  font-size: 0.9375rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color .12s ease, box-shadow .12s ease;
}

textarea { min-height: 7rem; resize: vertical; line-height: 1.6; }

input::placeholder, textarea::placeholder { color: var(--muted); opacity: .7; }

input[type=file] {
  display: block;
  width: 100%;
  padding: 0.625rem 0.75rem;
  font: inherit;
  font-size: 0.875rem;
  color: var(--text);
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

input[type=file]::file-selector-button {
  font: inherit;
  font-size: 0.875rem;
  color: var(--text);
  background: var(--ground);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.375rem 0.75rem;
  margin-right: 0.75rem;
  cursor: pointer;
}

.file-limits {
  color: var(--muted);
  font-size: 0.8125rem;
  margin: 0.375rem 0 0;
}

/* The submission form's stand-in for a file input: the upload comes after
   verification, and this is where the form says so. */
.file-note {
  color: var(--muted);
  font-size: 0.875rem;
  padding: 0.625rem 0.75rem;
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  margin: 0;
}

/* The chevron is drawn here rather than left to the platform, because a
   native select arrow is the one control that makes a styled form look
   half-styled. `img-src data:` covers it; nothing is fetched. */
select {
  appearance: none;
  padding-right: 2.25rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%239a9aa8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 1rem;
}

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

/* One focus treatment everywhere: a ring outside the border rather than an
   outline on it, so nothing shifts by a pixel when a field takes focus. */
input:focus-visible, select:focus-visible, textarea:focus-visible,
button:focus-visible, a:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 35%, transparent);
}

/* --- checkboxes --------------------------------------------------------- */

.choices { display: grid; gap: 0.625rem; margin-top: 0.25rem; }

.ack label, .choice {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.625rem;
  align-items: start;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 400;
  letter-spacing: 0;
  cursor: pointer;
}

input[type=checkbox], input[type=radio] {
  appearance: none;
  width: 1.125rem;
  height: 1.125rem;
  margin: 0.19rem 0 0;
  flex: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
}
input[type=radio] { border-radius: 50%; }

input[type=checkbox]:checked, input[type=radio]:checked {
  background: var(--accent);
  border-color: var(--accent);
}
input[type=checkbox]:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ffffff' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-6'/%3E%3C/svg%3E");
  background-size: 0.875rem;
  background-position: center;
  background-repeat: no-repeat;
}

/* --- steps -------------------------------------------------------------- */

fieldset {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  margin: 0 0 2rem;
  background: color-mix(in srgb, var(--surface) 45%, transparent);
}

legend {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding: 0 0.5rem;
}

/* --- what went wrong ---------------------------------------------------- */

/* The signal colour marks and never fills. A field that failed gets a rule
   and a word, not a coloured panel — the palette has one accent and treats
   amber as a called-out line rather than a background. */
[aria-invalid=true] { border-color: var(--signal); }

.error {
  color: var(--signal);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  margin: 0.5rem 0 0;
}

.errors {
  border-left: 2px solid var(--signal);
  padding: 0.25rem 0 0.25rem 1rem;
  margin: 0 0 2rem;
}
.errors p { margin: 0 0 0.25rem; font-weight: 500; }
.errors ul { margin: 0; padding-left: 1.1rem; color: var(--muted); font-size: 0.9375rem; }

/* --- submit ------------------------------------------------------------- */

button {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 500;
  padding: 0.6875rem 1.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
  transition: opacity .12s ease;
}
button:hover { opacity: .88; }
button:active { opacity: .96; }

a { color: var(--accent); text-underline-offset: 2px; }

/* The confirmation and error pages share the shell, so they get the same
   measure and rhythm without a second stylesheet. */
main > h1:only-of-type + p { color: var(--muted); }

/* The hangar and its switchboards: a person's own pages.
   These classes were emitted with no rules behind them at all, so the page
   fell back to browser defaults -- bulleted lists and underlined blue links
   -- while every test passed, because a test asserts what a page *says* and
   none can assert that it looks designed. The person who opened it is what
   caught it, which is the same shape as a renderer needing a real terminal
   rather than a passing suite. */

/* A person's links, under their name. Spaced rather than run together: the
   default inline flow produced "Github Computational Social Affective
   Neuroscience Lab cosanlab.com" as one unreadable run. */
nav.links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.375rem 1.25rem;
  margin: 0 0 2.5rem;
}
nav.links a {
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: 1px;
}
nav.links a:hover { border-bottom-color: var(--accent); }
/* The host beside a link whose kind cannot vouch for it. Quiet, but never
   invisible -- it is the one thing standing between a labelled link and a
   phishing link. */
nav.links .muted { font-size: 0.875rem; }

/* The lines of a hangar or a switchboard. No bullets: these are destinations,
   not prose, and a marker in front of each adds nothing a reader uses. */
ul.lines {
  list-style: none;
  margin: 0 0 2.5rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
ul.lines li { margin: 0; }
ul.lines a {
  display: inline-block;
  color: var(--text);
  text-decoration: none;
  padding: 0.4375rem 0;
  font-weight: 450;
}
ul.lines a:hover { color: var(--accent); }

/* A switchboard's lines are the point of the page rather than an index of
   it, so they are buttons: bigger targets, a surface, and a blurb under the
   label when its author wrote one. */
ul.lines a.line {
  display: block;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.875rem 1rem;
  background: var(--surface);
}
ul.lines a.line:hover {
  border-color: var(--accent);
  color: var(--text);
}
ul.lines .label { display: block; }
ul.lines .blurb {
  display: block;
  color: var(--muted);
  font-size: 0.9375rem;
  font-weight: 400;
  margin-top: 0.1875rem;
}

/* The site header: mark on the left, account on the right. Present on the
   gate's own pages and on rendered forms; never on artifact origins, whose
   bytes are content-addressed and not ours to decorate. A full-width band
   with its own rule underneath, so the chrome reads as chrome and the page
   starts where the border ends. */
header.site {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.875rem 2rem;
  border-bottom: 1px solid var(--line);
  color: var(--text);
}
/* The mark wears the brand's accent — it sits on the page ground, where the
   theme's own light/dark accent pair is exactly the contrast rule. */
header.site a.mark { color: var(--accent); display: inline-flex; }
header.site a.mark:hover { opacity: .8; }
header.site > nav { display: flex; gap: 1rem; align-items: center; }
.site-right { display: flex; gap: 1rem; align-items: center; }
.site-right nav a { font-size: 0.875rem; color: var(--muted); text-decoration: none; }
.site-right nav a:hover { color: var(--accent); text-decoration: underline; }
.account-menu .menu label { font-size: 0.8125rem; }
.account-menu .menu input { margin: 0.25rem 0 0.625rem; }
header.site > nav a {
  font-size: 0.875rem;
  color: var(--muted);
  text-decoration: none;
}
header.site > nav a:hover { color: var(--accent); text-decoration: underline; }

/* The account dropdown is a <details>: open/close is the browser's, no
   script anywhere. */
.account-menu { position: relative; }
.account-menu > summary {
  list-style: none;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  padding: 0.375rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.account-menu > summary::-webkit-details-marker { display: none; }
.account-menu[open] > summary { border-color: var(--accent); }
.account-menu > .menu {
  position: absolute;
  right: 0;
  margin-top: 0.375rem;
  min-width: 14rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.75rem;
  z-index: 1;
}
.account-menu .menu p {
  color: var(--muted);
  font-size: 0.8125rem;
  margin: 0 0 0.5rem;
  overflow-wrap: anywhere;
}
.account-menu .menu nav a { display: block; padding: 0.25rem 0; font-size: 0.875rem; }
.account-menu .menu form { margin: 0.5rem 0 0; }
.account-menu .menu button {
  width: 100%;
  padding: 0.4375rem 0.75rem;
  font-size: 0.875rem;
  background: var(--ground);
  color: var(--text);
  border-color: var(--line);
}

/* The framed artifact viewer: the site header above, the bundle below,
   nothing scrolling but the frame itself. */
body.viewer { display: flex; flex-direction: column; height: 100vh; margin: 0; }
body.viewer iframe.artifact { border: 0; flex: 1; width: 100%; background: var(--ground); }
.account-menu .menu nav strong { display: block; padding: 0.25rem 0; font-size: 0.875rem; }

/* Tables — the account page's artifacts and machines. */
table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
  margin: 0.75rem 0 1.5rem;
}
th {
  text-align: left;
  color: var(--muted);
  font-weight: 500;
  padding: 0.375rem 0.75rem 0.375rem 0;
  border-bottom: 1px solid var(--line);
}
td {
  padding: 0.5rem 0.75rem 0.5rem 0;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
  overflow-wrap: anywhere;
}
td form { margin: 0; display: inline; }
td button { padding: 0.25rem 0.625rem; font-size: 0.8125rem; }

@media (max-width: 30rem) {
  main { padding: 2.5rem 1.25rem 4rem; }
  h1 { font-size: 1.5rem; }
  header.site { padding: 1rem 1.25rem 0; }
}

/* --- the week frame ----------------------------------------------------- */

.booking .weeknav { display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
  gap:0.75rem; margin:2rem 0 1rem; }
.booking .weeknav a { font-family:var(--font-mono); font-size:0.8125rem; text-decoration:none;
  color:var(--muted); border:1px solid var(--line); border-radius:999px; padding:0.3rem 0.9rem;
  justify-self:start; white-space:nowrap; transition:color .12s ease, border-color .12s ease; }
.booking .weeknav a:hover { color:var(--text); border-color:var(--muted); }
.booking .weeknav > :last-child { justify-self:end; }
.booking .weeknav .range { font-family:var(--font-mono); font-size:0.8125rem; font-weight:500;
  letter-spacing:0.06em; text-transform:uppercase; }

.booking .durations { display:inline-flex; gap:0.25rem; border:1px solid var(--line);
  border-radius:999px; padding:0.25rem; margin:0 0 1.25rem; }
.booking .durations a { font-family:var(--font-mono); font-size:0.8125rem; text-decoration:none;
  color:var(--muted); padding:0.25rem 0.9rem; border-radius:999px; transition:color .12s ease; }
.booking .durations a:hover { color:var(--text); }
.booking .durations a[aria-current="true"] { background:var(--accent); color:var(--on-accent); }

.booking .zone { color:var(--muted); font-size:0.875rem; margin:0 0 1.25rem; }
.booking .stale { color:var(--signal); font-size:0.9rem; }
.booking .notice { color:var(--signal); border-left:2px solid var(--signal);
  padding:0.25rem 0 0.25rem 1rem; font-size:0.9375rem; }
.booking .notice a { color:var(--signal); }
.booking .empty { color:var(--muted); margin:2rem 0; }

.booking .week { display:grid; grid-auto-flow:column; grid-auto-columns:minmax(7.25rem,1fr);
  gap:1rem; overflow-x:auto; scroll-snap-type:x proximity; padding-bottom:0.75rem; }
.booking .day { scroll-snap-align:start; min-width:0; }
.booking .day h2 { display:flex; flex-direction:column; gap:0.125rem; margin:0 0 0.75rem;
  padding:0 0 0.5rem; border-bottom:1px solid var(--line); }
.booking .day .dow { font-family:var(--font-mono); font-size:0.75rem; font-weight:500;
  text-transform:uppercase; letter-spacing:0.08em; }
.booking .day .date { font-size:0.8125rem; font-weight:400; color:var(--muted); }

/* --- slots: the time is the button -------------------------------------- */

.booking .slot { display:block; text-align:center; font-family:var(--font-mono);
  font-size:0.875rem; font-variant-numeric:tabular-nums; border:1px solid var(--line);
  border-radius:var(--radius); padding:0.55rem 0.5rem; margin-bottom:0.5rem; cursor:pointer;
  background:var(--surface); transition:border-color .12s ease, background .12s ease,
  color .12s ease, opacity .3s ease, transform .3s ease; }
.booking .slot:not(.answer):hover { border-color:var(--accent); color:var(--accent); }
.booking .slot input { position:absolute; opacity:0; pointer-events:none; }
/* The fill is the *booking* pick alone. A poll cell is also a `.slot` and
   always holds a checked tri-state radio, so keying this on `input:checked`
   would flood every cell — the selector names the booking radio on purpose. */
.booking .slot:has(input[name="_slot"]:checked) { background:var(--accent);
  border-color:var(--accent); color:var(--on-accent); }
.booking .slot:has(input:focus-visible) { border-color:var(--ring);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--ring) 35%, transparent); }
/* A slot someone else just took leaves quietly; its day follows when empty. */
.booking .slot.gone { opacity:0; transform:scale(0.96); pointer-events:none; }

/* --- the reveal: pick a time, get the form ------------------------------- */

.booking .pick-hint { color:var(--muted); font-size:0.9375rem; margin:1.5rem 0 0; }
.booking .picked { display:none; }
.booking .picked:not(:empty) { display:block; font-family:var(--font-mono); font-size:0.875rem;
  border:1px solid var(--accent); border-left-width:3px; border-radius:var(--radius);
  padding:0.6rem 0.9rem; margin:1.5rem 0 1.5rem; }
.booking .details { margin-top:1.5rem; }
@supports selector(:has(*)) {
  .booking form:not(:has(input[name="_slot"]:checked)) .details,
  .booking form:not(:has(input[name="_slot"]:checked)) button[type="submit"] { display:none; }
  .booking form:has(input[name="_slot"]:checked) .pick-hint { display:none; }
  .booking form:not(:has(input[name="_slot"])) .pick-hint { display:none; }
}

/* --- the poll grid ------------------------------------------------------- */

.booking .slot.answer { display:block; text-align:left; font-family:var(--font-sans);
  cursor:default; padding:0.55rem 0.75rem; }
.booking .slot.answer legend { font-family:var(--font-mono); font-size:0.8125rem;
  font-weight:500; padding:0; float:left; width:100%; text-transform:none;
  letter-spacing:0.01em; color:var(--text); }
.booking .slot.answer .len { color:var(--muted); font-weight:400; }
.booking .count { color:var(--muted); font-size:0.75rem; white-space:nowrap;
  float:right; font-weight:400; }
.booking .slot.answer .tri { display:inline-flex; gap:0.3rem; align-items:center; clear:both;
  margin:0.35rem 0.9rem 0 0; cursor:pointer; color:var(--muted); font-size:0.9rem; }
.booking .slot.answer .tri input { position:static; opacity:1; pointer-events:auto; }
.booking .slot.answer:has(input[value="yes"]:checked) { border-color:var(--accent);
  outline:2px solid var(--ring); }
.booking .slot.answer:has(input[value="if_needed"]:checked) { border-color:var(--signal); }

/* Heat: how many others said yes, in six tellable steps. Colour never
   carries it alone — the count is in the cell's text and accessible name. */
.booking .heat-1 { background:color-mix(in srgb, var(--accent) 8%, var(--surface)); }
.booking .heat-2 { background:color-mix(in srgb, var(--accent) 16%, var(--surface)); }
.booking .heat-3 { background:color-mix(in srgb, var(--accent) 24%, var(--surface)); }
.booking .heat-4 { background:color-mix(in srgb, var(--accent) 33%, var(--surface)); }
.booking .heat-5 { background:color-mix(in srgb, var(--accent) 42%, var(--surface)); }

/* poll.js upgrades the grid to tap-to-cycle painting: the radios leave the
   page (they stay in the form), the whole cell becomes the control, and the
   state chip says what your answer is. */
.booking .week.poll.paint .slot.answer { cursor:pointer; touch-action:none;
  user-select:none; -webkit-user-select:none; }
.booking .paint .slot.answer .tri { display:none; }
.booking .slot.answer .state { display:none; }
.booking .paint .slot.answer .state { display:inline-block; font-family:var(--font-mono);
  font-size:0.75rem; margin-top:0.35rem; padding:0.1rem 0.5rem; border-radius:999px;
  border:1px solid var(--line); color:var(--muted); }
.booking .paint .slot.answer[data-state="yes"] .state { background:var(--accent);
  border-color:var(--accent); color:var(--on-accent); }
.booking .paint .slot.answer[data-state="if_needed"] .state { border-color:var(--signal);
  color:var(--signal); }
.booking .paint .slot.answer:focus-visible { outline:none; border-color:var(--ring);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--ring) 35%, transparent); }
.booking .savestate { font-family:var(--font-mono); font-size:0.8125rem; color:var(--muted); }
.booking form.autosaves button[type="submit"] { display:none; }

/* --- the survey ---------------------------------------------------------- */
.survey .question { margin:1.5rem 0; padding:1rem 0; border-top:1px solid var(--line, #8884); }
.survey .question h2 { font-size:1.05rem; margin:0 0 .5rem; }
.survey .req { color:var(--accent, inherit); }
/* A choice is a card you press, not a dot you aim at.
   The radio stays in the markup and in the tab order — it is what a form
   posts, what a screen reader announces, and what works with the script
   blocked — but it is not what anyone looks at. `:has(:checked)` moves the
   selected state onto the whole card, which is a target the size of the thing
   being chosen rather than a 16-pixel circle beside it. That matters most in
   the case this arrived with: an option that *is* a picture, where the dot sat
   outside the image it referred to and the image itself was not clickable.
   The scale keeps its dots (`.point`), because five cards in a row stop
   reading as a scale. */
.survey .opt:not(.rank) { display:flex; align-items:center; gap:.5rem;
  margin:.35rem 0; padding:.5rem .65rem; border:1px solid var(--rule, #8883);
  border-radius:var(--radius); cursor:pointer; position:relative;
  transition:border-color .12s ease, background-color .12s ease; }
.survey .opt:not(.rank):hover { border-color:var(--accent); }
/* Visually gone, functionally present: still focusable, still announced,
   still the thing that posts. `display:none` would take all three away. */
.survey .opt:not(.rank) > input { position:absolute; width:1px; height:1px;
  opacity:0; margin:0; pointer-events:none; }
.survey .opt:not(.rank):has(:checked) { border-color:var(--accent);
  background:color-mix(in srgb, var(--accent) 12%, transparent); }
/* The keyboard has to be able to see where it is, and the input it would
   normally ring is invisible now. */
.survey .opt:not(.rank):has(:focus-visible) { outline:2px solid var(--accent);
  outline-offset:2px; }
.survey .opt:not(.rank):has(:checked)::after { content:"✓"; margin-left:auto;
  padding-left:.5rem; color:var(--accent); font-weight:700; }
.survey .opt:not(.rank):has(:disabled) { cursor:default; opacity:.7; }
.survey .opt:not(.rank):has(:disabled):hover { border-color:var(--rule, #8883); }
.survey .opt .detail, .survey .cap { opacity:.75; font-size:.9rem; }
.survey .cap { margin:.25rem 0; }
/* A scale is one row of equal columns, each point's label under its control.
   It was a wrapping flex row of radio-and-label pairs, which put the fifth
   point of a five-point Likert on a second line — at which point it stops
   reading as a scale and starts reading as a list. Equal columns also stop
   "Neutral" from getting less room than "Strongly disagree". */
.survey .scale { display:grid; gap:.4rem .5rem; align-items:start;
  grid-template-columns:repeat(var(--points, 5), minmax(0, 1fr)); }
.survey .scale .point { display:flex; flex-direction:column; align-items:center;
  text-align:center; gap:.3rem; font-size:.9rem; line-height:1.25; }
/* Asked to stack, or too narrow to be a row: back to one point per line,
   control beside label. Five columns on a phone is five columns of one word. */
.survey .opts:not(.across) .scale { grid-template-columns:1fr; }
.survey .opts:not(.across) .scale .point { flex-direction:row; align-items:center;
  text-align:left; }
@media (max-width:32rem) {
  .survey .scale { grid-template-columns:1fr; }
  .survey .scale .point { flex-direction:row; align-items:center; text-align:left; }
}

/* The VAS: anchors at the two ends of the track they anchor, the track its
   full width, the number beside it. Inline anchors could only wrap, which
   left "100 — …" on the line below the slider describing nothing. */
/* Two half-width columns under the track, not one column with both anchors
   justified to its ends: at the ends of a *shared* cell, "0 — Not at all
   confident" and "100 — Completely confident" simply overlap in the middle,
   which is what they did. A half each means each can wrap into its own space
   and the two can never collide. */
.survey .vas { display:grid; grid-template-columns:1fr 1fr auto; align-items:end;
  column-gap:.75rem; row-gap:.35rem; }
.survey .vas .anchor { grid-row:1; font-size:.85rem; opacity:.75; line-height:1.25; }
.survey .vas .anchor-min { grid-column:1; justify-self:start; }
.survey .vas .anchor-max { grid-column:2; justify-self:end; text-align:right; }
.survey .vas .vas-range { grid-row:2; grid-column:1 / span 2; align-self:center; }
.survey .vas input[type=number] { grid-row:2; grid-column:3; width:4.5rem;
  align-self:center; }
/* Script off: there is no track, so the number takes the row rather than
   sitting alone against the right margin. */
.survey .vas:not(:has(.vas-range)) input[type=number] { grid-column:1; justify-self:start; }
.survey textarea { width:100%; max-width:36rem; }
.survey .results { margin:.75rem 0 0; }
.survey .tallybar { display:flex; align-items:center; gap:.5rem; margin:.2rem 0; }
.survey .tallybar .optlabel { min-width:9rem; }
.survey .tallybar meter { flex:1; max-width:18rem; }
.survey .prose-answers li { margin:.4rem 0; }
.survey .voters { margin:.5rem 0; font-size:.9rem; }
.survey .promise { font-style:italic; }
.booking .resolution { border-left:3px solid var(--accent, currentColor); padding:.5rem .75rem; }
.survey .cloud { line-height:2.2; margin:.5rem 0; }
.survey .cloud-w { margin-right:.9rem; white-space:nowrap; }
.survey .cloud-w .count { font-size:.65em; opacity:.6; vertical-align:super; margin-left:.15rem; }
.survey .cw1 { font-size:.95rem; opacity:.75; }
.survey .cw2 { font-size:1.15rem; opacity:.85; }
.survey .cw3 { font-size:1.45rem; }
.survey .cw4 { font-size:1.8rem; }
.survey .cw5 { font-size:2.25rem; font-weight:600; }

/* survey.js swaps the rank selects for a buttoned list, and lays a
   thumbless track over the VAS number field. Both are enhancement-only:
   the classes below exist for pages where the script ran. */
.survey .vas-range { width:100%; min-width:0; }
.survey .vas-range.untouched::-webkit-slider-thumb { opacity:0; }
.survey .vas-range.untouched::-moz-range-thumb { opacity:0; }
.survey .vas-number { width:4.5rem; }
/* Pictures in a question. Capped rather than trusted: a figure whose natural
   size is 3000px would otherwise decide the page, and a question people have
   to scroll past to reach its options is one they answer without looking. */
.survey .qmedia { display:block; max-width:100%; height:auto; border-radius:var(--radius);
  margin:.35rem 0 .6rem; }
.survey .optmedia { max-width:100%; height:auto; border-radius:var(--radius); }
/* A picture option is a card with the picture in it: the image first, its
   label under it, and the whole thing the target. Choosing between figures
   should mean clicking the figure. */
.survey .opt.has-media { flex-direction:column; align-items:stretch; gap:.45rem;
  max-width:22rem; padding:.5rem; }
.survey .opt.has-media .optmedia { max-height:14rem; object-fit:contain;
  border-radius:calc(var(--radius) - 2px); background:#0000000a; }
.survey .opt.has-media .optlabel { font-size:.95rem; }
/* The tick belongs on the image, not trailing under the caption. */
.survey .opt.has-media:has(:checked)::after { position:absolute; top:.75rem;
  right:.75rem; margin:0; padding:0; width:1.5rem; height:1.5rem;
  display:flex; align-items:center; justify-content:center; border-radius:50%;
  background:var(--accent); color:var(--on-accent); font-size:.85rem; }
/* Side by side when there is room: comparing figures means seeing them at
   once, and a column of two puts the second one below the fold. */
.survey .opts:has(.opt.has-media) { display:flex; flex-wrap:wrap; gap:.6rem;
  align-items:flex-start; }
.survey .rank-list .optmedia { max-height:2.5rem; width:auto; margin-right:.4rem; }

/* `layout` in the spec, as one class on one wrapper. Horizontal wraps
   rather than scrolls: a row of options that runs off the side of a phone
   is a row of options nobody answers. */
.survey .opts.across { display:flex; flex-wrap:wrap; gap:.4rem .9rem; align-items:center; }
.survey .opts.across .opt { margin:0; }

/* The rank row reads left to right: position, grip, nudges, label. The
   buttons deliberately do NOT inherit the page's accent-filled `button` —
   at .6875rem/1.5rem padding two of them per row is a wall of colour with
   the option text pushed off to one side, which is what shipped. They are
   quiet controls next to the thing they move. */
.survey .rank-list { margin:.35rem 0; padding-left:0; list-style:none;
  counter-reset:rank; }
.survey .rank-list li { display:flex; align-items:center; gap:.4rem;
  margin:.25rem 0; padding:.3rem .45rem; border:1px solid var(--rule, #8883);
  border-radius:var(--radius); background:var(--surface, transparent); }
/* The position is the whole point of a ranking, and `list-style:none` took
   the browser's numbering away. Put it back deliberately, sized and aligned. */
.survey .rank-list li::before { counter-increment:rank; content:counter(rank);
  min-width:1.35rem; text-align:right; opacity:.55; font-variant-numeric:tabular-nums; }
/* The dragged row lifts off the list, so the thing following the pointer is
   obviously the thing being moved. Opacity alone read as "nothing happened". */
.survey .rank-list li.dragging { position:relative; z-index:2;
  background:var(--bg, #fff); border-color:var(--accent);
  box-shadow:0 2px 10px #0003; cursor:grabbing; }
.survey .rank-list button { min-width:1.9rem; padding:.2rem .35rem; font-size:.85rem;
  line-height:1; background:transparent; color:inherit; border:1px solid var(--rule, #8883); }
.survey .rank-list button:hover:not(:disabled) { background:var(--accent); color:var(--on-accent);
  border-color:transparent; opacity:1; }
.survey .rank-list button:disabled { opacity:.3; cursor:default; }
.survey .rank-list .optlabel { flex:1; }
.survey .rank-list .grip { cursor:grab; touch-action:none; user-select:none;
  -webkit-user-select:none; padding:0 .3rem; opacity:.6; }
.survey .rank-list li.dragging .grip { cursor:grabbing; }
.survey .ranks-enhanced label.opt.rank { display:none; }
.visually-hidden { position:absolute; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); white-space:nowrap; }

/* --- the projector -------------------------------------------------------
   Sized for the back row: the join line and the bars are the page. */
.screen h1 { font-size:2.4rem; }
.screen .join { font-size:1.9rem; margin:.25rem 0; }
.screen .count { font-size:1.3rem; opacity:.8; }
.screen .question h2 { font-size:1.6rem; }
.screen .tallybar { font-size:1.4rem; }
.screen .tallybar .optlabel { min-width:14rem; }
.screen .tallybar meter { max-width:none; height:1.4rem; }
.screen .cap { font-size:1.15rem; }
