/* AB#9981. Meeting Speaker Participation report.
 *
 * Tokens and type come from the PublicInput Admin UI design kit, carried over from the
 * approved prototype at resources.publicinput.com/prototypes/meeting-speaker-report.
 *
 * The brand blue has three jobs and only one of them is text. #0395FF is 3.1:1 on
 * white, so it is a FILL only (rules, bars, borders); #0277CC is the filled-chip
 * background that white text sits on at 4.65:1; #00548C is the text blue, at or above
 * 5.6:1 on every surface in this document including the washes. The kit greys #7E8299
 * (3.8:1) and #9098A8 (2.9:1) are retired for text and survive only as hairlines.
 */

/* ==========================================================================
   Brand fonts.

   These MUST live here rather than in the view. ReportPreview.cshtml sets
   Layout = null and hand-rolls its own <head>, so there is no shared layout to
   hang a font <link> on -- which is exactly why --spr-display and --spr-body
   named Montserrat and Libre Franklin for months while the report silently
   rendered in Arial.

   @font-face cannot be scoped, so unlike every other rule in this file it sits
   outside .spr. That is deliberate; do not "fix" it by nesting it.

   Paths are ../fonts/ because this stylesheet lives one directory deeper than
   the four admin stylesheets that declare the same faces (project-admin-bs5,
   email-admin-bs5, customer-settings-bs5, InterpreterMode) against
   /Content/fonts/. Same files, same weights, one extra hop.
   ========================================================================== */
