/* ==========================================================================
   Resource Hub IQ — "Signal" UI direction.
   Branded 2026-08-23: logo and favicon live in wwwroot/img/ and
   wwwroot/favicon.ico, per resource-hub-iq-brand-guide.md alongside them.
   The palette tokens below were deliberately left as the pre-brand
   navy/blue rather than swapped for the brand guide's exact hex (#0B2345
   navy, #3E67C6 blue, #18AAA6 teal): several are contrast-tested against
   `--surface` and `--brand-tint` — see the --warn comment further down,
   which failed 4.5:1 twice before landing on its current value. Full
   palette adoption is a separate pass that re-runs the axe-core contrast
   checks on purpose, not a side effect of dropping in a logo. The header
   is already within a shade of the brand navy, so it reads as one thing
   with the new wordmark on it.
   Signal reads the roster as a measuring instrument: score meters, a KPI
   summary strip, mono-labelled columns, tabular figures. See
   the Signal direction brief in the project design docs for the detail — this
   file implements it, it does not re-derive it.
   No CSS framework: this is a data-entry tool, and hand-written CSS at this
   size is smaller, faster and easier to keep accessible.
   ========================================================================== */

:root {
  --brand-navy:        #0C2340;
  --brand-navy-dark:   #0A1B3D;
  --brand-blue:        #3255A4;
  --brand-blue-dark:   #27437f;   /* 4.5:1 on white for link text */
  --brand-grey:        #69727D;
  --brand-tint:        #EDF1F5;   /* Signal thead/tint background, exact */
  --cyan:            #067A8C;   /* Signal secondary accent (established sparklines) */

  --ink:             #0E1721;
  --ink-muted:       #5C6B7A;
  --bg:              #F6F8FA;  /* Signal page background */
  --surface:         #ffffff; /* cards/panels float white on --bg */
  --surface-muted:   #EEF1F5; /* muted/disabled fills that must stay distinct from white */
  --line:            #DCE3EA; /* Signal rule, exact */

  --ok:              #12694A; /* Signal green — Established */
  /* Signal amber — Provisional. Was #A66A00, which is 4.48:1 on white and so failed
     WCAG 1.4.3 as body text. Nothing had rendered it small until the event pipeline
     view did: the only other place is the "thin" rater badge, which needs an event
     with 1-2 raters and the accessibility fixture's event has none, so it was never
     scanned. This value must clear 4.5:1 against BOTH --surface and --brand-tint,
     because `tbody tr:hover` puts the tint behind any badge in a table row - which is
     what the second failure turned out to be. #8F5B00 is 5.7:1 on white, 5.0:1 on
     tint, and reads as the same amber. */
  --warn:            #8F5B00;
  --bad:             #a32020;

  --radius: 6px;
  --gap: 1rem;
  --measure: 68ch;

  --font: "Poppins", system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg);
  letter-spacing: -.006em;
}

/* --- skip link ----------------------------------------------------------- */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--brand-navy); color: #fff; padding: .75rem 1rem;
}
.skip-link:focus { left: 0; }

/* --- focus: visible everywhere, never removed ---------------------------- */
:focus-visible {
  outline: 3px solid var(--brand-blue);
  outline-offset: 2px;
}

/* --- icons ---------------------------------------------------------------- */
.icon-sprite { display: none; }
.icon {
  width: 1.05em; height: 1.05em;
  flex: 0 0 auto; vertical-align: -0.15em;
}
.icon--heading { width: 1em; height: 1em; color: var(--brand-blue); }

/* --- header -------------------------------------------------------------- */
.site-header {
  background: var(--brand-navy);
  color: #fff;
  padding: .75rem 1rem;
}
.site-header__inner {
  max-width: 1200px; margin: 0 auto;
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
}
.site-header__brand {
  display: flex; align-items: center;
  min-height: 44px; text-decoration: none;
}
/* The wordmark PNG is 725x134 (~5.41:1) - height drives the box, width follows
   it, so the logo never distorts regardless of what a future header-height
   change does. A PNG, not the brand package's SVG: see the comment on the
   <img> in _Layout.cshtml for why the SVG's "IQ" badge renders misaligned
   (a font-fallback problem, not a CSS one).
   64px, not the 40px first tried: the "IQ" chip is the smallest element in the
   lockup, and at 40px it rendered as an illegible smudge rather than two
   letters. */
.site-header__logo { display: block; height: 64px; width: auto; }
.site-header__spacer { flex: 1 1 auto; }

/* Account block: informational, not a call to action. The old white button
   competed with the logo for attention. */
