/**
 * Fairness View Panel
 * ============================================
 * Styles for the redesigned Fairness analysis mode:
 *   - Hourly per-member burden strip
 *   - Historical trend bars
 *   - 9-step single-hue violet ramp (light → deep; "more cost = deeper")
 *   - Availability-density timeline-cell shading
 *
 * Activated when body[data-color-mode="fairness"].
 * Follows DARK_MODE_POLICY (see docs/DARK_MODE_POLICY.md).
 * ============================================
 */

/* ── 9-step single-hue violet ramp ──────────────────────── */
:root {
  /* Fairness ramp — single-hue sequential violet (light → deep).
     2026-06-01 repaint v3 (view-mode shading audit): replaced the
     diverging green → amber → red ramp. The diverging ramp had two
     deeper problems the v2 "full saturation" repaint actually amplified:
       1) ALARM REFLEX. A standard 9–17 schedule painted saturated amber
          shoulders + busy-red caps on every row's after-hours, so a
          routine "slightly costly hour" fired the strongest signal the
          visual system has. Fairness mode was the loudest surface in the
          app — a jarring saturation jump from the gentle Local Time +
          Overlap palettes the rest of the product is tuned to.
       2) RED ≡ BUSY-RED COLLISION. l7/l8/l9 were the canonical busy-red
          family, so a high-cost fairness cell was visually identical to a
          calendar "busy" cell while meaning something completely
          different. Per three_layer_coherence.md the per-hour VERDICT
          lives in the duration pill, not the cell — cells should describe,
          not judge. A green↔red ramp on cells overreached into verdict
          territory.

     A single-hue sequential ramp says "more cost = deeper" on ONE
     perceptual axis (the same lesson Local Time and Overlap teach), never
     fires the alarm reflex, and stops borrowing the traffic-light hues —
     so fairness cells no longer collide with busy/flex/sleep paint. Violet
     is the mode's own accent hue (--mode-accent #7e22ce). Biased toward
     true-violet/orchid rather than the bluer canonical sleep-indigo so the
     two purples stay separable; fairness cells never paint at sleep hours
     anyway (sleep cost is baked into the score), so the collision risk is
     low and the bias is belt-and-braces.

     TRAFFIC-LIGHT RAMP (restored 2026-06-02). Reverted the 2026-06-01
     single-hue violet repaint (49c0954) back to the green→amber→red diverging
     ramp the user prefers ("the colours we missed"): low cost = green, mid =
     amber, high = red. The one carry-over from the violet repaint we KEEP is
     the calmer intensity — the cell painter still mixes at 70% (not the old
     95%), and l9 is a deep-but-not-violent red (#b91c1c, not the near-black
     #7f1d1d the audit flagged as reading "critical error"). So: traffic-light
     semantics back, alarm-loudness gone. Anchored on canonical
     STATUS_PRESENTATION hues so fairness, status, and the team-bar agree. */
  --fv-l1: #14532d;  /* very deep green — most fair */
  --fv-l2: #15803d;  /* deep green */
  --fv-l3: #16a34a;  /* canonical working green */
  --fv-l4: #22c55e;  /* canonical available green */
  --fv-l5: #fbbf24;  /* canonical flex amber — the ONE amber, neutral midpoint */
  --fv-l6: #f97316;  /* orange — bridges the single amber toward red (2026-06-06:
                        was #f59e0b, a second amber that read as two ambers next
                        to l5; now a distinctly hotter orange so the ramp has one
                        amber then climbs orange→red) */
  --fv-l7: #ef4444;  /* light red — bridges orange to canonical busy-red */
  --fv-l8: #dc2626;  /* canonical busy red */
  --fv-l9: #b91c1c;  /* deep red — most unfair (gentler than the old #7f1d1d) */

  --fv-cell-border: rgba(0, 0, 0, 0.06);
  /* Quarter-day anchor lines (6a / 12p / 6p) drawn THROUGH every member row so
     a cell stays locatable without tracing back up to the axis.
     ⚠️LIGHT, not dark, in BOTH themes. A dark hairline is the obvious choice on
     a light panel, but these lines are drawn over the RAMP, not over the panel
     (the ramp's own colours run to deep green (#15803d) and deep red
     (#b91c1c). Rendered at rgba(0,0,0,.20) they vanished completely into every
     saturated cell, which is precisely where a reader most needs the anchor.
     A translucent WHITE line separates from every hue on the ramp, in both
     themes, because the ramp is mid-to-dark throughout. */
  --fv-anchor-line: rgba(255, 255, 255, 0.55);
  --fv-anchor-line-strong: rgba(255, 255, 255, 0.85);
  --fv-panel-bg: var(--surface, #ffffff);
  --fv-panel-border: var(--border, #e5e7eb);
  --fv-text: var(--text-primary, #111827);
  --fv-text-secondary: var(--text-secondary, #6b7280);
  --fv-header-bg: var(--surface-secondary, #f9fafb);
}

body.dark-mode,
body.dark-mode {
  /* Dark-mode fairness ramp — TRAFFIC-LIGHT (restored 2026-06-02), the
     dark-surface pair of the light-mode revert. Green→amber→red, tuned a
     touch brighter so each tier stays legible against the dark card. l9 is a
     deep red (#b91c1c), not near-black, so "most unfair" reads as serious
     without vanishing into the surface. */
  --fv-l1: #15803d;  /* deep green — most fair */
  --fv-l2: #16a34a;  /* canonical working green */
  --fv-l3: #22c55e;  /* canonical available green */
  --fv-l4: #4ade80;  /* light green — keeps the slight-fair tier visible on dark bg */
  --fv-l5: #fbbf24;  /* the ONE amber — neutral midpoint (bright reads well on dark) */
  --fv-l6: #fb8c2a;  /* orange — bridges amber→red (2026-06-06: was a second amber
                        #fbbf24; now a hotter orange, dark-tuned, so the ramp shows
                        one amber then climbs orange→red) */
  --fv-l7: #ef4444;  /* light red — bridges orange to canonical red */
  --fv-l8: #dc2626;  /* canonical busy red */
  --fv-l9: #b91c1c;  /* deep red — most unfair */

  --fv-cell-border: rgba(255, 255, 255, 0.08);
  /* Slightly softer than light mode: the dark panel already lowers the ramp's
     apparent brightness, so a full-strength white line would over-assert.
     Still white for the same reason: the line sits on the ramp, not the panel. */
  --fv-anchor-line: rgba(255, 255, 255, 0.42);
  --fv-anchor-line-strong: rgba(255, 255, 255, 0.72);
  --fv-panel-bg: var(--surface, #1f2937);
  --fv-panel-border: var(--border, #374151);
  --fv-text: var(--text-primary, #f3f4f6);
  --fv-text-secondary: var(--text-secondary, #9ca3af);
  --fv-header-bg: var(--surface-secondary, #111827);
}

/* ── Panel container ───────────────────────────────────── */
.fv-panel {
  position: relative;
  display: none;
  flex-direction: column;
  gap: 16px;
  padding: 16px;
  /* Align horizontal edges with the .ts-panel siblings and the timezone rows
     above. Without margin the panel ran flush to the .app container edges and
     read as a wider row than the calendar.
     ⚠️2026-08-08: both panels moved from a 16px margin to the board's own
     --space-3 (12px), because .timezone-section uses that and the summary
     strips must sit on the same column track as the member rows' timelines.
     Keep these two in step — they are siblings and a split reads as a bug. */
  margin: 8px var(--space-3, 12px) 24px;
  background: var(--fv-panel-bg);
  border: 1px solid var(--fv-panel-border);
  border-radius: 12px;
  color: var(--fv-text);
  font-size: 13px;
  min-height: 0;
  overflow: auto;
}

body[data-color-mode="fairness"] .fv-panel {
  display: flex;
}

.fv-panel[hidden] { display: none !important; }

/* ── Header ────────────────────────────────────────────── */
.fv-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--fv-panel-border);
}

/* Panel title, matched to the canonical .ts-title (team-summary-panel.css:
   13px / 600 / gap 8px / 0.01em). The two footers occupy the SAME slot under
   the board and were 2px apart for the identical role — the sort of drift that
   reads as sloppiness when a user switches modes. (Polish pass 2026-08-04.) */
.fv-header-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--fv-text);
}

.fv-header-title i { color: var(--fv-l2); }

/* Score number + label — shared by the Zone A verdict (.fv-verdict-score).
   The [data-level] tint lives on .fv-verdict (below). */
.fv-team-score-number {
  font-size: 24px;
  font-weight: 700;
  line-height: 1;
}

.fv-team-score-label {
  font-size: 11px;
  color: var(--fv-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ── Period toggle ───────────────────────────────────────
   2026-07-30: moved OUT of .fv-header into the panel body. The header is the
   collapse tap-handle and owns the chevron lane, so it carries the title
   alone. As a body row the toggle needs its own top margin and must not
   stretch, hence the flex-start wrapper behaviour below. */
.fv-period-toggle {
  display: inline-flex;
  margin-top: 12px;
  background: var(--fv-header-bg);
  border: 1px solid var(--fv-panel-border);
  border-radius: 8px;
  padding: 2px;
  gap: 2px;
}
/* Collapsed (deskpan mobile), the body is hidden — so are the tabs. */
.fv-panel[data-summary-collapsed="true"] .fv-period-toggle {
  display: none !important;
}

.fv-period-btn {
  padding: 5px 10px;
  border: none;
  background: transparent;
  color: var(--fv-text-secondary);
  font-size: 12px;
  font-weight: 500;
  border-radius: 6px;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}

.fv-period-btn:hover { color: var(--fv-text); }

.fv-period-btn.active {
  background: var(--fv-panel-bg);
  color: var(--fv-text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}

/* ── Zone A: team verdict ──────────────────────────────────
   One line that answers the team-level question: score + band word +
   trend at a glance + handoff to full history. */
.fv-verdict {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.fv-verdict-score {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-variant-numeric: tabular-nums;
}

/* Score number tint — same endpoint-pinned logic as the old .fv-team-score
   so the verdict number reads green/grey/red regardless of band. */
.fv-verdict[data-level="1"] .fv-team-score-number,
.fv-verdict[data-level="2"] .fv-team-score-number,
.fv-verdict[data-level="3"] .fv-team-score-number { color: var(--fv-l1); }
.fv-verdict[data-level="4"] .fv-team-score-number,
.fv-verdict[data-level="5"] .fv-team-score-number,
.fv-verdict[data-level="6"] .fv-team-score-number { color: var(--fv-text-secondary); }
.fv-verdict[data-level="7"] .fv-team-score-number,
.fv-verdict[data-level="8"] .fv-team-score-number,
.fv-verdict[data-level="9"] .fv-team-score-number { color: var(--fv-l9); }

.fv-verdict-band {
  font-size: 12px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--fv-header-bg);
  border: 1px solid var(--fv-cell-border);
  color: var(--fv-text);
}

.fv-verdict-trend {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.fv-spark.fv-verdict-spark {
  width: 64px;
  height: 18px;
  color: var(--fv-l2);
}

.fv-verdict .fv-spark-history-link { margin-top: 0; margin-left: auto; }

.fv-advisory {
  margin: 0 0 4px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--fv-text-secondary);
}

/* Neutral, actionable framing line under the verdict. Not a warning colour —
   this is guidance ("share the balance"), never blame. Slightly stronger than
   the advisory footnote so it reads as the actionable takeaway. */
.fv-framing {
  margin: 2px 0 6px;
  font-size: 12px;
  line-height: 1.4;
  /* `--fv-text-primary` was NEVER DEFINED anywhere (the file declares --fv-text,
     not --fv-text-primary), so this always fell through to the literal #e2e8f0 —
     a light slate that is near-invisible on the light-mode white panel. The
     takeaway line was effectively unreadable in light mode. Use the real token,
     which is already dark/light aware. (2026-08-04.) */
  color: var(--fv-text);
  font-weight: 500;
}
.fv-framing-building {
  color: var(--fv-text-secondary);
  font-weight: 400;
}

/* ── Sections ─────────────────────────────────────────── */
/* `.fv-section` is emitted by fairness-view.js (Zone A/B/C) but had NO rule at
   all, so the panel's three zones had no rhythm of their own and everything
   fell to the panel's flex `gap:16px` — which then fought the per-child margins
   below it, producing gaps of 16/18/20/22/26px depending on which pair you
   measured. Giving the section a real box lets the zones read as zones and lets
   the child margins go. (Polish pass 2026-08-04.) */
.fv-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* .fv-strip-section and .fv-insight-owed are the same Zone wrappers under
   different names in the renderer; keep them on one box so a rename can't
   silently drop the rhythm. */
.fv-strip-section,
.fv-insight-owed {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.fv-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 0;      /* the section's own gap owns this now */
  gap: 8px;
}

/* Micro-label rank, aligned to the canonical .ts-stat-label
   (team-summary-panel.css: 10px / 600 / uppercase / 0.05em). The panel was at
   12px / 0.06em, one of FIVE different letter-spacings the footer used for the
   same rank. One scale across both footers. (2026-08-04.) */
.fv-section-title {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fv-text-secondary);
}

.fv-section-meta {
  font-size: 11px;
  color: var(--fv-text-secondary);
}

.fv-empty-state {
  padding: 20px 12px;
  text-align: center;
  color: var(--fv-text-secondary);
  font-size: 12px;
  background: var(--fv-header-bg);
  border-radius: 8px;
  border: 1px dashed var(--fv-panel-border);
}

.fv-empty-state i {
  display: block;
  font-size: 22px;
  margin-bottom: 8px;
  opacity: 0.5;
}

/* ── Hourly strip ──────────────────────────────────────── */
.fv-strip {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* 27-col template: name gutter · 24 hours · impact tier · off-hours count.
   The axis mirrors it so the hour numbers stay over their cells.

   ⚠️THE TWO TEMPLATES BELOW MUST STAY IDENTICAL — the axis is a SEPARATE grid
   from the rows, so any divergence desyncs every hour label from its cell.
   Both read the same --fv-gutter-* tokens for exactly that reason; change the
   token, never one template.

   2026-08-04 (user: "fairness used an overly compressed cell"). The hour cells
   were being squeezed to ~30px on a 1080px phone by FOUR compounding taxes:
     • 256px of FIXED gutters (120 + 92 + 44) skimmed off the top, at every
       viewport, before the 24 1fr hour tracks got anything;
     • gap: 1px × 26 gaps = 26 more px;
     • a per-cell 1px border on all four sides (2px of a ~10px-wide box);
     • ZERO media queries in this entire file, so none of it ever relaxed.
   The gutters are now tokens that shrink at each breakpoint, the gap is 0
   (the cells carry their own hairline like .ts-cell does), and the cell
   border is right-edge only. Hour tracks get min-width:0 so they can never be
   forced wider than their share and push the row into overflow. */
.fv-strip {
  --fv-gutter-name: 120px;
  --fv-gutter-tier: 92px;
  --fv-gutter-hard: 44px;
}

.fv-strip-hour-axis {
  display: grid;
  grid-template-columns:
    var(--fv-gutter-name) repeat(24, minmax(0, 1fr))
    var(--fv-gutter-tier) var(--fv-gutter-hard);
  gap: 0;
  font-size: 9px;
  line-height: 1;
  color: var(--fv-text-secondary);
  padding-bottom: 4px;
  font-variant-numeric: tabular-nums;
  user-select: none;
}
/* The CLOSING axis under the last member row (2026-08-05). Its padding is the
   mirror of the opening one. A bottom-padded closing ruler would float away
   from the row it serves and leave a gap before the next section. */
.fv-strip-hour-axis[data-axis-pos="bottom"] {
  padding-bottom: 0;
  padding-top: 5px;
}

.fv-strip-hour-axis > span:first-child { grid-column: 1; }
.fv-strip-hour-axis > span {
  text-align: center;
  font-variant-numeric: tabular-nums;
  justify-self: center;
  white-space: nowrap;
  letter-spacing: -0.02em;   /* claw back a hair at 24-across */
}
/* Midnight and noon are the landmarks the rest of the axis is read against —
   weight + text colour only, no new hue, so the axis stays subordinate to the
   strip it labels. Mirrors .ts-axis[data-anchor]. */
.fv-strip-hour-axis > span[data-anchor="true"] {
  font-weight: 600;
  color: var(--fv-text);
}
/* Edge ticks: nudge first/last inward so a centred glyph can't bleed past the
   panel padding. */
.fv-strip-hour-axis > span[data-tick="0"]  { justify-self: start; }
.fv-strip-hour-axis > span[data-tick="23"] { justify-self: end; }

.fv-strip-row {
  display: grid;
  grid-template-columns:
    var(--fv-gutter-name) repeat(24, minmax(0, 1fr))
    var(--fv-gutter-tier) var(--fv-gutter-hard);
  gap: 0;
  align-items: center;
}

/* Name gutter now stacks the name line over the 30-day share bar. */
.fv-strip-member {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  padding-right: 8px;
  min-width: 0;
}

.fv-strip-member-name {
  font-size: 12px;
  font-weight: 500;
  color: var(--fv-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fv-strip-member-tz {
  font-size: 10px;
  color: var(--fv-text-secondary);
  font-weight: 400;
  margin-left: 4px;
}

/* ── Share gutter bar ("Bar under each name") ──────────────
   30-day share-vs-team-mean. Fill colour comes inline from
   FairnessPresentation.tiltPresentation().cssVar (green/amber half only).
   Absent state (no signal yet) renders a dashed baseline, no fill. */
.fv-strip-share {
  display: block;
  height: 4px;
  border-radius: 2px;
  background: var(--fv-header-bg);
  border: 1px solid var(--fv-cell-border);
  overflow: hidden;
}

.fv-strip-share-fill {
  display: block;
  width: 100%;
  height: 100%;
}

.fv-strip-share--absent {
  background: transparent;
  border-style: dashed;
}

/* ── Per-row impact tier (Low..High impact) ──────────────── */
.fv-row-tier {
  font-size: 11px;
  font-weight: 600;
  text-align: right;
  padding-right: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fv-row-tier[data-fv-level="1"],
.fv-row-tier[data-fv-level="2"],
.fv-row-tier[data-fv-level="3"] { color: var(--fv-l1); background: transparent; }
.fv-row-tier[data-fv-level="4"],
.fv-row-tier[data-fv-level="5"],
.fv-row-tier[data-fv-level="6"] { color: var(--fv-text-secondary); background: transparent; }
.fv-row-tier[data-fv-level="7"],
.fv-row-tier[data-fv-level="8"],
.fv-row-tier[data-fv-level="9"] { color: var(--fv-l9); background: transparent; }
.fv-row-tier--empty { color: var(--fv-text-secondary); opacity: 0.5; }

/* ── Per-row off-hours count (was the standalone hard-hour chart) ── */
/* The off-hours counter ("● 3/12") lives in the last grid track, which narrows
   to 34px on a phone (see the 560px block). Without min-width:0 a flex item
   defaults to min-width:auto and REFUSES to shrink below its content, so the
   value overflowed leftward and painted on top of the last hour cell and its
   own dot — the "two overlapping 0s" (2026-08-04). Its sibling .fv-row-tier
   already carries exactly this guard; .fv-row-hard was simply missed. */
.fv-row-hard {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  min-width: 0;
  overflow: hidden;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--fv-text-secondary);
}
.fv-row-hard-val {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fv-row-hard-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: none;
}
.fv-row-hard-val { font-weight: 600; color: var(--fv-text); }
.fv-row-hard-total { color: var(--fv-text-secondary); font-weight: 400; }

/* 2026-08-04: was `border: 1px solid` + `border-radius: 2px` on all four sides.
   At ~10-30px of track that meant 2px of every cell was border and each cell
   read as a detached candy-stripe bar rather than an hour of a continuous day.
   Now a right-edge hairline only (the same treatment .ts-cell uses), with the
   row's own rounding applied at the two ends — so the strip reads as one
   24-hour band you can actually index into. min-width:0 lets a cell shrink to
   its track instead of forcing the row wider. */
.fv-strip-cell {
  height: 22px;
  min-width: 0;
  border-right: 1px solid var(--fv-cell-border);
  cursor: pointer;
  transition: transform 80ms ease, outline-color 80ms ease;
  outline: 2px solid transparent;
  outline-offset: 1px;
}

/* ── QUARTER-DAY ANCHOR LINES (2026-08-05) ─────────────────────────────────
   User: "the cells and indicators aren't directly relatable and don't provide
   the hourly labels as expected throughout my app."

   The footer summary solved this by numbering each cell (.ts-cell-hour). That
   fix does NOT transfer here, and the difference is structural: the footer is
   ONE row, this strip is one row PER MEMBER. Numbering every cell would print
   24 x N glyphs (120 on a 5-member board) at 22px over saturated red/green
   fills: noise competing with the data it labels.

   The actual failure is distance, not absence: the hour axis is a single row
   pinned above the FIRST member, so reading the 3pm cell of the fifth row
   means tracing five rows up to the ruler and back. What that reader needs is
   a vertical anchor they can follow down the strip.

   So: a hairline at each quarter-day boundary (6a / 12p / 6p), carried by the
   cell that STARTS the quarter and running the full row height, with noon
   given more ink as the day's midpoint. Three lines per row, not 24 numerals,
   and they compound down the rows into continuous columns the eye tracks
   without leaving its row. Uses the existing --fv-cell-border token family,
   so no new colour language enters the panel.

   ⚠️Left border, not a pseudo: .fv-strip-cell:hover scales 1.2 and a pseudo
   would scale with it, thickening the anchor on hover. */
.fv-strip-cell[data-hour="6"],
.fv-strip-cell[data-hour="12"],
.fv-strip-cell[data-hour="18"] {
  border-left: 1px solid var(--fv-anchor-line);
}
/* Noon is the landmark the rest of the day is read against, so it gets the
   firmer line, the same "midnight and noon lead" hierarchy the axis already
   uses via [data-anchor]. */
.fv-strip-cell[data-hour="12"] {
  border-left-color: var(--fv-anchor-line-strong);
}
/* Round only the outer ends of the 24-cell band. :nth-child is counted against
   the ROW, whose children are: member gutter (1), hours (2..25), tier, hard. */
.fv-strip-row > .fv-strip-cell:nth-child(2) {
  border-top-left-radius: 3px;
  border-bottom-left-radius: 3px;
}
.fv-strip-row > .fv-strip-cell:nth-child(25) {
  border-top-right-radius: 3px;
  border-bottom-right-radius: 3px;
  border-right-color: transparent;
}

.fv-strip-cell:hover,
.fv-strip-cell:focus-visible {
  transform: scale(1.2);
  outline-color: var(--fv-text);
  z-index: 2;
  position: relative;
}

/* ── Responsive strip ──────────────────────────────────────────────────────
   ⭐2026-08-04 — this file previously contained ZERO @media queries, so the
   256px of fixed gutters and the 27-column template never relaxed at ANY
   width. On a phone that left the 24 hour cells fighting over what was left,
   which is the "overly compressed cell" the user reported.

   Two things scale down together:
     (a) THE GUTTERS shrink, handing their width back to the hour tracks.
     (b) THE AXIS thins BY DENSITY (drop every other label, then every third),
         keeping midnight + noon longest. Same ladder as .ts-axis in
         team-summary-panel.css — a label the reader can't resolve is worse
         than no label, and thinning by parity keeps ticks evenly spaced.
   The JS always emits all 24 ticks; only CSS decides which are legible here. */
@media (max-width: 1100px) {
  .fv-strip {
    --fv-gutter-name: 104px;
    --fv-gutter-tier: 72px;
    --fv-gutter-hard: 40px;
  }
  /* All 24 axis labels stay (2026-08-10) — the odd-hour hiding that lived
     here made half the columns unlabelled on a laptop. The type shrinks
     instead; .fv-mer drops at the narrowest step below. */
  .fv-strip-hour-axis > span { font-size: 8px; letter-spacing: -0.03em; }
}

@media (max-width: 820px) {
  .fv-strip {
    --fv-gutter-name: 88px;
    --fv-gutter-tier: 0px;    /* tier text drops out; the row keeps its dot+count */
    --fv-gutter-hard: 38px;
  }
  /* The tier word ("Low impact") is the first thing worth losing — it repeats
     the colour the cells already carry, and it is the widest text in the row. */
  .fv-row-tier { display: none; }
}

@media (max-width: 560px) {
  /* All three gutters collapse to zero at this width: the reflow below moves the
     name and the counter onto their own line, so the axis and the row are a
     plain gutterless repeat(24, 1fr) — the same shape as the canonical
     .ts-strip. Kept declared (rather than deleted) so any rule still reading a
     gutter var resolves to 0 instead of inheriting the 820px value. */
  .fv-strip {
    --fv-gutter-name: 0px;
    --fv-gutter-tier: 0px;
    --fv-gutter-hard: 0px;
  }
  /* GIVE THE TIMELINE THE FULL WIDTH (2026-08-04).
     Squeezing labels was treating the symptom. The real problem is that on a
     phone the name gutter (74px) and the off-hours counter (44px) took 118px of
     a ~360px viewport, leaving ~242px for 24 hours — about 10px per hour, which
     no label fits in and which makes the cells themselves hard to read or tap.
     The canonical .ts-strip never hits this because it is a single AGGREGATE row
     with no name gutter at all; the fairness strip is PER-MEMBER, so it does
     need to name each row — but it does not need to name it *beside* the
     timeline.
     So on phones the row becomes two lines: the member name + counter ride on
     their own line above, and the 24 hour cells get the entire width beneath.
     That is ~118px back — hour tracks go from ~10px to ~15px, a 50% gain — and
     it is what makes the hour labels legible rather than merely non-overlapping. */
  .fv-strip-row {
    grid-template-columns: repeat(24, minmax(0, 1fr));
    row-gap: 3px;
    padding-bottom: 8px;
    align-items: stretch;
  }
  /* Row 1 = name (left) + counter (right). Row 2 = the 24 hour cells, which
     auto-place into the next available track and therefore fill row 2 exactly.
     Explicit line numbers rather than named areas: the renderer emits
     member → 24 cells → tier → hard, and placing only the two bookends keeps
     the 24 cells on the auto-placement path they already use. */
  .fv-strip-member {
    grid-column: 1 / 15;
    grid-row: 1;
    flex-direction: row;
    align-items: baseline;
    justify-content: flex-start;
    gap: 6px;
    padding-right: 0;
  }
  .fv-strip-row .fv-row-hard {
    grid-column: 15 / -1;
    grid-row: 1;
    justify-self: end;
    align-self: baseline;
  }
  /* The tier chip is already display:none at this width; keep it out of the
     auto-placement flow so it cannot claim a cell track. */
  .fv-strip-row .fv-row-tier { grid-row: 1; }
  .fv-strip-cell { grid-row: 2; }
  /* The share bar is a name-gutter ornament; on the meta line it would fight the
     name for width. The hour cells already carry the same signal. */
  .fv-strip-row .fv-strip-share { display: none; }

  /* The axis loses its gutters in lockstep, or every label points at the wrong
     cell. This is the invariant the legibility guard pins. */
  .fv-strip-hour-axis {
    grid-template-columns: repeat(24, minmax(0, 1fr));
  }
  .fv-strip-hour-axis > span:first-child { display: none; }

  /* All 24 labels, shortened rather than sampled (2026-08-10). The previous
     rule showed six landmarks and hid the rest — "the user asked for hour
     labels" and then it deleted eighteen of them. At ~15px tracks a 2-digit
     numeral at 7px fits; the am/pm letter goes instead. */
  .fv-strip-hour-axis > span {
    font-size: 7px;
    letter-spacing: -0.06em;
  }
  .fv-strip-hour-axis .fv-mer { display: none; }
  /* Taller cells claw back the touch target the narrower tracks give up, and
     hover-scale is pointless on touch (it fires on tap and sticks). */
  .fv-strip-cell { height: 26px; }
  .fv-strip-cell:hover { transform: none; }
  .fv-strip-member-tz { display: block; margin-left: 0; }
}

/* ── 9-step color mapping (used by strip, trend, mode picker pip legend) ──
   Canonical traffic-light vocabulary so the legend pips match the
   fairness-mode cell shading and the broader STATUS_PRESENTATION canon. */
[data-fv-level="1"] { background: var(--fv-l1); }
[data-fv-level="2"] { background: var(--fv-l2); }
[data-fv-level="3"] { background: var(--fv-l3); }
[data-fv-level="4"] { background: var(--fv-l4); }
[data-fv-level="5"] { background: var(--fv-l5); }
[data-fv-level="6"] { background: var(--fv-l6); }
[data-fv-level="7"] { background: var(--fv-l7); }
[data-fv-level="8"] { background: var(--fv-l8); }
[data-fv-level="9"] { background: var(--fv-l9); }

/* ── Per-row fairness painting on main timeline cells ──────
 * Identity 2 of the view-modes rework. Each cell gets a
 * data-fairness-level (1..9) reflecting THIS row's member's burden
 * at that hour, computed by FairnessView.applyFairnessToRow.
 *
 * Selector chains .timeline-cell twice (0,4,1) to outweigh the day-cycle
 * [data-hour="N"] gradient (0,3,1) on the `background` channel — see the
 * 2026-06-05 P0-fix note below for why the fill lives on `background`, not
 * `box-shadow`. */
/* 2026-06-05 (P0 fix — fairness fill clobber): paint the level fill on the
   `background` channel, OPAQUE, exactly like availability-mode.css does. The
   previous design painted the fill as a translucent `box-shadow: inset 100px`
   overlay so the day/night gradient read through as the base. But `box-shadow`
   is a single, non-stacking property, and the working/sleep/golden bracket
   rules ALSO set `box-shadow` (style.css `.working-hour`/`.sleeping-hour`
   `box-shadow:none !important`; best-time-indicator.css golden caps). On every
   working- or sleeping-hour cell — ~66% of every row — the bracket rule won and
   the fairness fill VANISHED, dropping the cell to the bare day/night sky
   gradient. Verified live (chrome-devtools): 1096/1656 cells fell through.
   That bleed-through was the "too many colours / glitches in fairness" report.

   Fix: `background` channel (NOT box-shadow) on the doubled
   `.timeline-cell.timeline-cell[data-fairness-level]` selector (specificity
   0,4,1) — out-specifies the day-cycle `[data-hour]` gradient (0,3,1), and is a
   DIFFERENT property from the bracket box-shadows so they can no longer collide.

   2026-06-05 (gentle restore): the fill is a 70% color-mix toward an OPAQUE
   surface (--surface-primary, white in light / dark in dark mode) — NOT the pure
   opaque token, and NOT toward --cell-base (which is `transparent`, so mixing
   toward it would yield a 70%-ALPHA fill and let the day/night gradient bleed
   back through — the exact P0 bug). Mixing toward the opaque surface gives the
   calm muted register the user prefers WHILE staying a solid, deterministic
   colour with zero gradient bleed. The P0 fix only needed to change the CHANNEL
   (box-shadow → background); saturation stays gentle. Brackets still render via
   their ::before side-caps (below). */
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level="1"] { background: linear-gradient(color-mix(in srgb, var(--fv-l1) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l1) 70%, var(--surface-primary, #fff))), var(--cell-daynight), var(--cell-daynight-solid) !important; }
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level="2"] { background: linear-gradient(color-mix(in srgb, var(--fv-l2) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l2) 70%, var(--surface-primary, #fff))), var(--cell-daynight), var(--cell-daynight-solid) !important; }
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level="3"] { background: linear-gradient(color-mix(in srgb, var(--fv-l3) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l3) 70%, var(--surface-primary, #fff))), var(--cell-daynight), var(--cell-daynight-solid) !important; }
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level="4"] { background: linear-gradient(color-mix(in srgb, var(--fv-l4) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l4) 70%, var(--surface-primary, #fff))), var(--cell-daynight), var(--cell-daynight-solid) !important; }
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level="5"] { background: linear-gradient(color-mix(in srgb, var(--fv-l5) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l5) 70%, var(--surface-primary, #fff))), var(--cell-daynight), var(--cell-daynight-solid) !important; }
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level="6"] { background: linear-gradient(color-mix(in srgb, var(--fv-l6) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l6) 70%, var(--surface-primary, #fff))), var(--cell-daynight), var(--cell-daynight-solid) !important; }
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level="7"] { background: linear-gradient(color-mix(in srgb, var(--fv-l7) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l7) 70%, var(--surface-primary, #fff))), var(--cell-daynight), var(--cell-daynight-solid) !important; }
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level="8"] { background: linear-gradient(color-mix(in srgb, var(--fv-l8) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l8) 70%, var(--surface-primary, #fff))), var(--cell-daynight), var(--cell-daynight-solid) !important; }
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level="9"] { background: linear-gradient(color-mix(in srgb, var(--fv-l9) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l9) 70%, var(--surface-primary, #fff))), var(--cell-daynight), var(--cell-daynight-solid) !important; }

/* ── Categorical-status override ──────────────────────────────────────────
   Sleep is intentionally NOT overridden in fairness mode — the fairness
   ramp already encodes sleep cost via chronotype + work window, so
   painting sleep-purple on top would double-encode and flatten the
   ramp's gradient through the night. Busy and away remain as overrides
   because they are real events the ramp doesn't know about. */
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-categorical-status="busy"] {
  /* --shading-busy (red busy TIME), not --status-busy (slate busy CHIP). This
     rule was painting a slate cell under a red team-bar segment (2026-09-02). */
  background: linear-gradient(var(--shading-busy, #dc2626), var(--shading-busy, #dc2626)), var(--cell-daynight), var(--cell-daynight-solid) !important;
}
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-categorical-status="away"] {
  background: repeating-linear-gradient(
    -45deg,
    var(--status-away) 0,
    var(--status-away) 6px,
    #86198f           6px,
    #86198f           10px
  ), var(--cell-daynight), var(--cell-daynight-solid) !important;
}

/* Don't let day-cycle bracket pseudos paint over the fill. */
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level]::before,
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level]::after {
  background: transparent !important;
}

/* Per-hour separators — same translucent hairline availability/overlap use.
   Keyed on [data-fairness-level] because the availability-mode separator
   rule only matches [data-availability-status], which fairness cells don't
   carry.

   2026-06-13 (divider legibility): the old single 0.18 line vanished against
   the deeper fills (l1/l2 deep green, l6-l9 orange to red) while reading fine
   on the light tiers, so cells looked like they were missing dividers. A lone
   translucent line always loses against fills near its own luminance. A
   box-shadow companion line would read on every tier but is unsafe here: it
   collides with the work/sleep bracket box-shadows on start/end cells (see the
   2026-06-05 fill-channel note above). So we just give the border-right enough
   contrast to read on every tier: a darker line in light mode, a brighter line
   in dark mode. */
body[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level] {
  border-right: 1px solid rgba(15, 23, 42, 0.42) !important;
}
body.dark-mode[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level],
body.dark-mode[data-color-mode="fairness"] .timeline-cell.timeline-cell[data-fairness-level] {
  border-right: 1px solid rgba(255, 255, 255, 0.38) !important;
}

/* Accessibility: diagonal patterns when data-a11y="patterns" */
body[data-a11y="patterns"] [data-fv-level="4"],
body[data-a11y="patterns"] [data-fv-level="5"],
body[data-a11y="patterns"] [data-fv-level="6"] {
  background-image: repeating-linear-gradient(
    45deg,
    rgba(0, 0, 0, 0.18) 0 2px,
    transparent 2px 5px
  );
  background-blend-mode: multiply;
}

body[data-a11y="patterns"] [data-fv-level="7"],
body[data-a11y="patterns"] [data-fv-level="8"],
body[data-a11y="patterns"] [data-fv-level="9"] {
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(0, 0, 0, 0.3) 0 2px,
    transparent 2px 4px
  );
  background-blend-mode: multiply;
}

/* ── Phase 5: sparkline → dashboard handoff ───────────────
   Small inline link below the 10-point sparkline that takes users to
   the full Fairness Dashboard (multi-year ledger trend + team equity
   card + CSV export). */
.fv-spark-history-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
  padding: 2px 0;
  font-size: 11px;
  font-weight: 600;
  color: var(--fv-text-secondary);
  text-decoration: none;
  cursor: pointer;
  transition: color 0.15s ease;
}
.fv-spark-history-link:hover  { color: var(--fv-text); }
.fv-spark-history-link:focus-visible { outline: none; box-shadow: var(--focus-ring-shadow); }
.fv-spark-history-link i      { font-size: 9px; opacity: 0.7; }

/* ── Next pick — Phase 3 of fairness redesign ─────────────
   Leads the convenience-rotation section with a single concrete
   recommendation + an action button. The ranked top-3 list stays
   below as supporting context. */
.fv-next-pick {
  margin: 6px 0 10px;
  padding: 10px 12px;
  background: var(--fv-panel-bg);
  border: 1px solid var(--fv-cell-border);
  border-left: 3px solid var(--fv-l3);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Same micro-label rank as .fv-section-title / .ts-stat-label — was 700/0.06em
   against their 600/0.05em. (Polish pass 2026-08-04.) */
.fv-next-pick-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--fv-text-secondary);
}

.fv-next-pick-copy {
  font-size: 12px;
  line-height: 1.4;
  color: var(--fv-text);
}

.fv-next-pick-action {
  align-self: flex-start;
  margin-top: 2px;
  padding: 4px 10px;
  background: var(--fv-l3);
  color: #ffffff;
  border: 1px solid transparent;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  transition: filter 0.15s ease;
}
.fv-next-pick-action:hover  { filter: brightness(1.08); }
.fv-next-pick-action:focus-visible { outline: none; box-shadow: var(--focus-ring-shadow); }
.fv-next-pick-action:active { filter: brightness(0.95); }

/* ── Owed convenience next ─────────────────────────────── */
.fv-owed-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.fv-owed-row {
  display: grid;
  /* 5 cells: rank · name · headroom-chip · structural-pill · meta.
     The structural pill column collapses to 0 when absent (auto sizing). */
  grid-template-columns: 22px 1fr auto auto auto;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: var(--fv-header-bg);
  border: 1px solid var(--fv-cell-border);
  border-radius: 8px;
  font-size: 12px;
}

.fv-owed-rank {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--fv-panel-bg);
  border: 1px solid var(--fv-panel-border);
  color: var(--fv-text-secondary);
  font-weight: 700;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.fv-owed-name {
  color: var(--fv-text);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fv-owed-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #ffffff;
  border: 1px solid var(--fv-cell-border);
}
/* Chip text colour: white reads on saturated endpoints (l1/l2/l8/l9);
   the pale mid-band (l3..l7) needs dark text for contrast. */
[data-fv-level="1"].fv-owed-chip,
[data-fv-level="2"].fv-owed-chip,
[data-fv-level="8"].fv-owed-chip,
[data-fv-level="9"].fv-owed-chip { color: #ffffff; }
[data-fv-level="3"].fv-owed-chip,
[data-fv-level="4"].fv-owed-chip,
[data-fv-level="5"].fv-owed-chip,
[data-fv-level="6"].fv-owed-chip,
[data-fv-level="7"].fv-owed-chip { color: var(--fv-text); }

/* Phase 2: structural pill — timezone-tax portion that's reported but
   not charged. Greyed and de-emphasised so it sits next to the headroom
   chip without competing for attention. */
.fv-owed-structural {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--fv-text-secondary);
  background: var(--fv-panel-bg);
  border: 1px dashed var(--fv-cell-border);
  white-space: nowrap;
}

.fv-owed-meta {
  font-size: 11px;
  color: var(--fv-text-secondary);
  font-variant-numeric: tabular-nums;
}

/* ── Team sparkline ────────────────────────────────────── */
.fv-spark {
  width: 100%;
  height: 40px;
  color: var(--fv-l2);
  display: block;
}

.fv-spark-delta {
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.fv-spark-delta-up { color: var(--fv-l2); }
.fv-spark-delta-down { color: var(--fv-l8); }
.fv-spark-delta-flat { color: var(--fv-text-secondary); }

/* ── Legend ────────────────────────────────────────────── */
.fv-legend {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--fv-panel-border);
  font-size: 11px;
  color: var(--fv-text-secondary);
  flex-wrap: wrap;
}

.fv-legend-scale {
  display: flex;
  align-items: center;
  gap: 2px;
}

.fv-legend-chip {
  width: 16px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid var(--fv-cell-border);
}

.fv-legend-label {
  padding: 0 4px;
}

/* Decoder appended below the legend when the tilt layer is on.
   Tells the user the shading combines hour cost + personal recent share. */
.fv-legend-note {
  margin: 6px 8px 0;
  font-size: 11px;
  line-height: 1.4;
  color: var(--text-secondary, #94a3b8);
}

/* Suppress legacy purple fairness heatmap cell shading while fairness view owns it */
body[data-color-mode="fairness"] .timeline-cell[data-fairness] {
  /* availability density wins — neutralize legacy overlay */
  --fairness-overlay: none;
}

/* ── Scanner in fairness mode ────────────────────────────
 * REVERTED: earlier edits added always-visible drag handles and an outline
 * ring on .vertical-comparison-column scoped to fairness mode. Removed to
 * ensure the scanner renders identically in all modes — its native
 * traffic-light status colours + breathing animation already convey state.
 * The availability count badge below is self-contained and does not mutate
 * the scanner's layout. */

/* ── Scanner badge — per-mode readout ──────────────────────
 * Renamed from .fv-scanner-badge to .vm-scanner-badge: the badge is no
 * longer fairness-specific. ScannerBadge module (js/ui/scanner-badge.js)
 * decides per mode what to render and whether to show the badge at all.
 * Standard mode hides it via JS (no DOM node). The CSS just handles
 * appearance. */
.vm-scanner-badge {
  position: absolute;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  padding: 3px 10px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.92);
  color: var(--fv-text, #111827);
  border: 1.5px solid rgba(0, 0, 0, 0.15);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  white-space: nowrap;
  pointer-events: none;
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  line-height: 1.2;
  /* Mode-hue stripes are added symmetrically per data-bar-mode below
   * (left + right inset shadows of equal width). Padding stays balanced
   * so the pill reads even on both edges. */
}

.vm-scanner-badge-value {
  font-weight: 700;
}

.vm-scanner-badge-sep {
  opacity: 0.55;
  margin: 0 1px;
}

.vm-scanner-badge-label {
  margin-left: 4px;
  font-weight: 500;
  opacity: 0.75;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.vm-scanner-badge-empty {
  opacity: 0.6;
  font-weight: 500;
}

/* P1-10: the empty ("No team") badge previously rendered at level 5, so it
   looked like a real neutral verdict. Neutralize the verdict tint — dashed
   grey border + muted text — so "nothing to judge" never reads as a score. */
.vm-scanner-badge:has(.vm-scanner-badge-empty) {
  border-color: var(--border-color, #d1d5db) !important;
  border-style: dashed !important;
  opacity: 0.7;
}

/* Colour-code badge border by level (unified convention: 1=best/green, 9=worst/red). */
.vm-scanner-badge[data-fv-level="1"] { border-color: var(--fv-l1); }
.vm-scanner-badge[data-fv-level="2"] { border-color: var(--fv-l2); }
.vm-scanner-badge[data-fv-level="3"] { border-color: var(--fv-l3); }
.vm-scanner-badge[data-fv-level="4"] { border-color: var(--fv-l4); }
.vm-scanner-badge[data-fv-level="5"] { border-color: var(--fv-l5); }
.vm-scanner-badge[data-fv-level="6"] { border-color: var(--fv-l6); }
.vm-scanner-badge[data-fv-level="7"] { border-color: var(--fv-l7); }
.vm-scanner-badge[data-fv-level="8"] { border-color: var(--fv-l8); }
.vm-scanner-badge[data-fv-level="9"] { border-color: var(--fv-l9); }

body.dark-mode .vm-scanner-badge,
body.dark-mode .vm-scanner-badge {
  background: rgba(31, 41, 55, 0.92);
  color: var(--fv-text, #f3f4f6);
  border-color: rgba(255, 255, 255, 0.2);
}

/* Auto-pair confirmation pulse — fires when the cell mode change snaps
   the scanner chip to the recommended pairing. Brief glow + scale so the
   user sees the chip just changed without a modal or toast. */
@keyframes vm-scanner-badge-pulse {
  0%   { box-shadow: 0 0 0 0   rgba(99, 102, 241, 0.55); transform: scale(1); }
  40%  { box-shadow: 0 0 0 8px rgba(99, 102, 241, 0);    transform: scale(1.06); }
  100% { box-shadow: 0 0 0 0   rgba(99, 102, 241, 0);    transform: scale(1); }
}
.vm-scanner-badge.vm-scanner-badge-pulse {
  animation: vm-scanner-badge-pulse 0.55s ease-out;
}

/* Mode signature now lives on the team-status-bar (the avg bar at the top
 * of the calendar), not the scanner pill. The pill keeps only its
 * level-based border (data-fv-level) — clean separation:
 *   • status-bar    → "what lens am I in" (app-level, ambient)
 *   • scanner pill  → "how good is this slot" (slot-level, dynamic) */

/* Mode-signature tint (left edge + drag handles) removed: the left chip's
 * icon already signals the active lens. data-bar-mode is still set on the
 * column for downstream behaviour (per-row strip suppression in
 * scanner-scope-shading.css keys on its presence). */

/* ══════════════════════════════════════════════════════════════════════
   ZONE A2 — FAIREST WINDOWS FOR THE GROUP (2026-08-06)
   One 24-cell TEAM lane on the viewer's clock, traffic-lighted by the
   group's combined per-hour impact. Cells reuse the global
   [data-fv-level] ramp fills; hour numerals mirror the footer summary's
   .ts-cell-hour idiom (in-cell, white-halo, density-thinned) so this
   lane and the strips above it speak one vocabulary.
   ══════════════════════════════════════════════════════════════════════ */
.fv-team-window-section { margin-top: 2px; }

.fv-team-window-verdict {
  font-size: 13px;
  color: var(--fv-text);
  margin: 2px 0 8px;
}
.fv-team-window-verdict b { font-weight: 700; }

.fv-team-strip {
  display: grid;
  grid-template-columns: repeat(24, minmax(0, 1fr));
  /* ONE row (2026-08-10): the fairest-window mark moved out of the strip and
     onto the shared star-diamond arc (.ts-barlane) above it, exactly like the
     three footer panels. The strip is cells only, at the family height - the
     same clamp the .ts-strip runs, so the five summaries read as one system. */
  grid-template-rows: clamp(28px, calc(var(--timeline-row-height, 68px) * 0.62), 44px);
  row-gap: 0;
  column-gap: 0;
}

/* The labeled best-window bar. The label answers "what is this bar?"
   in place — the unlabeled overline was read as decoration (user
   2026-08-06: "the underline and overlineing is confusing"). */
.fv-team-bar {
  grid-row: 1;
  align-self: end;
  height: 5px;
  border-radius: 3px;
  background: var(--fv-l3);
  position: relative;
}
.fv-team-bar-label {
  position: absolute;
  bottom: calc(100% + 1px);
  left: 50%;
  transform: translateX(-50%);
  font-style: normal;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--fv-l2);
  white-space: nowrap;
  pointer-events: none;
}

.fv-team-cell {
  position: relative;
  border-right: 1px solid var(--fv-cell-border);
  min-width: 0;
  height: 100%;
}

/* ── CANONICAL WASH OVER THE SKY (2026-08-10) ────────────────────────────────
   .fv-team-cell joined style.css's [data-hour] sky selectors, so these washes
   MUST outrank the sky by SPECIFICITY (0,4,1 vs the sky's 0,3,1) - never by
   file order, which the production bundler does not preserve (the exact bug
   that turned every footer panel into Local times this morning). The paint is
   the BOARD's own fairness-cell shape verbatim: the level token at 70% over
   the surface, layered on the day/night gradient. --fv-l* are theme-scoped,
   so dark mode adapts through the tokens with no extra rules. */
body[data-color-mode="fairness"] .fv-team-cell.fv-team-cell[data-fv-level="1"] { background: linear-gradient(color-mix(in srgb, var(--fv-l1) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l1) 70%, var(--surface-primary, #fff))), var(--cell-daynight, none), var(--cell-daynight-solid, var(--surface-primary, #fff)) !important; }
body[data-color-mode="fairness"] .fv-team-cell.fv-team-cell[data-fv-level="2"] { background: linear-gradient(color-mix(in srgb, var(--fv-l2) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l2) 70%, var(--surface-primary, #fff))), var(--cell-daynight, none), var(--cell-daynight-solid, var(--surface-primary, #fff)) !important; }
body[data-color-mode="fairness"] .fv-team-cell.fv-team-cell[data-fv-level="3"] { background: linear-gradient(color-mix(in srgb, var(--fv-l3) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l3) 70%, var(--surface-primary, #fff))), var(--cell-daynight, none), var(--cell-daynight-solid, var(--surface-primary, #fff)) !important; }
body[data-color-mode="fairness"] .fv-team-cell.fv-team-cell[data-fv-level="4"] { background: linear-gradient(color-mix(in srgb, var(--fv-l4) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l4) 70%, var(--surface-primary, #fff))), var(--cell-daynight, none), var(--cell-daynight-solid, var(--surface-primary, #fff)) !important; }
body[data-color-mode="fairness"] .fv-team-cell.fv-team-cell[data-fv-level="5"] { background: linear-gradient(color-mix(in srgb, var(--fv-l5) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l5) 70%, var(--surface-primary, #fff))), var(--cell-daynight, none), var(--cell-daynight-solid, var(--surface-primary, #fff)) !important; }
body[data-color-mode="fairness"] .fv-team-cell.fv-team-cell[data-fv-level="6"] { background: linear-gradient(color-mix(in srgb, var(--fv-l6) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l6) 70%, var(--surface-primary, #fff))), var(--cell-daynight, none), var(--cell-daynight-solid, var(--surface-primary, #fff)) !important; }
body[data-color-mode="fairness"] .fv-team-cell.fv-team-cell[data-fv-level="7"] { background: linear-gradient(color-mix(in srgb, var(--fv-l7) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l7) 70%, var(--surface-primary, #fff))), var(--cell-daynight, none), var(--cell-daynight-solid, var(--surface-primary, #fff)) !important; }
body[data-color-mode="fairness"] .fv-team-cell.fv-team-cell[data-fv-level="8"] { background: linear-gradient(color-mix(in srgb, var(--fv-l8) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l8) 70%, var(--surface-primary, #fff))), var(--cell-daynight, none), var(--cell-daynight-solid, var(--surface-primary, #fff)) !important; }
body[data-color-mode="fairness"] .fv-team-cell.fv-team-cell[data-fv-level="9"] { background: linear-gradient(color-mix(in srgb, var(--fv-l9) 70%, var(--surface-primary, #fff)), color-mix(in srgb, var(--fv-l9) 70%, var(--surface-primary, #fff))), var(--cell-daynight, none), var(--cell-daynight-solid, var(--surface-primary, #fff)) !important; }

/* The ratio bar (user: "consider bars to show percentage of ... fairness"):
   height = this hour's share of the day's heaviest combined impact, in the
   app's neutral more-intensity ink, rising from the same floor as every
   other summary bar. The wash answers in bands; the bar answers exactly. */
.fv-team-cell::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: calc(var(--fv-ratio, 0) * 100%);
  background: rgba(15, 23, 42, 0.26);
  pointer-events: none;
}
body.dark-mode .fv-team-cell::after {
  background: rgba(0, 0, 0, 0.34);
}
/* The bars are <span>s, the cells <div>s, so type-scoped selectors count
   cells only regardless of how many bar arcs render ahead of them. */
div.fv-team-cell:first-of-type { border-radius: 6px 0 0 6px; }
div.fv-team-cell:last-of-type { border-radius: 0 6px 6px 0; border-right: none; }

/* ⚠️PAINT-FREE (2026-08-10): the white in-cell ring was the fairness twin of
   the footer's retired green tick - a second drawing of the window the
   star-diamond arc above the strip already marks. data-fairest stays as a
   data/a11y attribute with no visual, same contract as data-peak. */
.fv-team-cell[data-fairest="true"] {
  z-index: 1;
}

/* In-cell hour numeral — the .ts-cell-hour idiom verbatim. */
.fv-team-cell-hour {
  position: absolute;
  /* Bottom-centre (2026-08-10), matching the footer summary cells - the
     board keeps hour labels on the cell floor, and two aggregate strips
     with labels in different corners read as two different widgets. */
  bottom: 3px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1;
  font-size: 7.5px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary, #475569);
  text-shadow:
    0 0 2px rgba(255, 255, 255, 0.95),
    0 0 4px rgba(255, 255, 255, 0.85),
    0 1px 1px rgba(255, 255, 255, 0.9);
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
}
.fv-team-cell[data-hour="0"] .fv-team-cell-hour,
.fv-team-cell[data-hour="12"] .fv-team-cell-hour {
  font-weight: 700;
  color: var(--text-primary, #0f172a);
}
/* ⚠️SHRINK, NEVER HIDE (2026-08-10, user: "fairness still no time labels").
   The density thinning above-900px/below-560px was the same disease the
   footer summary was cured of two days earlier: the numerals were always
   emitted, CSS hid most of them, and on a phone the strip read as unlabelled.
   All 24 stay visible at every width; below 560px the am/pm letter (.fv-mer)
   drops — "12a" becomes "12" — so three characters never fight a ~15px cell.
   Landmarks keep their weight only. */
@media (max-width: 900px) {
  .fv-team-cell-hour { font-size: 7px; letter-spacing: -0.04em; }
}
@media (max-width: 560px) {
  .fv-team-cell-hour { font-size: 7px; letter-spacing: -0.06em; }
  .fv-team-cell-hour .fv-mer { display: none; }
}

/* MOBILE = NUMERALS ONLY (2026-08-29, owner: "only times should appear, no am
   or pm"). The width queries above are a VIEWPORT test; the compact board is a
   LAYOUT mode, so a compact board on a wider viewport kept its a/p. Both
   fairness hour surfaces are gated here — the per-member strip axis and the
   team-window strip — so the whole board speaks one way on mobile.
   Numerals always stay (SHRINK, NEVER HIDE, 2026-08-10); the `.fv-mer` span
   is aria-hidden, and the spoken hour comes from the cell title/aria-label. */
body[data-layout="compact"] .fv-strip-hour-axis .fv-mer,
body[data-layout="compact"] .fv-team-cell-hour .fv-mer { display: none; }

/* ── Sticky header on the phone sheet (2026-08-10) ──────────────────────────
   On deskpan the fairness panel is a fixed bottom sheet capped at 70dvh with
   overflow-y:auto. Mid-scroll, the title/tabs/score left the viewport and the
   first thing visible was a lone right-aligned "Full history" link over dead
   space (user: "random unfocused design and text"). The header now rides the
   scroll, so the sheet always names itself; the shadow only earns its keep
   once content actually slides underneath. */
body[data-mobile-board="deskpan"] .fv-panel .fv-header {
  position: sticky;
  top: calc(-1 * var(--space-4, 16px));
  z-index: 5;
  background: var(--fv-panel-bg);
  margin-top: calc(-1 * var(--space-2, 8px));
  padding-top: var(--space-2, 8px);
  padding-bottom: var(--space-2, 8px);
  box-shadow: 0 6px 8px -6px rgba(15, 23, 42, 0.18);
}