/*
  The brand faces. The files live in the design-system tree at
  css/publicinput-ds/fonts/, NOT under Content/, so the path out of Content/Meetings/
  is two levels up and across. An earlier version used "../fonts/", which resolves to
  Content/fonts/ -- a directory that does not exist -- so all nine faces 404'd. It looked
  fine because the Metronic layout pulls Montserrat from Google Fonts, which masked the
  failure for every weight except Libre Franklin.

  @font-face cannot be scoped, so these sit at top level while every other rule in this
  file is under .spr.
*/
@font-face {
  font-family: "Montserrat";
  src: url("../../css/publicinput-ds/fonts/Montserrat-Light.ttf") format("truetype");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Montserrat";
  src: url("../../css/publicinput-ds/fonts/Montserrat-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Montserrat";
  src: url("../../css/publicinput-ds/fonts/Montserrat-SemiBold.ttf") format("truetype");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Montserrat";
  src: url("../../css/publicinput-ds/fonts/Montserrat-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Libre Franklin";
  src: url("../../css/publicinput-ds/fonts/LibreFranklin-Light.ttf") format("truetype");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Libre Franklin";
  src: url("../../css/publicinput-ds/fonts/LibreFranklin-LightItalic.ttf") format("truetype");
  font-weight: 300;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Libre Franklin";
  src: url("../../css/publicinput-ds/fonts/LibreFranklin-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Libre Franklin";
  src: url("../../css/publicinput-ds/fonts/LibreFranklin-SemiBold.ttf") format("truetype");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Libre Franklin";
  src: url("../../css/publicinput-ds/fonts/LibreFranklin-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

.spr {
  --pi-blue: #0395ff;
  --pi-blue-d: #0277cc;
  --pi-blue-ink: #00548c;
  --pi-blue-light: #dbf1ff;
  --pi-blue-edge: #b6e0f8;
  --pi-orange: #ff7c1d;

  /* The chart-safe orange. --pi-orange is 2.58:1 on white, below the 3:1 floor WCAG 1.4.11
   * sets for a 2px stroke and for the legend swatch a reader maps a colour to a name with.
   * The brand orange is untouched everywhere it is used as a fill or a border. */
  --pi-orange-ink: #b8560a;

  --spr-bg: #ffffff;
  --spr-bg-2: #fbfcfd;
  --spr-canvas: #f4f6fa;
  --spr-ink: #1f2430;
  --spr-mid: #3f4254;
  --spr-soft: #52566b;
  --spr-hairline: #9098a8;
  --spr-line: #e4e6ef;
  --spr-line-2: #f2f3f6;

  --spr-ok: #1b6b3a;
  --spr-bad: #a33223;
  --spr-neutral: #5c6270;
  --spr-warn: #7a5f00;
  /* Two channels and only two: someone is in the room or on the phone. */
  --spr-phone: #0b6e7a;

  --spr-display: "Montserrat", "Helvetica Neue", Arial, sans-serif;
  --spr-body: "Libre Franklin", "Helvetica Neue", Arial, sans-serif;
  --spr-pad-x: 52px;

  color: var(--spr-ink);
  font-family: var(--spr-body);
  font-size: 14px;
  line-height: 1.55;

  /* The report shell centres its whole column (#reportPreview carries .text-center). A
     record of who spoke is a document, not a poster, so each insight re-establishes normal
     reading alignment for itself rather than inheriting the shell's. */
  text-align: left;
}

@media (max-width: 720px) {
  .spr {
    --spr-pad-x: 20px;
  }
}

.spr *,
.spr *::before,
.spr *::after {
  box-sizing: border-box;
}

.spr h1,
.spr h2,
.spr h3 {
  font-family: var(--spr-display);
  margin: 0;
}

/* One focus ring for everything focusable, rather than an allow-list that new
   controls keep falling off. 3:1 against the sheet and against every wash. */
.spr :is(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--pi-blue-ink);
  outline-offset: 2px;
  border-radius: 6px;
}

/* The .spr-header* family and .spr-body were removed with the cover block (audit A1,
   dropped from scope). Their comment claimed they were "rendered server-side", but
   nothing rendered them: ReportPreview.cshtml sets Layout = null and emits no such
   markup, and none of the eight TypeScript files reference the classes. They were
   dead from the first commit. */

/* Each insight is one block inside the report shell's column, not a page of its own, so it
   carries its own breathing room rather than relying on a wrapper it no longer has. */
.spr > .spr-block:first-child,
.spr > .spr-viewbar:first-child {
  margin-top: 0;
}

.spr > .spr-block:last-child {
  margin-bottom: 0;
}

/* -- view bar -- */

.spr-viewbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 10px 0 18px;
}

.spr-spacer {
  flex: 1;
  min-width: 8px;
}

.spr-stamp {
  font-size: 0.8rem;
  color: var(--spr-soft);
}

/* -- blocks -- */

.spr-block {
  background: var(--spr-bg);
  border: 1px solid var(--spr-line);
  border-radius: 10px;
  padding: 22px 24px 24px;
  margin-bottom: 18px;
}

.spr-block-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.spr-block-head h2 {
  font: 700 20px/1.2 var(--spr-display);
  letter-spacing: -0.005em;
}

.spr-sub {
  font-size: 0.82rem;
  color: var(--spr-soft);
  margin: 0;
}

.spr-section {
  margin-top: 18px;
}

.spr-section-title {
  font: 700 13px/1.3 var(--spr-display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pi-blue-ink);
  padding-bottom: 6px;
  border-bottom: 2px solid var(--pi-blue);
  margin-bottom: 4px;
}

/* -- stat tiles -- */

.spr-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: 12px;
}

.spr-stat {
  border: 1px solid var(--spr-line);
  border-top: 3px solid var(--pi-blue);
  border-radius: 6px;
  padding: 14px 15px;
  background: var(--spr-bg);
}

.spr-stat-v {
  display: block;
  font: 700 28px/1.05 var(--spr-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.spr-stat-k {
  display: block;
  font: 600 10.5px/1.3 var(--spr-display);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--spr-soft);
  margin-top: 8px;
}

.spr-stat-s {
  display: block;
  font-size: 11.5px;
  color: var(--spr-soft);
  margin-top: 3px;
}

/* A zero count for these two is the ordinary case, so the tile is quietened -- but NOT
   with opacity. opacity: 0.62 composited --spr-soft (#52566b, 7.4:1) down to ~#9496a3 on
   white, which is 2.93:1 and fails WCAG 2.1 AA for text this size. Contrast is not a
   thing to spend for emphasis on a platform with a legal accessibility obligation.
   De-emphasise the SURFACE instead: drop the accent rule and tint the background, leaving
   every glyph at full contrast. */
.spr-stat[data-stat="orphanedSignUps"][data-value="0"],
.spr-stat[data-stat="overCap"][data-value="0"] {
  background: var(--spr-bg-2);
  border-top-color: var(--spr-line);
}

/* -- grids. Grids rather than tables, so a roster can reflow to two rows per record
   at 320px without losing a cell. -- */

.spr-grid {
  border-top: 1px solid var(--spr-line);
  overflow-x: auto;
}

.spr-row {
  display: grid;
  grid-template-columns: minmax(180px, 2.2fr) repeat(auto-fit, minmax(96px, 1fr));
  gap: 12px;
  align-items: start;
  padding: 9px 4px;
  border-bottom: 1px solid var(--spr-line-2);
}

.spr-row-head {
  border-bottom: 1px solid var(--spr-line);
}

.spr-grid-recon .spr-row {
  grid-template-columns: minmax(140px, 1.6fr) repeat(3, minmax(84px, 1fr));
}

.spr-cell {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 0.86rem;
  color: var(--spr-mid);
}

.spr-cell-head {
  font: 600 10px/1.3 var(--spr-display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--spr-soft);
}

.spr-num {
  font-variant-numeric: tabular-nums;
}

.spr-total {
  font-weight: 700;
  color: var(--spr-ink);
}

/* -- sort headers. The direction lives in the accessible name, because aria-sort is
   invalid outside a real columnheader and these are grids. -- */

/* WCAG 2.5.5 -- pointer targets are at least 44x44 CSS px.
   The controls below were all sized for a mouse (24-32px). This report is read on phones:
   AC-39 gives the profile a bottom-sheet presentation at 375px for exactly that reason, so
   its controls have to be thumb-sized too. Raising min-height here rather than only inside
   a mobile breakpoint, because a 44px target is not worse with a mouse -- it is only
   larger -- and one rule cannot drift out of step with the breakpoint the way two can. */
.spr-sort {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: 0;
  padding: 4px 2px;
  min-height: 44px;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
}

.spr-sort:hover {
  color: var(--pi-blue-ink);
}

.spr-sort.is-active {
  color: var(--pi-blue-ink);
}

/* The neutral mark is quieter than an active one, but it is still a real mark: a column
   that offers a sort has to say so before it is sorted, not after. It is decoration --
   aria-hidden in the markup -- so the muting costs a reader nothing. */
.spr-sort-mark {
  font-size: 0.9em;
}

.spr-sort:not(.is-active) .spr-sort-mark {
  color: var(--spr-line);
}

.spr-sort:hover .spr-sort-mark {
  color: inherit;
}

/* -- agenda items and rosters -- */

/* Declared BEFORE the agenda templates below, which qualify this same row with
   .spr-grid-agenda. A base rule that trails its own qualified form is a specificity
   descent -- it reads as the override when it is the default. */
.spr-row-total {
  border-top: 2px solid var(--spr-line);
  padding-top: 10px;
  margin-top: 4px;
}


/* A FIXED template, not the shared auto-fit .spr-row.
 *
 * auto-fit drops a track whose cell is empty, so an agenda where one item has no phone
 * callers would silently render with different columns from the item above it. It also
 * cannot be reasoned about at 320px. Both .spr-grid-recon and .spr-grid-directory
 * already pin their own templates for the same reason. */
.spr-grid-agenda .spr-row-head,
.spr-grid-agenda .spr-row-total,
.spr-grid-agenda .spr-item-head {
  display: grid;

  /* Every track but the title is FIXED, not fr.
   *
   * This grid never sees a viewport. It renders inside the report canvas, which is a
   * fixed 728px page shell at EVERY viewport width -- measured identical at 1280, 1440
   * and 1860 -- so the row is always 678px and a wider screen buys the title nothing.
   * With every track on an fr the title shared the surplus five ways and settled at
   * 204.6px, which is narrower than the single token "Commissions/Committees/Special".
   * A token wider than its track is exactly when overflow-wrap: break-word is SUPPOSED
   * to character-break, so Los Angeles County read "...Specia / l Districts". The break
   * property was never wrong; the track was too narrow to give it a choice.
   *
   * The ITEM column is the disclosure control and the item number, so the 110px the
   * disclosure used to hold on the right is gone and 80px is spent on the left instead:
   * "SD-1" is the widest number this agenda prints, and 80px holds it with the caret and
   * the focus ring beside it. Everything else in the row is its measured max-content need
   * plus a hair -- "29F / 8O / 4N" for position -- so nothing clips to buy the room.
   *
   * 678 - (80 + 64 + 64 + 52 + 68) - (5 x 8px gap) = 310px of TITLE, against 280px before
   * and 204.6px before that. The 720px stacked block below overrides this wholesale. */
  grid-template-columns:
    80px                /* item: caret + number, the disclosure control */
    minmax(150px, 1fr)  /* title -- takes ALL the remaining width       */
    64px                /* in person                                    */
    64px                /* by phone                                     */
    52px                /* time                                         */
    68px;               /* position                                     */
  gap: 8px;
  align-items: start;
}

.spr-item {
  border-bottom: 1px solid var(--spr-line-2);
  padding: 10px 0;
}

/* The TITLE column: status badge, then the title, then the section when one is shown.
 *
 * A wrapping row rather than a column, because the badge and the section chip are
 * inline-sized things that should sit beside each other when both are present. The title
 * claims a whole line of its own (below), so whatever precedes it lands on the line
 * above it -- which is how the badge comes to sit ABOVE the title without either
 * element being positioned. */
.spr-item-ident {
  flex-direction: row;
  align-items: baseline;
  gap: 4px 8px;
  flex-wrap: wrap;
}

/* The control fills its track and starts at the top of the row, level with the first
   line of the title beside it. */
.spr-item-disclose {
  align-items: stretch;
  justify-content: flex-start;
}

/* The roster spans the full width beneath its item rather than sitting in a column. */
.spr-item-panel-row {
  display: block;
}

.spr-item-panel {
  display: block;
  padding: 0;
}

/* spoke / signed-up, with the figure carrying the ink. */
.spr-ratio {
  font-variant-numeric: tabular-nums;
  color: var(--spr-ink);
  font-weight: 600;
}

.spr-ratio-of {
  color: var(--spr-soft);
  font-weight: 400;
}

/* One bar mechanism, same --spr-fill custom property the breakdown panels already use,
   so there is one idiom for "a proportion" in this stylesheet rather than two. */
.spr-bar {
  display: block;
  height: 4px;
  border-radius: 2px;
  background: var(--spr-line-2);
  overflow: hidden;
  position: relative;
}

.spr-bar::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--spr-fill, 0%);
  background: var(--pi-blue);
}

[data-column-key="phone"] .spr-bar::after {
  background: var(--spr-phone);
}

.spr-bar-note {
  font-size: 0.7rem;
  color: var(--spr-soft);
  font-variant-numeric: tabular-nums;
}

/* Three segments, one row. Fills only -- the readout beneath carries the meaning, and
   colour is never the sole channel. */
.spr-bar-stack {
  display: flex;
  height: 6px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--spr-line-2);
}