.account { display: flex; align-items: center; gap: .5rem; font-size: .85rem; }
.account__name { color: #c3cee0; }
.account .icon { color: #8fa2c0; }
.link-button {
  background: none; border: 0; padding: .25rem 0; margin: 0;
  font: inherit; color: #fff; text-decoration: underline;
  text-underline-offset: 2px; cursor: pointer;
}
.link-button:hover { color: #fff; text-decoration-thickness: 2px; }
/* Plain links in the header are white on navy - but NOT buttons, which carry
   their own colours. `.site-header a` (0-1-1) outranks `.btn--secondary` (0-1-0),
   so without :not(.btn) the signed-out "Sign in" link renders white on white. */
.site-header a:not(.btn) { color: #fff; }

/* --- nav ----------------------------------------------------------------- */
.site-nav { background: var(--brand-navy-dark); }
.site-nav ul {
  max-width: 1200px; margin: 0 auto; padding: 0 1rem;
  list-style: none; display: flex; flex-wrap: wrap; gap: .25rem;
}
.site-nav a {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .7rem .9rem;
  color: #dbe3f0; text-decoration: none; font-size: .95rem;
  border-bottom: 3px solid transparent;
}
.site-nav a:hover { color: #fff; background: rgba(255,255,255,.07); }
.site-nav a[aria-current="page"] {
  color: #fff; border-bottom-color: var(--brand-blue); font-weight: 600;
}

/* --- the admin menu: a native disclosure, no script ----------------------- */
/* <details> gives the keyboard model, the expanded state and the focus behaviour
   for free, which is the entire reason this is not a scripted menu. What CSS
   adds is the panel position and a summary that reads as a nav item.
   Note what is deliberately absent: no hover-to-open. That needs script, fails
   WCAG 2.5.x on pointer, and is hostile on a touch screen. Press to open. */
.nav-menu__host { position: relative; }

.nav-menu__summary {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .7rem .9rem; min-height: 44px;
  color: #dbe3f0; font-size: .95rem; cursor: pointer;
  border-bottom: 3px solid transparent;
  /* Both spellings: the triangle is a ::marker in most engines and a
     ::-webkit-details-marker in older WebKit, and either one left in place puts a
     stray glyph in the middle of the navigation bar. */
  list-style: none;
}
.nav-menu__summary::-webkit-details-marker { display: none; }
.nav-menu__summary::marker { content: ""; }
.nav-menu__summary:hover { color: #fff; background: rgba(255,255,255,.07); }
/* One of the pages behind this menu is the current one. Marked the same way as a
   top-level item, but not with aria-current - the summary is not a link, and the
   real current page carries it on the link inside. */
.nav-menu__summary--current { color: #fff; border-bottom-color: var(--brand-blue); font-weight: 600; }

.nav-menu__chevron { transition: transform .15s ease; }
.nav-menu[open] .nav-menu__chevron { transform: rotate(180deg); }

/* Scoped through .site-nav on purpose: `.site-nav ul` is a class plus an element
   and so outranks a bare class, which was silently handing this panel the bar's
   own 1rem side padding and its flex-wrap - the item layout looked deliberate and
   was not. One column, always, each row a full-width target. */
.site-nav .nav-menu__items {
  list-style: none; margin: 0; padding: .35rem; max-width: none;
  display: flex; flex-direction: column; gap: .1rem;
  /* The same navy as the bar it drops out of, and the same link colours - that
     pair already ships in the nav above and is the one contrast this file does
     not have to re-argue. A white panel would want a tint on hover, and
     --brand-blue-dark on --brand-tint is the 4.5:1 failure documented above. */
  background: var(--brand-navy-dark);
  border: 1px solid rgba(255,255,255,.14);
  border-radius: var(--radius);
  box-shadow: 0 10px 24px rgba(10, 27, 61, .35);
}
.site-nav .nav-menu__items a {
  display: flex; align-items: center; gap: .45rem;
  padding: .55rem .7rem; min-height: 44px;
  color: #dbe3f0; text-decoration: none; font-size: .95rem;
  border-radius: calc(var(--radius) - 2px); white-space: nowrap;
}
.site-nav .nav-menu__items a:hover { color: #fff; background: rgba(255,255,255,.09); }
.site-nav .nav-menu__items a[aria-current="page"] { color: #fff; font-weight: 600; background: rgba(255,255,255,.12); }

/* Floating over the page on anything with room for it. Above main, and outside
   the bar's own stacking so the shadow is not clipped. */
@media (min-width: 641px) {
  .site-nav .nav-menu[open] .nav-menu__items {
    position: absolute; z-index: 40; top: 100%; left: .25rem; min-width: 13rem;
  }
}

/* --- layout -------------------------------------------------------------- */
main { max-width: 1200px; margin: 0 auto; padding: 1.5rem 1rem 4rem; }
.prose { max-width: var(--measure); }

h1 {
  font-size: 1.6rem; margin: 0 0 .25rem; color: var(--brand-navy);
  display: flex; align-items: center; gap: .5rem;
  font-weight: 650; letter-spacing: -.015em;
}
h2 { font-size: 1.2rem; margin: 2rem 0 .5rem; color: var(--brand-navy); letter-spacing: -.01em; }
.page-lede { color: var(--ink-muted); margin: 0 0 1.5rem; }

/* --- mono labels: the Signal direction's small-caps-of-the-web treatment,
   used for every column head and KPI label. --- */
.label-mono {
  font-family: var(--mono); font-size: .68rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-muted); font-weight: 500;
}
.num { font-variant-numeric: tabular-nums; }

/* --- KPI summary strip: rater count, median score etc. always on screen -
   the standing "single-rater bias" risk needs that number visible without
   anyone going looking for it. --- */
.kpi-strip {
  display: flex; flex-wrap: wrap; gap: 1px;
  background: var(--line); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; margin-bottom: 1.5rem;
}
.kpi-tile {
  flex: 1 1 140px; background: var(--surface); padding: .7rem 1rem;
  display: flex; flex-direction: column; gap: .2rem;
}
.kpi-tile b {
  font-size: 1.35rem; font-weight: 650; letter-spacing: -.02em;
  color: var(--brand-navy); font-variant-numeric: tabular-nums;
}
.kpi-tile span { font-family: var(--mono); font-size: .68rem; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-muted); }
.kpi-tile--warn b { color: var(--warn); }

/* --- score meter: decoration only. The number, rating count and confidence
   label carry the accessible information; the bar never encodes anything on
   its own (WCAG 1.4.1). --- */
.meter { display: inline-flex; align-items: center; gap: .55rem; }
/* Larger context (e.g. the speaker-detail stat panel): scale the meter up
   without touching the shared component's default (table-row) size. */
.stat .meter { gap: .7rem; }
.stat .meter i { width: 84px; height: 7px; }
.stat .meter .score { font-size: 2rem; }
.stat .meter .score__n { font-size: .85rem; }
.meter i {
  display: block; width: 62px; height: 5px; border-radius: 3px;
  background: var(--surface-muted); position: relative; overflow: hidden; flex: 0 0 auto;
}
.meter i u { position: absolute; inset: 0 auto 0 0; border-radius: 3px; display: block; }

a { color: var(--brand-blue-dark); }
a:hover { color: var(--brand-navy); }

/* --- cards / panels ------------------------------------------------------ */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem;
}
.card-grid {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
}
.stat { font-size: 2rem; font-weight: 650; color: var(--brand-navy); line-height: 1.1; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.stat-label {
  color: var(--ink-muted); font-family: var(--mono); font-size: .68rem;
  letter-spacing: .1em; text-transform: uppercase; font-weight: 500;
}

/* --- tables: real tables, scrollable on small screens -------------------- */
/* Floats as a white card on the cooler --bg page background — the Signal
   "instrument panel" read. Overflow stays on this element, never the body:
   there's a regression test pinning that (AxeTests, 320px reflow). */
.table-scroll {
  overflow-x: auto; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
}
/* Caption sits outside the scroll container so it stays put and wraps. */
.table-caption { color: var(--ink-muted); font-size: .9rem; margin: 0 0 .5rem; }
table { border-collapse: collapse; width: 100%; font-size: .95rem; }
caption { text-align: left; color: var(--ink-muted); padding-bottom: .5rem; }
th, td { text-align: left; padding: .6rem .7rem; border-bottom: 1px solid var(--line); }
thead th {
  background: var(--brand-tint); color: var(--ink-muted); white-space: nowrap;
  font-family: var(--mono); font-size: .7rem; letter-spacing: .1em;
  text-transform: uppercase; font-weight: 500;
}
tbody tr:last-child th, tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--brand-tint); }

/* --- score display ------------------------------------------------------- */
/* Never colour alone (WCAG 1.4.1): every score carries its number, its rating
   count and a text confidence label. "Unrated" is a word, not an empty cell. */
.score { font-weight: 650; font-variant-numeric: tabular-nums; }
.score--none { font-weight: 500; color: var(--ink-muted); }
.score__n { color: var(--ink-muted); font-weight: 400; font-size: .85rem; }
.confidence {
  display: inline-flex; align-items: center; gap: .35rem;
  font-family: var(--mono); font-size: .68rem; letter-spacing: .08em;
  text-transform: uppercase; padding: .15rem .55rem;
  border-radius: 999px; border: 1px solid currentColor; white-space: nowrap;
}
.confidence::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex: 0 0 auto;
}
.confidence--unrated     { color: var(--ink-muted); }
.confidence--provisional { color: var(--warn); }
.confidence--established { color: var(--ok); }

/* --- explainer (native details, no JS) ---------------------------------- */
.explainer { margin: 0 0 1rem; }
.explainer summary {
  cursor: pointer; color: var(--brand-blue-dark); font-size: .9rem;
  padding: .4rem 0; min-height: 44px; display: flex; align-items: center;
}
.explainer summary:hover { color: var(--brand-navy); }
.explainer__body {
  border-left: 3px solid var(--line); padding: .25rem 0 .25rem 1rem;
  max-width: var(--measure);
}
.explainer__body p { margin: 0 0 .6rem; font-size: .9rem; color: var(--ink-muted); }
.explainer__body p:last-child { margin-bottom: 0; }

/* --- the roster filter panel --------------------------------------------- */
/* Nine controls, folded away until wanted. A native <details> again, so the
   keyboard model and the expanded state come from the browser - but note the
   class: site.js light-dismisses .nav-menu and .row-menu, and a filter panel
   that closed itself when you clicked the table would be unusable. This one is
   deliberately not in that list.
   Its contents are still submitted while it is closed. A closed disclosure hides
   its children; it does not remove them from the form - which is what lets the
   search button carry the filters and the filter button carry the search. */
.filters-panel { margin: .25rem 0 1rem; }
.filters-panel > summary {
  cursor: pointer; color: var(--brand-blue-dark); font-size: .9rem;
  padding: .4rem 0; min-height: 44px; display: flex; align-items: center; gap: .4rem;
}
.filters-panel > summary:hover { color: var(--brand-navy); }
/* Open, the grid needs air above it and a rule to sit under, or the controls read
   as part of the search field they drop out of. */
.filters-panel[open] > summary { font-weight: 600; }
.filters-panel .filters {
  /* Keeps the base rule's bottom margin: zeroing it butted the first checkbox
     group's legend straight against the hint text above it. */
  margin: .6rem 0 1.25rem; padding-top: .9rem;
  border-top: 1px solid var(--line);
}
/* The checkbox groups are the multi-select controls. Bordered like any other
   fieldset would be too heavy for nine of them stacked, so they read as grouped
   by their legend and a little space instead. */
.filters-panel .checkbox-group { border: 0; padding: 0; margin: 0 0 1rem; }
.filters-panel .checkbox-group > legend {
  padding: 0 0 .25rem; font-size: .95rem; font-weight: 500; color: var(--ink);
}
/* Role and format are four short options each - side by side rather than stacked,
   which is two fewer screens of scrolling on a phone. */
.filters-pair {
  display: grid; gap: 0 var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
}

/* --- forms --------------------------------------------------------------- */
fieldset { border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem; margin: 0 0 1rem; }
legend { font-weight: 600; color: var(--brand-navy); padding: 0 .4rem; }
label { display: block; font-weight: 500; margin-bottom: .25rem; }
/* Named by what is NOT a text box rather than by what is. The list used to be a
   whitelist - text, email, search, number, date, password - and type=tel was
   missing from it, so the speaker form's Phone field alone fell through to the
   browser default: a narrow box, no padding, its own border, half the height of
   the Email field directly above it. A whitelist here fails silently and looks
   like a one-off design decision, and the next type added would have gone the
   same way.
   The exclusions are the controls that must keep their native rendering:
   checkboxes and radios are drawn by .checkbox and .rating-scale, submit is a
   button, hidden has no box, and the import screen's file input has a control
   that cannot be restyled coherently anyway. An input with no type attribute is
   a text box and now gets caught too, which the old list also missed. */
input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=hidden]):not([type=file]),
select, textarea {
  width: 100%; max-width: 32rem; padding: .5rem .6rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; color: inherit; background: #fff;
  min-height: 44px;               /* comfortable touch target */
}
textarea { min-height: 6rem; }
.field { margin-bottom: 1rem; }
.field__hint { color: var(--ink-muted); font-size: .85rem; margin-top: .2rem; }
.validation-summary-errors, .field-validation-error { color: var(--bad); font-size: .9rem; }

/* Rating input: a radio group in a fieldset. Deliberately NOT a star widget -
   those are typically div-and-icon constructions with no keyboard model. */
.rating-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding: .6rem 0; margin: 0;
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
}
.rating-row:last-child { border-bottom: 0; }
.rating-scale { display: flex; gap: .35rem; flex-wrap: wrap; }
.rating-scale label {
  margin: 0; cursor: pointer; font-weight: 400;
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: var(--radius); background: #fff;
}
.rating-scale input { position: absolute; opacity: 0; width: 0; height: 0; }
.rating-scale input:checked + span {
  background: var(--brand-navy); color: #fff;
}
/* The "no rating" option is the default state, so a solid navy block would read
   as a score. Muted outline instead: visible, clearly selected, not a value. */
.rating-scale label:first-child input:checked + span {
  background: var(--surface-muted); color: var(--ink-muted);
  box-shadow: inset 0 0 0 2px var(--line);
}
.rating-scale label span {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; min-width: 42px; min-height: 42px;
  border-radius: calc(var(--radius) - 1px);
}
.rating-scale input:focus-visible + span { outline: 3px solid var(--brand-blue); outline-offset: 2px; }

/* Screen-reader-only text: the rating scale numbers need context ("4 out of 5
   for Delivery, Dana Example") that sighted users get from the visible legend. */
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

.rating-row__label { font-weight: 500; color: var(--ink); padding: 0; float: none; }
.legend-role { font-weight: 400; color: var(--ink-muted); }

/* Rating page: one card per thing being rated (the session, then each speaker).
   A rating form for a session with three presenters is four near-identical
   blocks of the same radio rows, so the boundary between them has to be
   unmistakable - a plain legend on a hairline border was not.
   float + width:100% is what makes a legend lay out as a full-width block
   header; .rate-section__body clears it. */
.rate-section {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); padding: 0; margin: 0 0 2rem;
  overflow: hidden;                /* keeps the header bar inside the corners */
}
.rate-section > legend {
  float: left; width: 100%; margin: 0; padding: .7rem 1rem;
  background: var(--brand-navy); color: #fff;
  font-size: 1.05rem; line-height: 1.3;
}
.rate-section__kind {
  display: block; font-weight: 400;
  font-family: var(--mono); font-size: .68rem;
  letter-spacing: .1em; text-transform: uppercase;
  color: #fff; opacity: .85;
}
.rate-section > legend .legend-role { color: #fff; opacity: .85; }
.rate-section__body { clear: both; padding: 1rem; }
.rate-section__body > .field:last-child,
.rate-section__body > fieldset:last-child { margin-bottom: 0; }

.checkbox-group { border: 0; padding: 0; margin: 0 0 1rem; }
.checkbox-group legend { padding: 0; font-size: .95rem; }
/* The box sits on the first line of its label, not halfway down the block. With
   align-items: center a two-line label - most of the "(general)" topics - left the
   box floating between the lines, pointing at neither.
   flex-start alone would wreck the single-line case: min-height is the 44px touch
   target, and top-aligning 24px of text inside 44px leaves it hanging with 20px of
   nothing underneath. The padding is what holds that still - .625rem above and
   below exactly reproduces the (44 - 24) / 2 the centring used to compute, so a
   one-line row renders identically to before and a two-line row simply grows
   downwards. */
.checkbox {
  display: inline-flex; align-items: flex-start; gap: .4rem;
  margin: 0 1rem .25rem 0; padding: .625rem 0;
  font-weight: 400; min-height: 44px;
}
/* flex-shrink: 0 is not optional here. The box is a flex item beside its label, and
   a width alone does not hold: where the label is long enough to wrap - which in the
   15rem topic columns is most of the "(general)" ones - flex takes the difference out
   of the box instead, and the same checkbox renders visibly smaller than the
   single-line ones next to it. */
/* Top-aligned it would touch the cap line rather than sit on it, so it drops by
   half the difference between the label's line box and the box itself - the same
   offset centring used to give it against a single line, computed rather than
   guessed.
   rem on both terms, not em: em on an input resolves against the input's own
   font-size, which the UA sheet sets to 13.33px and not the 16px it inherits
   visually. `1.5em` here computed to 20px, the nudge came out 0.8px instead of
   2.8px, and every box sat 2px high. The label's line box is 1.5 x 1rem. */
.checkbox input {
  width: 1.15rem; height: 1.15rem; flex: 0 0 auto;
  margin-top: calc((1.5rem - 1.15rem) / 2);
}

/* --- buttons ------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  min-height: 44px; padding: .55rem 1.1rem;
  font: inherit; font-weight: 500; cursor: pointer;
  border-radius: var(--radius); border: 1px solid transparent;
  text-decoration: none;
}
/* A row of buttons is a flex row, and has to be one.
   It was never given a rule at all - it relied on the buttons being inline-flex and
   laying out inline, which aligns them on the BASELINE. A <button> and an <a class="btn">
   do not share a baseline: the browser takes an inline-flex box's baseline from its first
   flex item, and a button's is synthesised differently, so a submit button sat several
   pixels higher than the links beside it. On the roster that was Search against Export
   and Add speaker; the same thing was true on all thirty screens using this class.
   Aligning the items explicitly is the fix - baselines are the wrong tool for a row of
   boxes that should simply share a centre line. The gap also replaces the inter-element
   whitespace the inline layout was silently supplying. */
.btn-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem;
}