.spr-bar-stack > span {
  width: var(--spr-fill, 0%);
}

.spr-bar-for { background: var(--spr-ok); }
.spr-bar-against { background: var(--spr-bad); }
.spr-bar-neutral { background: var(--spr-neutral); }

/* Position counts as icon + number pairs. Shape carries the meaning as well as colour
   (tick, cross, bar), so the tally reads in greyscale. Colours are the same tokens the
   legend swatches and the stacked bar above use. */
.spr-postally {
  display: inline-block;
  max-width: 100%;
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  color: var(--spr-mid);
}

/* Wraps rather than overflows: the position track is narrow in print and in the
   directory, and three pairs on one unbreakable line pushed the printed page 35px wide. */
.spr-postally-pairs {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 10px;
}

.spr-postally-pair {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}

.spr-postally-icon {
  flex: none;
  fill: none;
  stroke: currentcolor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.spr-postally-for .spr-postally-icon { color: var(--spr-ok); }
.spr-postally-against .spr-postally-icon { color: var(--spr-bad); }
.spr-postally-neutral .spr-postally-icon { color: var(--spr-neutral); }

.spr-postally-n {
  color: var(--spr-ink);
  font-weight: 600;
}

/* A blank with a mouseover. The dash is the visible value; the tooltip names the absence. */
.spr-blank {
  cursor: help;
}

/* Below the agenda grid's natural width, stack each row and label its cells. Without
   this the grid becomes an inner horizontal scroller holding the primary content, which
   is a 1.4.10 finding in spirit even where the data-table exception applies. */
@media (max-width: 720px) {
  .spr-grid-agenda .spr-row-head {
    display: none;
  }

  .spr-grid-agenda .spr-item-head,
  .spr-grid-agenda .spr-row-total {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
  }

  /* Stacked, the control is one line at the top of the item, not a block spanning the
     width of the phone. inline-flex on a stretched grid cell would still be as wide as
     the cell, so the cell itself is told to shrink-wrap it. */
  .spr-grid-agenda .spr-item-disclose {
    align-items: flex-start;
  }

  .spr-grid-agenda .spr-roster-toggle {
    width: auto;
  }

  .spr-grid-agenda .spr-cell[data-column-label]::before {
    content: attr(data-column-label);
    display: block;
    font: 700 9.5px/1 var(--spr-display);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--spr-soft);
  }
}

.spr-item-no {
  font: 700 11px/1 var(--spr-display);
  /* Item numbers stack down a column, so they need to line up. The other figure
     classes already carry this; .spr-count and .spr-page-of deliberately do not,
     because those read as prose rather than as a column. */
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  color: var(--pi-blue-ink);
  background: var(--pi-blue-light);
  border: 1px solid var(--pi-blue-edge);
  border-radius: 4px;
  padding: 3px 7px;
  flex: none;
}

/* A whole line, always. Sharing the line with the status badge would hand a 310px track
   back to roughly 220px of text and put the widened column back where it started, and it
   is also what puts the badge on the line above rather than trailing the title. */
.spr-item-title {
  flex: 1 1 100%;
  min-width: 0;
  font-weight: 600;
  color: var(--spr-ink);
}

.spr-item-allot {
  font-size: 0.78rem;
  color: var(--spr-soft);
  white-space: nowrap;
}

/* The disclosure control: a caret and the item number, filling the ITEM column.
 *
 * The button itself is unstyled -- no border, no fill. The caret carries the affordance
 * and the pointer target, which is what lets the whole control sit inside an 80px track
 * without reading as a second badge competing with the status badge next to it.
 *
 * 44x44 comes from the caret plus the button's own min-height, so the target clears
 * WCAG 2.5.5 at every pointer type rather than only under (pointer: coarse). */
.spr-roster-toggle {
  display: inline-flex;

  /* Top-aligned, not centred. The button is 44px tall for the target size but the row is
     as tall as the title beside it, and a centred caret drifts down the row as the title
     grows -- on a six-line title it ends up level with nothing at all. Pinned to the top
     it stays level with the status badge and the title's first line. */
  align-items: flex-start;
  gap: 6px;
  min-height: 44px;
  width: 100%;
  padding: 1px 0 0;
  border: 0;
  background: none;
  font: 600 11.5px/1 var(--spr-body);
  color: var(--spr-ink);
  cursor: pointer;
  text-align: left;
}

/* The caret is the target and the state. Rotation is the only motion in the table and it
   is a 120ms transform, so it is suppressed wholesale under reduced-motion below. */
.spr-chev {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 24px;
  height: 24px;
  border: 1px solid var(--spr-line);
  border-radius: 6px;
  background: #fff;
  color: var(--spr-mid);
  font-size: 13px;
  line-height: 1;
  transition: transform 120ms ease, background-color 120ms ease, border-color 120ms ease;
}

.spr-roster-toggle:hover .spr-chev {
  border-color: var(--pi-blue-d);
  background: var(--pi-blue-light);
  color: var(--pi-blue-ink);
}

.spr-roster-toggle[aria-expanded="true"] .spr-chev {
  transform: rotate(90deg);
  border-color: var(--pi-blue-d);
  background: var(--pi-blue-d);
  color: #fff;
}

@media (prefers-reduced-motion: reduce) {
  .spr-chev {
    transition: none;
  }
}

/* The item number inside the control is PLAIN, not the pill .spr-item-no wears in the
   speaker profile. In a column of its own the pill's border and fill are a second frame
   around a number that already has the caret beside it; the weight and the blue are
   enough to make it the row's anchor. */
.spr-roster-toggle .spr-item-no {
  min-width: 0;
  font-size: 14px;
  letter-spacing: 0;
  background: none;
  border: 0;
  border-radius: 0;

  /* Padded onto the caret's optical centre rather than left on its own baseline: the
     two are a single mark, and a 24px box next to a 14px cap height reads crooked. */
  padding: 5px 0 0;
  overflow-wrap: anywhere;
}

/* An item nobody signed up for recedes instead of shouting. It stays a real button at
   the full 44px target -- an empty item must remain reachable and operable, and it must
   not be the one row a keyboard user cannot get to -- but the caret and the number drop
   to the quiet ink, so a scan down the agenda lands on the items that drew speakers. */
.spr-roster-toggle[data-signups="0"] .spr-chev {
  border-color: var(--spr-line-2);
  color: var(--spr-soft);
}

.spr-roster-toggle[data-signups="0"] .spr-item-no {
  color: var(--spr-soft);
}

.spr-roster-toggle[data-signups="0"]:hover .spr-chev,
.spr-roster-toggle[data-signups="0"]:focus-visible .spr-chev {
  border-color: var(--pi-blue-d);
  color: var(--pi-blue-ink);
}

/* The totals row's ITEM cell. Same track, same optical weight as a number. */
.spr-item-sum {
  display: inline-block;
  padding-left: 30px;
  font: 700 15px/1 var(--spr-display);
  color: var(--pi-blue-ink);
}

.spr-roster {
  margin-top: 10px;
  border: 1px solid var(--spr-line);
  border-radius: 8px;
  background: var(--spr-bg-2);
  padding: 4px 12px 10px;
  overflow-x: auto;
}

.spr-group-band {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding: 5px 2px;
  font: 700 9.5px/1 var(--spr-display);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--spr-soft);
  border-bottom: 1px solid var(--spr-line);
}

.spr-group-band[data-channel="phone"] {
  color: var(--spr-phone);
}

.spr-group-count {
  font-variant-numeric: tabular-nums;
}

.spr-roster-empty,
.spr-empty {
  font-size: 0.85rem;
  color: var(--spr-soft);
  padding: 12px 2px;
  margin: 0;
}

.spr-omitted {
  font-size: 0.82rem;
  color: var(--spr-warn);
  border-top: 1px solid var(--spr-line);
  padding-top: 8px;
  margin: 8px 0 0;
}

/* -- names, chips, states -- */

.spr-name {
  background: none;
  border: 0;
  padding: 2px 0;
  min-height: 44px;
  font: 600 0.88rem/1.35 var(--spr-body);
  color: var(--pi-blue-ink);
  text-align: left;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.spr-org {
  font-size: 0.76rem;
  color: var(--spr-soft);
}

.spr-chip {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 4px;
  font: 600 0.68rem/1 var(--spr-body);
  padding: 3px 7px;
  border-radius: 5px;
  border: 1px solid currentColor;
  white-space: nowrap;
}

.spr-chip-cap {
  color: var(--spr-warn);
}

/* Channel as a chip on the row, matching the band above the group. Colour is a
   reinforcement here, never the only signal -- the chip carries its own label. */
.spr-chip-channel {
  color: var(--pi-blue-ink);
  border-color: var(--pi-blue-edge);
  background: var(--pi-blue-light);
}

.spr-chip-channel.spr-channel-phone {
  color: var(--spr-phone);
  border-color: currentColor;
  background: transparent;
}

.spr-chip-open {
  color: var(--spr-phone);
}

.spr-chip-party {
  color: var(--pi-blue-ink);
}

.spr-chip-segment {
  color: var(--pi-blue-ink);
  background: var(--pi-blue-light);
  border-color: var(--pi-blue-edge);
}

.spr-outcome-spoke {
  color: var(--spr-ok);
  font-weight: 600;
}

.spr-outcome-no-show {
  color: var(--spr-bad);
  font-weight: 600;
}

.spr-pos-for {
  color: var(--spr-ok);
}

.spr-pos-against {
  color: var(--spr-bad);
}

.spr-pos-neutral,
.spr-pos-unset {
  color: var(--spr-neutral);
}

.spr-len {
  font-variant-numeric: tabular-nums;
}

/* The em-dash a cell shows when it has nothing to report -- no sign-ups, nobody heard,
   no timer run. Soft rather than black because it is the absence of a value, but still
   legible rather than washed out into looking like a rendering failure. Where the dash
   stands for a specific fact, a .spr-sr sibling names that fact for a screen reader. */
.spr-untimed {
  color: var(--spr-soft);
  font-style: italic;
}

/* -- notices -- */

.spr-notice {
  margin: 14px 0 0;
  padding: 11px 14px;
  border: 1px solid var(--spr-line);
  border-left: 3px solid var(--pi-orange);
  border-radius: 0 8px 8px 0;
  background: var(--spr-bg-2);
  font-size: 0.85rem;
  color: var(--spr-mid);
}

.spr-notice-quiet {
  border-left-color: var(--spr-hairline);
}

/* -- directory -- */

.spr-search {
  display: flex;
  align-items: center;
  gap: 9px;
  border: 1px solid var(--spr-line);
  border-radius: 10px;
  padding: 8px 12px;
  background: #fff;
  max-width: 420px;
}

.spr-search:focus-within {
  border-color: var(--pi-blue);
  box-shadow: 0 0 0 3px rgba(3, 149, 255, 0.14);
}

.spr-input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  border: 0;
  outline: 0;
  background: transparent;
  font: 400 13px/1.4 var(--spr-body);
  color: var(--spr-ink);
}

.spr-count {
  font-size: 0.8rem;
  color: var(--spr-soft);
  margin: 9px 0 4px;
}

/* -- legend. Colour is never the only channel here: every swatch has its label beside
      it, so the key works in greyscale and for a colour-blind reader. -- */

.spr-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  list-style: none;
  margin: 4px 0 12px;
  padding: 0;
  font-size: 0.75rem;
  color: var(--spr-soft);
}

.spr-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.spr-legend-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: none;
}

.spr-swatch-in-person { background: var(--pi-blue); }
.spr-swatch-phone { background: var(--spr-phone); }
.spr-swatch-for { background: var(--spr-ok); }
.spr-swatch-against { background: var(--spr-bad); }
.spr-swatch-neutral { background: var(--spr-neutral); }

/* -- totals row. Heavier rule above it so it reads as the bottom line rather than as
      one more item. -- */