.btn--primary { background: var(--brand-navy); color: #fff; }
.btn--primary:hover { background: var(--brand-blue); color: #fff; }
.btn--secondary { background: #fff; color: var(--brand-navy); border-color: var(--brand-navy); }
.btn--secondary:hover { background: var(--brand-tint); color: var(--brand-navy); }

/* --- scaffolded Identity UI ---------------------------------------------
   The Identity pages ship with Bootstrap class names and this project has no
   Bootstrap. Two consequences worth fixing rather than living with: buttons
   render as unstyled browser defaults, and .form-floating puts the label AFTER
   the input in the DOM, so without Bootstrap it displays underneath - which
   reads as a caption for the wrong field.
   ------------------------------------------------------------------------- */

/* The sign-in page links to pages this application deliberately does not have:
   registration is removed (accounts are created by an administrator - see
   Program.cs) so these now answer 404. The links live in the Identity UI's own
   Login page, which is a Razor Class Library page and so cannot be edited without
   scaffolding the whole thing into the project.
   Keyed on the href rather than a class, because the class names come from
   Bootstrap markup this project does not otherwise use and would be the first
   thing to change. If registration ever returns, delete this rule. */
a[href*="/Identity/Account/Register"],
a[href*="/Identity/Account/ResendEmailConfirmation"] {
    display: none;
}
.form-floating {
  /* column-reverse shows the label above while leaving DOM order (and therefore
     the label/input association) untouched. */
  display: flex; flex-direction: column-reverse;
  margin-bottom: 1rem; max-width: 32rem;
}
.form-floating > label { margin: 0 0 .25rem; font-weight: 500; }
.form-control { width: 100%; }

.form-check { display: flex; align-items: center; gap: .5rem; margin-bottom: 1rem; }
/* Same flex-shrink note as .checkbox input above. Identity's own labels are short
   enough that it has never shown, which is exactly why it is worth pinning. */
.form-check-input { width: 1.15rem; height: 1.15rem; margin: 0; flex: 0 0 auto; }
.form-check-label { margin: 0; font-weight: 400; }

.btn-primary, .btn-lg, button.w-100[type=submit] {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: .55rem 1.1rem;
  font: inherit; font-weight: 500; cursor: pointer;
  border-radius: var(--radius); border: 1px solid transparent;
  background: var(--brand-navy); color: #fff; text-decoration: none;
}
.btn-primary:hover, .btn-lg:hover { background: var(--brand-blue); color: #fff; }

/* Identity renders its validation summary as a bare <div>; give it the same
   treatment as ours so errors are not silently invisible. */
.text-danger { color: var(--bad); }

/* --- pagination ----------------------------------------------------------- */
.pager {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-top: 1rem; font-size: .9rem;
}
.pager__pages {
  list-style: none; display: flex; gap: .25rem; margin: 0; padding: 0;
  flex-wrap: wrap;
}
.pager__step, .pager__page {
  display: inline-flex; align-items: center; justify-content: center; gap: .3rem;
  min-width: 44px; min-height: 44px; padding: .4rem .7rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: #fff; text-decoration: none; color: var(--brand-blue-dark);
}
.pager__step:hover, .pager__page:hover { background: var(--brand-tint); color: var(--brand-navy); }
.pager__page--current {
  background: var(--brand-navy); border-color: var(--brand-navy);
  color: #fff; font-weight: 600;
}
.pager__step--off { color: var(--ink-muted); background: var(--surface-muted); }

/* --- notices ------------------------------------------------------------- */
.notice {
  border-left: 4px solid var(--brand-blue);
  background: var(--brand-tint);
  padding: .8rem 1rem; border-radius: 0 var(--radius) var(--radius) 0;
  margin-bottom: 1.5rem;
}
.notice--warn { border-left-color: var(--warn); background: #fdf6e6; }

/* --- table row actions and nested speaker lists --------------------------- */
/* Mixed children: plain links, .btn links, and single-button forms. Without
   align-items and a matching height on each, the form's 44px button pushes its
   own baseline down and the row reads as two staggered lines. */
.row-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem 1rem; }
.row-actions > a, .row-actions > form { display: inline-flex; align-items: center; margin: 0; }
.row-actions > a:not(.btn) { min-height: 44px; }

.speaker-list { list-style: none; margin: .4rem 0 0; padding: 0; }
.speaker-list li { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .2rem; }

/* --- row action menus ----------------------------------------------------- */
/* One disclosure per row where a session used to carry four outlined buttons in a
   wrapped two-by-two block. The same native <details> as the admin menu, for the
   same reason - see the note there; the browser owns the keyboard model and this
   application ships no script.
   What differs is the panel, and it is not a style choice. The nav menu floats;
   this one cannot. .table-scroll sets overflow-x, and overflow-x: auto computes
   overflow-y from visible to auto, so a scroll container clips on BOTH axes - an
   absolutely positioned panel inside one is cut off at the cell's edge or, worse,
   only reachable by scrolling the table. So it opens in flow and the one row
   grows. That settles 1.4.10 for free as well: nothing is positioned, so there is
   nothing to push the page sideways at 320px. */
.row-menu { display: inline-block; }

.row-menu__summary {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; min-height: 44px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--brand-navy); cursor: pointer;
  /* Both spellings, as in the nav menu: a stray triangle next to the dots. */
  list-style: none;
}
.row-menu__summary::-webkit-details-marker { display: none; }
.row-menu__summary::marker { content: ""; }
.row-menu__summary:hover { background: var(--brand-tint); border-color: var(--brand-navy); }
/* Which row is open, when the panel below it is a plain list of links. */
.row-menu[open] .row-menu__summary { background: var(--brand-navy); color: #fff; border-color: var(--brand-navy); }

.row-menu__items {
  list-style: none; margin: .4rem 0 0; padding: .25rem; max-width: none;
  display: flex; flex-direction: column; gap: .1rem;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 6px 16px rgba(12, 35, 64, .10);
}
.row-menu__items a {
  display: flex; align-items: center;
  padding: .5rem .6rem; min-height: 44px;
  color: var(--brand-blue-dark); text-decoration: none;
  border-radius: calc(var(--radius) - 2px); white-space: nowrap;
}
.row-menu__items a:hover { background: var(--brand-tint); color: var(--brand-navy); }
/* Deleting a session destroys its ratings, so it does not read as the two links
   above it. Two classes and an element, so it outranks the rule above without
   !important. --bad is 7.5:1 on --surface and 6.6:1 on the tint that
   `tbody tr:hover` paints behind the whole row. */
.row-menu__items a.row-menu__danger { color: var(--bad); }
.row-menu__items a.row-menu__danger:hover { background: #fbeaea; color: #7d1919; }

/* The panel opens in flow, so it becomes the widest thing in the cell and the
   column sizes to it - which would reflow every other column the moment somebody
   opened a menu, and read as a bug. Reserved up front instead. Still a third of
   what the button block was taking.
   Right-aligned so the control sits on the table's edge rather than stranded in
   the middle of its own column, and top-aligned so it does not move down the row
   as the speaker list grows. */
.sessions__actions { width: 11rem; text-align: right; vertical-align: top; }
/* text-align is inherited, and the panel is a left-aligned list. */
.sessions__actions .row-menu__items { text-align: left; }
/* Rate sits above the kebab rather than inside it. Stacked deliberately rather than by
   wrapping: side by side they are 11rem exactly, so they wrapped anyway and landed a
   row apart with a full 1rem of gap between them, which read as a mistake. A column
   also keeps the reservation above honest - the widest thing in the cell is still the
   open panel, not a line of two controls. */
.sessions__actions .row-actions {
  flex-direction: column; align-items: flex-end; gap: .35rem;
}

/* .link-button is white for the navy header. In page content it has to read as an
   ordinary link, and it has to stay a real <button> - "retire this topic" changes
   state, so it posts. */
.link-button--inline { color: var(--brand-blue-dark); min-height: 44px; }
.link-button--inline:hover { color: var(--brand-navy); }

/* --- topics -------------------------------------------------------------- */
/* Assigning topics is a checkbox per topic, grouped under its parent. A multi-select
   listbox would be one control instead of twenty, and unusable on a phone and with a
   screen reader alike. Columns keep a fifteen-row taxonomy on one screen. */
.topic-columns {
  display: grid; gap: .25rem 1rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
}
.topic-group { border: 0; padding: 0; margin: 0 0 1rem; }
.topic-group legend {
  padding: 0; font-family: var(--mono); font-size: .68rem; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-muted);
}
/* One per line so the 44px targets do not sit shoulder to shoulder. */
.topic-columns .checkbox { display: flex; margin-right: 0; }

/* Read-only topic tags, on the speaker and session detail views. */
.tag-list { list-style: none; display: flex; flex-wrap: wrap; gap: .4rem; margin: .4rem 0 0; padding: 0; }
.tag {
  display: inline-flex; align-items: center;
  background: var(--brand-tint); color: var(--brand-navy);
  border: 1px solid var(--line); border-radius: 999px;
  padding: .15rem .7rem; font-size: .85rem;
}
.tag--inferred { background: var(--surface); color: var(--ink-muted); }

/* --- the rating queue ---------------------------------------------------- */
/* The screen a rater starts from: what is waiting for them, and one press per row to
   the form. Almost all of it is the existing table, notice and KPI vocabulary - what
   follows is only what those did not already cover. */

/* One press from the row to the form, so the action is a real button rather than the
   text link the other tables use. Fixed at its content width: .btn goes width:100% on
   a phone (see the narrow-viewport block), which is right for a form's button row and
   wrong for a cell in a table that already scrolls inside .table-scroll. */
.queue__actions { white-space: nowrap; vertical-align: middle; }
.queue__actions .btn { width: auto; }

/* What you covered on a session you have rated: a tick, a count and maybe a badge.
   Three different kinds of thing, so they are spaced items rather than inline text -
   run together they read as one garbled phrase ("THE SESSION 1 of 1 speaker"). */
.rated-parts { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .7rem; }

/* "You did this one." A word and a tick, never the tick alone (WCAG 1.4.1), and the
   same --ok as an established score - it is the same claim about the same subject. */
.mine {
  display: inline-flex; align-items: center; gap: .3rem; color: var(--ok);
  font-family: var(--mono); font-size: .68rem; letter-spacing: .08em;
  text-transform: uppercase; white-space: nowrap;
}
.mine .icon { width: .85rem; height: .85rem; }

/* The queue's second table, folded away: after a week of use it is the longer of the
   two and it is never why somebody opened the screen. Styled as the filter panel's
   summary is, because it is the same gesture - and, like it, deliberately outside the
   set of classes site.js light-dismisses. */
.done-panel { margin: 1.5rem 0 0; }
.done-panel > summary {
  cursor: pointer; color: var(--brand-blue-dark); font-weight: 600;
  padding: .4rem 0; min-height: 44px; display: flex; align-items: center;
}
.done-panel > summary:hover { color: var(--brand-navy); }

/* The rating form's own header line: where this session sits in the event's work, and
   the two ways out that are not the form. A row that wraps rather than a paragraph of
   prose, so the count and the links stay legible beside each other at 320px. */
.rate-progress {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1.25rem;
  margin: 0 0 1rem;
}
.rate-progress a { min-height: 44px; display: inline-flex; align-items: center; }

/* The dashboard's call to action: a notice that ends in a button rather than only
   telling you something. The button is the only reason this variant exists. */
.notice--action { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1.25rem; }
.notice--action p { margin: 0; flex: 1 1 18rem; }
.notice--action .btn { flex: 0 0 auto; }

/* --- audit log ----------------------------------------------------------- */
/* Five filters that belong to one question, so they sit on one line where there
   is room and stack where there is not. Auto-fit rather than a fixed column
   count: the same grid has to survive a sixth filter without being re-tuned. */
.filters {
  display: grid; gap: 0 var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  align-items: end;
  margin-bottom: 1.5rem;
}
/* The controls are narrow here on purpose - the global 32rem max-width is for a
   form column, not for a filter strip. */
.filters select, .filters input[type=date] { max-width: 100%; }
/* Buttons take the whole last row rather than sharing a 11rem cell with a
   date picker, which is how "Apply" ends up two words tall. */
.filters .btn-row { grid-column: 1 / -1; margin-top: .5rem; }
.filters__note { grid-column: 1 / -1; margin: 0; }

/* Five columns of dates, names and before-and-after pairs do not compress: below
   this width the record column wraps one word per line and the change column is
   unreadable. Scrolling the card sideways is the better failure - which is what
   .table-scroll is for, and the body itself still does not move (WCAG 1.4.10). */
.audit-table { min-width: 48rem; }

/* --- rating dimensions --------------------------------------------------- */
/* The speaker questions and the session questions are two tables, one under the
   other, generated from the same block. Auto layout sizes a table from its own
   content, so one long description in the lower table widened its Question
   column and stepped every column after it out of line with the table above -
   the two are meant to read as one table interrupted by a heading.
   Fixed layout plus a declared width per column is what holds them together.
   Only the five narrow columns are declared; Question takes what is left, so the
   descriptions get the slack rather than a share of it. Widths are content-sized
   with the header in mind: `thead th` is nowrap, so a column narrower than its
   own label overflows rather than wrapping, and TIMES SCORED is the long one.
   min-width, and deliberately no tabindex on the container - same as
   .audit-table. It exceeds the viewport only on a phone, which is what
   .table-scroll is for; a tab stop on every table to cover a case that does not
   arise at the tested sizes is the thing the ratings-table note argues against. */
.dimensions-table { table-layout: fixed; min-width: 56rem; }
.dimensions-table thead th:nth-child(2) { width: 5rem; }    /* Order */
.dimensions-table thead th:nth-child(3) { width: 6rem; }    /* Scale, "1-10" */
.dimensions-table thead th:nth-child(4) { width: 8.5rem; }  /* Times scored */
.dimensions-table thead th:nth-child(5) { width: 11rem; }   /* "Retired 12 Aug 2026" */
.dimensions-table thead th:nth-child(6) { width: 11rem; }   /* Edit + Ask again, one line */

/* What kind of change a row records. Never colour alone (WCAG 1.4.1): the word
   is the information and the colour only agrees with it. Every token used here
   clears 4.5:1 on --surface AND on --brand-tint, because `tbody tr:hover` paints
   the tint behind the badge - see the note on --warn. */
.change-kind {
  display: inline-flex; align-items: center;
  font-family: var(--mono); font-size: .68rem; letter-spacing: .08em;
  text-transform: uppercase; padding: .15rem .55rem;
  border-radius: 999px; border: 1px solid currentColor; white-space: nowrap;
}
.change-kind--created  { color: var(--ok); }
.change-kind--changed  { color: var(--brand-navy); }
.change-kind--deleted  { color: var(--bad); }
/* Retired and restored are both updates to a soft-delete stamp, not deletions -
   amber rather than red, because the record is still there. */
.change-kind--retired  { color: var(--warn); }
.change-kind--restored { color: var(--ok); }

/* The before-and-after list inside a change. Values are user data of unknown
   length - an abstract can be paragraphs - so this wraps and breaks rather than
   truncating: a truncated audit value is the one thing this screen must not do,
   since for a deleted record it is the only copy left. */
.diff { list-style: none; margin: 0; padding: 0; max-width: 42rem; }
.diff li {
  display: flex; flex-wrap: wrap; gap: .3rem .45rem;
  align-items: baseline; padding: .15rem 0;
}
.diff__field {
  font-family: var(--mono); font-size: .68rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-muted);
  flex: 0 0 auto;
}
.diff__value { overflow-wrap: anywhere; }
.diff__value--old { color: var(--ink-muted); }
.diff__arrow { color: var(--ink-muted); flex: 0 0 auto; }

/* One record's whole history, from any row that mentions it. Sits under the
   record name as a second line so it never competes with it for the click. */
.audit-history { display: block; font-size: .85rem; }

/* --- attributed rating detail -------------------------------------------- */
/* Raters down the side, dimensions across the top. Wide on purpose: the question
   is comparative, and reading down a column is how "did everyone say that, or was
   it one person?" gets answered. Same trade as the audit table - the card scrolls
   sideways, the body never does (WCAG 1.4.10, pinned by AxeTests). */
.ratings-table { min-width: 44rem; }

/* Dimension names are sentences ("Delivery & engagement"), and the global
   thead nowrap turns each into a column as wide as the phrase. Let them wrap and
   put the scale on its own line: the table narrows by a third and fits most
   screens without scrolling at all. */
.ratings-table thead th { white-space: normal; }
.rating-scale-of { display: block; font-size: .7rem; text-transform: none; letter-spacing: 0; }

/* The kind chip sits above the heading rather than beside it: "Speaker" and a
   person's name on one line reads as part of the name at a glance. */
.rating-panel__kind { display: block; margin-bottom: .15rem; }

/* The address and access status under a rater's name. Its own line, because the
   name is what identifies the row and the rest is confirmation. */
.rating-rater__detail { display: block; }

/* The note spans the whole table on its own row, under the rater it belongs to.
   Capped at the reading measure rather than the table width: a comment set across
   a 60rem table is a line nobody can track back to its start. */
.rating-row--noted > th, .rating-row--noted > td { border-bottom: 0; }
.rating-note-row td { padding-top: 0; }
.rating-note {
  margin: 0; max-width: var(--measure);
  overflow-wrap: anywhere; font-size: .9rem;
}
.rating-note .label-mono { display: block; }

/* Column means, and they are a different claim from the scores above them. The
   tint separates the row without needing a heavier rule, and matches thead. */
.ratings-table tfoot th, .ratings-table tfoot td {
  background: var(--brand-tint); border-top: 2px solid var(--line); border-bottom: 0;
}
/* tbody's hover rule paints the same tint, so the foot needs its own hover state
   or the two become indistinguishable under the cursor. */
.ratings-table tfoot tr:hover { background: var(--brand-tint); }

/* --- footer -------------------------------------------------------------- */
.site-footer {
  border-top: 1px solid var(--line);
  color: var(--ink-muted); font-size: .85rem;
  padding: 1.5rem 1rem; margin-top: 3rem;
}
.site-footer__inner { max-width: 1200px; margin: 0 auto; }

/* --- small screens ------------------------------------------------------- */
/* Rating entry happens on a phone at a conference - that is the case to get
   right, not an afterthought. */
@media (max-width: 640px) {
  /* Chrome was eating ~29% of an 812px viewport before any content. Rating entry
     happens on a phone at a conference, so that space belongs to the form. */
  .site-header { padding: .5rem .75rem; }
  .site-header__inner { gap: .6rem; flex-wrap: nowrap; }
  /* 34px, not 46: the PNG wordmark is wider per pixel of height than the SVG it
     replaced (725:134 vs. 1510:320), and at 46px it pushed "Sign in" past the
     edge of a 320px viewport - measured, not guessed: logo 249px + gap 10px +
     button 90px = 349px against 296px available. 34px leaves ~12px to spare at
     the narrowest width this is tested at. */
  .site-header__logo { height: 34px; }
  /* The signed-in address is confirmable from the account page; on a phone the
     sign-out control is what needs to be reachable. */
  .account__name { display: none; }
  .account { min-height: 44px; }
  .site-nav a { padding: .6rem .7rem; font-size: .9rem; }
  /* In flow rather than floating: a phone has no room for the menu to overlay
     anything, and an absolutely-positioned panel is how a 320px viewport starts
     scrolling sideways. It pushes the page down instead, which is what the
     reflow test insists on. */
  .nav-menu__summary { padding: .6rem .7rem; font-size: .9rem; }
  /* Its own row, so the panel opens underneath rather than widening a flex item
     until the bar itself overflows. */
  .nav-menu__host { flex: 1 1 100%; }
  .site-nav .nav-menu[open] .nav-menu__items { margin: .25rem 0 .5rem; }
  main { padding: 1rem .75rem 3rem; }
  h1 { font-size: 1.35rem; }
  .rating-row { flex-direction: column; align-items: stretch; gap: .5rem; }
  .rating-scale { justify-content: space-between; }
  .rating-scale label { flex: 1 1 0; }
  .btn { width: 100%; }
  /* The header's sign-in link is a .btn too, so the full-width rule above reached
     it - fine when there was slack next to a narrower logo, wrong once the PNG
     wordmark (wider than the SVG it replaced) left less room and "Sign in"
     wrapped onto two lines. It belongs on one line at its own width, not
     stretched to fill whatever the header has left. */
  .site-header .btn { width: auto; white-space: nowrap; }
  /* Full-width buttons stack one per line on their own, and the row's gap already
     spaces them - so the margin this used to add is gone with the layout that needed
     it. Stretch rather than centre, or a wrapped button would be centred in a line of
     its own width. */
  .btn-row { align-items: stretch; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

@media print {
  .site-nav, .btn, .skip-link { display: none; }
}

/* --- the rating board: one card per event -------------------------------- */
/* The screen a rater lands on. Its unit is an event rather than a session because
   rating happens in a sitting - on the way home from one conference - so the first
   question is which pile, not which talk. Wider minimum than the shared .card-grid
   default: a card carries a name, dates, a window, a progress line and two rows of
   controls, and at 240px the buttons wrap to one per line. */
/* auto-FILL, not auto-fit, and with a maximum. auto-fit collapses the empty tracks, so
   a board holding one event drew a single card 1280px wide - which reads as a banner
   rather than as one of a set. Keeping the empty tracks means the first card is the
   width every later card will be. */
.rate-board {
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 24rem));
  justify-content: start;
}

.rate-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1rem;
  display: flex; flex-direction: column; gap: .6rem;
}
/* Finished, not disabled: the card stays legible and stays usable - editing a rating
   you already gave is the same form - so this is a quieter border, never a grey-out. */
.rate-card--done { border-color: var(--line); background: var(--bg); }

.rate-card__head { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .6rem; }
.rate-card__name { margin: 0; font-size: 1.05rem; line-height: 1.3; flex: 1 1 12rem; }

/* Dates, city and the window, as separate items rather than one sentence: three
   unrelated facts run together read as a single garbled phrase, and the middot
   separators have to survive wrapping at 320px. */
.rate-card__meta {
  margin: 0; display: flex; flex-wrap: wrap; gap: .1rem .5rem;
  color: var(--ink-muted); font-size: .9rem;
}
.rate-card__meta > span + span::before { content: "\00b7"; margin-right: .5rem; }

/* The number first and the bar under it: the bar is decoration and is hidden from
   assistive technology, so leading with it puts an unlabelled blue rule above the
   sentence that explains it. */
.rate-card__progress { margin: 0; display: flex; flex-direction: column-reverse; gap: .35rem; font-size: .9rem; }
/* Decoration only, and hidden from assistive technology - the count beside it carries
   the information (WCAG 1.4.1), exactly as the score meters do. */
.rate-card__bar {
  display: block; height: 5px; border-radius: 3px;
  background: var(--surface-muted); position: relative; overflow: hidden;
}
.rate-card__bar u {
  position: absolute; inset: 0 auto 0 0; display: block;
  border-radius: 3px; background: var(--brand-blue);
}

.rate-card__raters { margin: 0; }

.rate-card__actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
  margin-top: auto;   /* the primary press sits on the baseline whatever the card holds */
}
.rate-card__actions a:not(.btn) { min-height: 44px; display: inline-flex; align-items: center; }

/* The attendance question. Separated by a rule rather than spacing, because it asks
   about the event while everything above it is about the work - and without the line
   the two buttons read as a third and fourth action. */
.rate-card__attend {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem;
  border-top: 1px solid var(--line); padding-top: .7rem;
}
/* The question takes the whole line, so the two answers stay side by side under it.
   Sharing a line with them, it pushed the second button onto a row of its own at any
   card width - which read as one answer and an afterthought. */
.rate-card__ask { flex: 1 0 100%; color: var(--ink-muted); font-size: .9rem; }
/* .btn goes width:100% on a phone, which is right for a form's button row and wrong
   for two side-by-side answers to one question. */
.rate-card__attend .btn { width: auto; padding: .4rem .8rem; }

/* A way back up, above the heading. Not a full breadcrumb trail: there is exactly one
   place this screen is reached from and one word is enough. */
.crumb { margin: 0 0 .5rem; }
.crumb a { display: inline-flex; align-items: center; gap: .3rem; min-height: 44px; }