/* An agenda item's customer-defined status.
 *
 * The customer colour is a DOT and a left edge only -- never the text colour and never a
 * text background. An arbitrary customer hex cannot be guaranteed to clear 4.5:1 (yellow
 * on white is 1.07:1), but a non-text indicator only has to clear 3:1, and the label
 * beside it carries the meaning. --spr-status falls back to the neutral token whenever
 * the value was rejected, so a bad colour degrades to a plain badge. */
.spr-status {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: 600 0.66rem/1 var(--spr-display);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--spr-mid);
  background: var(--spr-line-2);
  border-left: 3px solid var(--spr-status, var(--spr-neutral));
  border-radius: 3px;
  padding: 3px 7px;
  white-space: nowrap;
}

.spr-status::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--spr-status, var(--spr-neutral));
  flex: none;
}

/* The section an item belongs to, shown on the row only once filtering or sorting has
   broken the section headings apart. */
.spr-item-section {
  font-size: 0.72rem;
  color: var(--spr-soft);
  border: 1px solid var(--spr-line);
  border-radius: 4px;
  padding: 1px 6px;
}

/* The figure carries the ink, the noun stays quiet -- the point of the line is that the
   two NUMBERS differ, so they are what the eye should land on. */
.spr-countpair b {
  color: var(--spr-ink);
  font-weight: 700;
}

.spr-countsep {
  padding: 0 2px;
}

/* The directory template flexes with availability: organization, language and position
   are optional columns, so a fixed track count would leave gaps on a meeting that has
   none of them. auto-fit is right HERE -- unlike the agenda, every directory cell is
   always populated. */
.spr-grid-directory .spr-row {
  grid-template-columns: minmax(200px, 2.4fr) minmax(96px, 1fr) repeat(3, minmax(80px, 0.7fr));
}

.spr-pager {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
}

.spr-page-of {
  font-size: 0.8rem;
  color: var(--spr-soft);
}

/* -- buttons -- */

.spr-btn {
  font: 600 12px/1 var(--spr-body);
  padding: 9px 14px;
  /* WCAG 2.5.5: a pointer target is at least 44x44 CSS px. 34px is a comfortable mouse
     target and a poor touch one, and this report is read on phones -- AC-39 gives the
     profile a bottom-sheet presentation at 375px for exactly that reason. Both the real
     view bar and viewBarSkeleton() use this class, so the loading and loaded states grow
     together and the header still does not shift. */
  min-height: 44px;
  border-radius: 9px;
  border: 1px solid var(--spr-line);
  background: #fff;
  color: var(--spr-ink);
  cursor: pointer;
  white-space: nowrap;
}

.spr-btn:hover:not([disabled]) {
  border-color: var(--pi-blue-d);
  color: var(--pi-blue-ink);
}

.spr-btn[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

.spr-btn-primary {
  background: var(--pi-blue-d);
  border-color: var(--pi-blue-d);
  color: #fff;
  font-family: var(--spr-display);
}

.spr-btn-primary:hover:not([disabled]) {
  background: var(--pi-blue-ink);
  border-color: var(--pi-blue-ink);
  color: #fff;
}

.spr-btn-sm {
  padding: 6px 11px;
  min-height: 44px;
}

/* -- breakdown panels -- */

.spr-panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap: 14px;
}

.spr-panel {
  border: 1px solid var(--spr-line);
  border-radius: 8px;
  padding: 14px 15px;
}

.spr-panel-title {
  font: 700 12px/1.3 var(--spr-display);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--spr-soft);
  margin-bottom: 8px;
}

.spr-panel-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.spr-panel-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 10px;
  align-items: baseline;
  font-size: 0.84rem;
}

.spr-panel-v {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.spr-panel-bar {
  grid-column: 1 / -1;
  height: 7px;
  border-radius: 3px;
  background: var(--spr-line);
  position: relative;
  overflow: hidden;
}

.spr-panel-bar::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--spr-fill, 0%);
  background: var(--pi-blue);
}

/* -- profile card -- */

.spr-profile {
  position: fixed;
  z-index: 60;
  background: #fff;
  border: 1px solid var(--spr-line);
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(16, 24, 40, 0.22);
  padding: 18px 20px 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Under 640px an anchored card cannot fit, so it becomes a bottom sheet rather than
   a card that hangs off the edge of the viewport. */
.spr-profile.is-sheet {
  position: fixed;
  inset: auto 0 0 0;
  width: 100% !important;
  max-height: 82vh;
  overflow-y: auto;
  border-radius: 14px 14px 0 0;
}

.spr-profile-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.spr-profile-name {
  font: 700 18px/1.25 var(--spr-display);
}

.spr-profile-org,
.spr-profile-channel {
  margin: 3px 0 0;
  font-size: 0.82rem;
  color: var(--spr-soft);
}

.spr-profile-close {
  border: 1px solid var(--spr-line);
  background: #fff;
  border-radius: 8px;
  min-width: 32px;
  min-height: 44px;
  font-size: 18px;
  line-height: 1;
  color: var(--spr-mid);
  cursor: pointer;
}

.spr-profile-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

.spr-profile-stat {
  border: 1px solid var(--spr-line);
  border-radius: 6px;
  padding: 9px 11px;
}

.spr-profile-stat-v {
  display: block;
  font: 700 18px/1.1 var(--spr-display);
  font-variant-numeric: tabular-nums;
}

.spr-profile-stat-k {
  display: block;
  font-size: 10.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--spr-soft);
  margin-top: 4px;
}

/* One LABEL per kind with each value on its own line beneath it. Two email addresses
   are two lines under one Email heading, not two headings both reading Email. */
.spr-profile-fields {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.spr-field {
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: 4px 12px;
  align-items: baseline;
}

.spr-field-k {
  font: 600 10.5px/1.4 var(--spr-display);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--spr-soft);
}

.spr-field-vs {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.spr-field-v {
  font-size: 0.85rem;
  word-break: break-word;
}

.spr-profile-withheld {
  margin: 0;
  font-size: 0.82rem;
  color: var(--spr-soft);
}

.spr-profile-segments {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.spr-profile-spokeon .spr-row {
  grid-template-columns: minmax(160px, 2fr) repeat(3, minmax(78px, 1fr));
}

/* -- export dialog -- */

.spr-export-scrim {
  position: fixed;
  inset: 0;
  background: rgba(16, 24, 40, 0.36);
  z-index: 70;
}

.spr-export-dialog {
  position: fixed;
  z-index: 71;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(520px, calc(100vw - 32px));
  max-height: 86vh;
  overflow-y: auto;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 16px 48px rgba(16, 24, 40, 0.28);
  padding: 22px 24px 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.spr-export-title {
  font: 700 19px/1.25 var(--spr-display);
}

.spr-hint {
  margin: 0;
  font-size: 0.82rem;
  color: var(--spr-soft);
}

.spr-field-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.spr-label {
  font: 600 11px/1.3 var(--spr-display);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--spr-soft);
}

.spr-field-row .spr-input {
  border: 1px solid var(--spr-line);
  border-radius: 9px;
  padding: 9px 12px;
}

.spr-field-row .spr-input:focus {
  border-color: var(--pi-blue);
  box-shadow: 0 0 0 3px rgba(3, 149, 255, 0.14);
}

.spr-export-actions {
  display: flex;
  justify-content: flex-end;
  gap: 9px;
  margin-top: 4px;
}

.spr-error-text {
  margin: 0;
  font-size: 0.83rem;
  color: var(--spr-bad);
}

.spr-ok-text {
  margin: 0;
  font-size: 0.83rem;
  color: var(--spr-ok);
}

/* -- error and loading -- */

.spr-error {
  border-left: 3px solid var(--spr-bad);
}

.spr-error-title {
  font: 700 17px/1.25 var(--spr-display);
  margin-bottom: 6px;
}

.spr-error-detail {
  margin: 0 0 12px;
  font-size: 0.88rem;
  color: var(--spr-mid);
}

/* The skeleton stands in for the real document, so the header above it is already
   placed and content arriving does not shove it down the page. */
.spr-skeleton {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: 46px;
}

/* .spr-skel-stats and .spr-skel-tile were removed: insights.ts paints the skeleton as
   three .spr-skel-row bars only, so the tile-shaped placeholders never rendered. */

.spr-skel-row {
  height: 34px;
  border-radius: 6px;
  background: var(--spr-line-2);
}

@media (prefers-reduced-motion: no-preference) {
  .spr-skel-row {
    animation: spr-pulse 1.5s ease-in-out infinite;
  }
}

@keyframes spr-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/* Visually hidden but announced.
 *
 * .spr-live is a live region; .spr-sr is static text that exists only for a screen
 * reader -- the meaning behind a cell whose visible content is an em-dash, or the name
 * of a column header that is blank on purpose. Same mechanics, different jobs, one rule
 * so they cannot drift apart. */
.spr-live,
.spr-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* -- meeting dashboards -- */

.spr-dash-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.spr-dash-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.spr-dash-link {
  color: var(--pi-blue-ink);
  font-weight: 600;
  text-decoration: underline;
  /* A link in a list is a target, so it gets the same 44px floor as every button. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.spr-dash-desc {
  font-size: 0.8rem;
  color: var(--spr-soft);
}

/* -- print. Every roster open, no interactive chrome, no page break inside an item. -- */

@media print {
  .spr {
    background: #fff;
  }

  .spr-viewbar,
  .spr-pager,
  .spr-search,
  .spr-btn,
  .spr-export-dialog,
  .spr-export-scrim,
  .spr-profile {
    display: none !important;
  }

  .spr-roster[hidden] {
    display: block !important;
  }

  /* On paper every roster is already open, so the caret is an affordance for a gesture
     nobody can make -- but the item NUMBER now lives inside the same control, and the
     agenda is unreadable without it. Hide the caret, keep the number, and reclaim the
     caret's width so the number sits at the margin. */
  .spr-roster-toggle .spr-chev {
    display: none !important;
  }

  .spr-roster-toggle {
    min-height: 0;
  }

  .spr-item-sum {
    padding-left: 0;
  }

  .spr-item,
  .spr-block {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .spr-block {
    border: 0;
    padding-left: 0;
    padding-right: 0;
  }
}

/* == the time profile insight: stat tiles, then the same meeting along its own clock == */

.spr-timeprofile {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* -- the block-size control.
 *
 * A segmented radiogroup. Every option is 44px tall for WCAG 2.5.5, and the CHOSEN one is
 * marked with a fill AND a weight change, never colour alone -- the group is legible in
 * greyscale and to a reader who cannot distinguish the blue.
 * -- */

.spr-iv-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin: 0 0 14px;
}

.spr-iv-label {
  font: 600 10px/1.3 var(--spr-display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--spr-soft);
}

.spr-iv-seg {
  display: inline-flex;
  border: 1px solid var(--spr-line);
  border-radius: 7px;
  overflow: hidden;
  background: var(--spr-bg);
}

.spr-iv-opt {
  min-height: 44px;
  padding: 0 14px;
  border: 0;
  border-right: 1px solid var(--spr-line);
  background: transparent;
  font: 600 0.8rem/1 var(--spr-body);
  color: var(--spr-mid);
  cursor: pointer;
}

.spr-iv-opt:last-child {
  border-right: 0;
}

.spr-iv-opt[aria-checked="true"] {
  background: var(--pi-blue-light);
  color: var(--pi-blue-ink);
  font-weight: 700;
  box-shadow: inset 0 -2px 0 var(--pi-blue);
}

.spr-iv-opt:hover {
  color: var(--pi-blue-ink);
}

/* -- the two time grids. FIXED templates, for the reason .spr-grid-agenda states: auto-fit
 * drops a track whose cells are empty, so a meeting with no phone callers would render
 * different columns from the block above it. -- */

.spr-grid-timeline .spr-row {
  grid-template-columns:
    minmax(150px, 1.9fr)   /* time block, and the activation trigger */
    minmax(74px, 1fr)      /* in person                              */
    minmax(74px, 1fr)      /* by phone                               */
    minmax(64px, 0.9fr)    /* turns                                  */
    minmax(74px, 1fr)      /* speakers                               */
    minmax(84px, 1fr)      /* active items                           */
    minmax(120px, 1.4fr);  /* distribution                           */

  gap: 10px;
}

/* One rule per per-language series count, keyed on a data attribute the renderer sets.
 *
 * NOT repeat(var(--spr-listen-series), ...). A meeting where every caller speaks one
 * language has ZERO language columns, repeat(0, ...) is invalid CSS, and an invalid
 * track list takes the WHOLE grid-template-columns declaration with it -- the table then
 * renders as one stacked column, which is exactly how it drew on QA meeting 66425. The
 * counts are bounded (0 to 5, see MAX_SERIES), so they are enumerated. */
.spr-grid-listen .spr-row {
  grid-template-columns: minmax(96px, 1.2fr) repeat(3, minmax(96px, 1fr));
  gap: 10px;
}

.spr-grid-listen[data-series="1"] .spr-row {
  grid-template-columns: minmax(96px, 1.2fr) repeat(4, minmax(96px, 1fr));
}

.spr-grid-listen[data-series="2"] .spr-row {
  grid-template-columns: minmax(96px, 1.2fr) repeat(5, minmax(96px, 1fr));
}

.spr-grid-listen[data-series="3"] .spr-row {
  grid-template-columns: minmax(96px, 1.2fr) repeat(6, minmax(96px, 1fr));
}

.spr-grid-listen[data-series="4"] .spr-row {
  grid-template-columns: minmax(96px, 1.2fr) repeat(7, minmax(96px, 1fr));
}

.spr-grid-listen[data-series="5"] .spr-row {
  grid-template-columns: minmax(96px, 1.2fr) repeat(8, minmax(96px, 1fr));
}

/* A block nothing happened in is still a row -- the gap in the meeting is the finding --
 * but it is not competing for attention with the blocks that carry numbers. */
.spr-row-quiet .spr-cell {
  color: var(--spr-soft);
}

.spr-bar-in-person { background: var(--pi-blue); }
.spr-bar-phone { background: var(--spr-phone); }

.spr-timeline-body {
  display: flex;
  flex-direction: column;
}

/* -- the activation popover.
 *
 * The shell is the offset parent the tooltip is positioned against, so it establishes the
 * containing block and nothing else. -- */

.spr-timeline-shell {
  position: relative;
}

/* 44px, because this carries tabindex="0" and AC-41 measures every focusable element in
 * every insight -- and because a tooltip a thumb cannot land on is a tooltip a phone reader
 * does not have. The dotted underline is what says it is more than a label. */
/* The underline is the ONLY signal that the block label does anything, and cursor: help is
 * not a signal -- it does not exist on touch and it is not a colour a scanner can measure.
 * --spr-hairline is 2.90:1 on white, under the 3:1 non-text floor, so this uses --spr-soft. */
.spr-ai-trigger {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-bottom: 1px dotted var(--spr-soft);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: help;
}

.spr-ai-trigger:focus-visible {
  outline: 2px solid var(--pi-blue-ink);
  outline-offset: 2px;
}

.spr-ai-pop {
  position: absolute;
  z-index: 20;
  max-width: min(360px, 92vw);
  background: var(--spr-bg);
  border: 1px solid var(--spr-line);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgb(31 36 48 / 16%);
  padding: 10px 12px;
  font-size: 0.78rem;
  color: var(--spr-mid);
}

.spr-ai-head {
  font: 700 0.8rem/1.2 var(--spr-display);
  color: var(--spr-ink);
  margin-bottom: 6px;
}

.spr-ai-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.spr-ai-sec {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
}

.spr-ai-sec-t {
  font-size: 0.74rem;
  color: var(--spr-soft);
}

.spr-ai-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.spr-ai-list li {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.spr-ai-n {
  font: 700 0.72rem/1.3 var(--spr-display);
  color: var(--spr-ink);
  white-space: nowrap;
}

/* 28ch and an ellipsis, with the whole string on title=. Truncating in the stylesheet
 * rather than in JavaScript is what keeps the DOM carrying the real value: a test, a find
 * on the page and a copy all see the agenda item's actual title. */
.spr-ai-title {
  max-width: 28ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.spr-ai-hint {
  display: block;
  font-size: 0.72rem;
  color: var(--spr-soft);
}

.spr-ai-group {
  color: var(--spr-ink);
}

.spr-ai-empty {
  margin: 0;
  color: var(--spr-soft);
}

.spr-chip-ok { color: var(--spr-ok); }
.spr-chip-quiet { color: var(--spr-soft); }

/* -- the listen-in chart. Colour is never the only signal: every series is repeated as a
 * column in the table below and named in the legend beside it. -- */

.spr-chart {
  margin: 4px 0 10px;
  overflow-x: auto;
}

.spr-chart svg {
  width: 100%;
  height: auto;
  max-width: 720px;
}

.spr-mc-grid {
  stroke: var(--spr-line);
  stroke-width: 1;
}

.spr-mc-axis {
  stroke: var(--spr-hairline);
  stroke-width: 1;
}

.spr-mc-label {
  fill: var(--spr-soft);
  font: 500 10px/1 var(--spr-body);
}

.spr-mc-series-1,
.spr-mc-series-2,
.spr-mc-series-3,
.spr-mc-series-4,
.spr-mc-series-5 {
  fill: none;
  stroke-width: 2;
}

.spr-mc-series-1 { stroke: var(--spr-phone); }
.spr-mc-series-2 { stroke: var(--pi-orange-ink); }
.spr-mc-series-3 { stroke: var(--pi-blue-ink); }
.spr-mc-series-4 { stroke: var(--spr-ok); }

/* The fifth is everything that did not fit in four, so it is dashed as well as grey --
   a fold-together series should not read as a fifth language. */
.spr-mc-series-5 {
  stroke: var(--spr-neutral);
  stroke-dasharray: 4 3;
}

.spr-mc-dot {
  stroke: none;
}

.spr-mc-series-1.spr-mc-dot { fill: var(--spr-phone); }
.spr-mc-series-2.spr-mc-dot { fill: var(--pi-orange-ink); }
.spr-mc-series-3.spr-mc-dot { fill: var(--pi-blue-ink); }
.spr-mc-series-4.spr-mc-dot { fill: var(--spr-ok); }
.spr-mc-series-5.spr-mc-dot { fill: var(--spr-neutral); }

.spr-swatch-series-1 { background: var(--spr-phone); }
.spr-swatch-series-2 { background: var(--pi-orange-ink); }
.spr-swatch-series-3 { background: var(--pi-blue-ink); }
.spr-swatch-series-4 { background: var(--spr-ok); }
.spr-swatch-series-5 { background: var(--spr-neutral); }

/* -- fine print. The report explains its own counting rules; these are the sentences that
 * do it, and they are quieter than the numbers without dropping below AA. -- */

.spr-fine {
  margin: 6px 0 0;
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--spr-soft);
}

/* On screen this is redundant with the popover; in print the popover does not exist. */
.spr-print-only {
  display: none;
}

@media (width <= 720px) {
  .spr-grid-timeline .spr-row,
  .spr-grid-listen .spr-row {
    /* The grids scroll sideways rather than reflow. A time table whose columns stack loses
     * the one thing it is for -- comparing one block against the next down a column. */
    min-width: 640px;
  }
}

/* -- print. The interval control is a control, so it goes; its APPLIED value does not, so
 * the heading (which names it) stays and the active-items list becomes text. -- */

@media print {
  .spr-iv-bar,
  .spr-ai-pop,
  .spr-chart {
    display: none !important;
  }

  .spr-print-only {
    display: block;
    font-size: 0.68rem;
    color: hsl(0deg 0% 0%);
  }

  /* A table clipped by an overflow container prints clipped. Every one of them opens up. */
  .spr-grid {
    overflow: visible !important;
  }

  .spr-grid-timeline .spr-row,
  .spr-grid-listen .spr-row {
    min-width: 0;
  }

  .spr-ai-trigger {
    border-bottom: 0;
  }

  /* One insight per page. A record folded into a Board packet is read section by section,
   * and a section split across a fold is one nobody reads the second half of. */
  .spr-timeprofile > .spr-block {
    break-before: page;
    page-break-before: always;
  }

  .spr-timeprofile > .spr-block:first-child {
    break-before: auto;
    page-break-before: auto;
  }
}

/* -- AB#9981. Speaking start / stop / length.
 *
 * Requested by Los Angeles County: "the individual speaker's start and stop talk times and
 * the exact amount of time spoken". Two columns on a roster row, and a table of every turn
 * on the speaker profile. -- */

/* A FIXED template per column count, for the reason .spr-grid-agenda states above: the
 * shared .spr-row uses repeat(auto-fit, ...), and auto-fit collapses a track whose cell is
 * empty. Before the Started column existed the roster got away with it; a column where some
 * rows read "Not recorded" and others carry a stamp is exactly the shape that made one item
 * render with different columns from the item above it. The count is published on the panel
 * by roster.ts, so the template is chosen by the data rather than guessed by the layout. */

/* The room comes from the SPEAKER floor and the gap, not from the metric floors.
 *
 * A flat 84px metric floor, a 150px speaker floor and a 10px gap put Los Angeles County's
 * ten-column roster at 1030px inside a 676px panel -- a horizontal scrollbar under the
 * primary content of the report. The metric floor turns out to be honest and cannot move:
 * "10:05:05 AM" measures 84px, so anything narrower clips the Started stamp. The speaker
 * floor and the gap were the slack, and 110px still holds a wrapped name comfortably.
 *
 * Three narrower-floor variants were tried and rejected, each caught by measurement rather
 * than by eye:
 *
 *   - Per-column `min-width` on the cells. minmax()'s lower bound is a FIXED length, so a
 *     cell's own min-width never feeds back into track sizing: the cell simply overflows
 *     its track and lands on top of the next column. Measured 12 overlapping cells.
 *   - `minmax(min-content, 1fr)`. Every .spr-row is its OWN grid, so a content-derived
 *     minimum resolves per row and the columns stop lining up. Measured 27px of drift.
 *   - An 80px metric floor. Clips the Started stamp by 4px on the widest timestamps.
 *
 * This does NOT make ten columns fit -- ten honest columns need ~920px in a 676px panel --
 * so LA County keeps a horizontal scroll, just a much shorter one. Closing the rest needs
 * `subgrid` on .spr-roster so every row shares one set of content-sized tracks, or one
 * fewer column; both are structural and belong in their own change. */
.spr-roster[data-columns] .spr-row {
  grid-template-columns:
    minmax(110px, 2.2fr)
    repeat(var(--spr-roster-rest, 5), minmax(84px, 1fr));
  gap: 6px;
}

.spr-roster[data-columns="3"] .spr-row { --spr-roster-rest: 2; }
.spr-roster[data-columns="4"] .spr-row { --spr-roster-rest: 3; }
.spr-roster[data-columns="5"] .spr-row { --spr-roster-rest: 4; }
.spr-roster[data-columns="6"] .spr-row { --spr-roster-rest: 5; }
.spr-roster[data-columns="7"] .spr-row { --spr-roster-rest: 6; }
.spr-roster[data-columns="8"] .spr-row { --spr-roster-rest: 7; }
.spr-roster[data-columns="9"] .spr-row { --spr-roster-rest: 8; }
.spr-roster[data-columns="10"] .spr-row { --spr-roster-rest: 9; }

/* Wider than the 84px floor, because "10:00:00 AM" does not wrap gracefully and a wrapped
 * clock reads as two values. */
.spr-roster .spr-cell[data-column-key="started"] {
  min-width: 84px;
}

/* The stamp is the reason the floor above is 80px rather than 66px, so it must not wrap
   even when a row is otherwise short -- a clock split over two lines reads as two values. */
.spr-roster .spr-cell[data-column-key="started"] .spr-len {
  white-space: nowrap;
}

/* Hidden at every width where the header row is visible: the cell is already named by its
 * columnheader, and a second copy is redundant to a sighted reader and (were it not
 * aria-hidden) announced twice to a screen reader one. Shown by the stacked breakpoint
 * below, where the header row is gone. */
.spr-cell-label {
  display: none;
  font: 700 9.5px/1 var(--spr-display);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--spr-soft);
}

/* Below the roster's natural width, one record per stacked block with each cell labelled.
 * The alternative is an inner horizontal scroller holding the primary content, which is a
 * 1.4.10 finding in spirit even where the data-table exception applies -- the same call
 * .spr-grid-agenda already makes at the same breakpoint. */
@media (width <= 720px) {
  .spr-roster .spr-row-head {
    display: none;
  }

  .spr-roster[data-columns] .spr-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
    padding: 10px 4px;
  }

  .spr-roster .spr-cell-label {
    display: block;
  }
}

/* -- the profile's speaking sessions -- */

.spr-profile-sessions {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--spr-line);

  /* flex: none is LOAD-BEARING, and the reason is two rules interacting.
   *
   * .spr-profile is a flex column that profile.ts caps with a max-height when the card is
   * taller than the space above its trigger. A flex item's automatic minimum size normally
   * stops it shrinking below its content -- EXCEPT when the item's own overflow is anything
   * but visible, which zeroes that minimum. The overflow-x: auto below (which this section
   * needs, because eight columns do not fit a 620px card) therefore made this the one
   * section the card was free to crush, and on a capped card it collapsed to 13px: the
   * heading, the time-zone note and every speaking turn squeezed out of existence while the
   * DOM still said they were there. Caught by an element screenshot; invisible to a test
   * that only queries for the node.
   *
   * Pinning flex-shrink to 0 puts the section back at its content height and lets the CARD
   * scroll instead, which is the behaviour every other section already gets for free. */
  flex: none;
}

.spr-profile-sessions-h {
  font: 600 12px/1.3 var(--spr-display);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--spr-soft);
  margin: 0 0 4px;
}

.spr-profile-sessions-note {
  font-size: 0.74rem;
  color: var(--spr-soft);
  margin: 0 0 8px;
}

/* A real table, scrolling inside its OWN container. The card is already position: fixed and
 * width-capped, so without this the eight columns push the card past the viewport edge. */
.spr-profile-sessions {
  overflow-x: auto;
}

.spr-sessions-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8rem;
  color: var(--spr-mid);
}

.spr-sessions-table th,
.spr-sessions-table td {
  padding: 6px 8px 6px 0;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--spr-line-2);
  white-space: nowrap;
}

.spr-sessions-table thead th {
  font: 600 9.5px/1.3 var(--spr-display);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--spr-soft);
  border-bottom: 1px solid var(--spr-line);
}

/* The agenda-item cell is the one that carries prose, so it is the one allowed to wrap. */
.spr-sessions-table td:last-child {
  white-space: normal;
  min-width: 120px;
}

.spr-sessions-total th,
.spr-sessions-total td {
  border-bottom: 0;
  border-top: 1px solid var(--spr-line);
  padding-top: 8px;
  font-weight: 600;
  color: var(--spr-ink);
}

/* The exclusion note sits next to the sum rather than under the card, so a reader cannot
   see the total without seeing what is missing from it. */
.spr-sessions-total .spr-bar-note {
  font-weight: 400;
  white-space: normal;
}
