/* ═══════════════════════════════════════════════════════════════════════════
   wcn-calendar — shared design tokens and components

   Ported from wcn-mainpage/index.html (the design system of record, rebuilt
   2026-07/08). The token block below is copied verbatim INCLUDING its comments,
   because those comments are the accessibility derivations — they are the reason
   the numbers are what they are, and a number without its derivation gets
   "cleaned up" by the next person.

   Two pages share this file (index.html and roster/index.html). wcn-mainpage
   inlines its CSS per page and copy-pastes the token block between index.html and
   cv/index.html; that is tolerable for two token blocks and intolerable for two
   copies of a month-grid implementation. Hence one stylesheet. There is still no
   build step, no framework, and no third-party request.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Type ─────────────────────────────────────────────────────────────────────
   Variable font, self-hosted. CANON's zero-third-party-request rule outranks the
   brand kit's Google Fonts sourcing note. This closes WCN-TODO.md:224 for this
   surface — the page this replaces called fonts.googleapis.com on every load. */

/* The file's wght axis is 100–900 (verified against its fvar table). ILBTYD and
   the walkthrough page declare `200 400` because that is all they use; copying
   that descriptor here would silently lock out every weight above 400 on a page
   that needs 600 for the stat figures. Same 43 KB file, full range. */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/raleway-latin-var.woff2') format('woff2');
}

/* Metric-matched fallback so the swap changes letterforms without reflowing.
   102% is measured (derivation on the ILBTYD site) — Raleway at weight 200 sets
   ~2% wider than the local grotesque. */
@font-face {
  font-family: 'Raleway Fallback';
  src: local('Helvetica Neue'), local('Arial');
  size-adjust: 102%;
}

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

:root {
  /* Company teal + off-white is the brand kit's designed pairing for the company
     mark. Off-white is #f4f4f4, NOT white — "the distinction matters." */
  --teal:      #216869;
  --teal-deep: #1a5354;
  --fg:        #f4f4f4;

  /* ── Why 0.82 and not ILBTYD's 0.55 ──────────────────────────
     This token could not be ported directly. On ILBTYD's pure black,
     rgba(255,255,255,0.55) composites to ~#8c8c8c and clears AA at 5.3:1.
     On teal the background is far lighter (relative luminance 0.113 vs 0.000),
     so the same 55% lands at 2.92:1 — failing AA for both normal AND large text,
     while being used for form labels, table cells, and body copy.
     0.82 composites to ~#ced9d9 and measures 4.54:1. Still visibly recessive
     against #f4f4f4, and actually readable. Do not lower it. */
  --muted:     rgba(244, 244, 244, 0.82);

  /* Decorative only — non-text, or text that is redundant with a real label.
     Never used for anything a reader must actually read.

     It has a second job on this page: it is the universal pill edge. Composited
     on teal it measures 3.20:1, which clears WCAG 1.4.11's 3:1 floor for the
     boundary of a non-text UI component. That matters because two of the pill
     fills below (sage 2.03:1, coral 2.09:1) do NOT clear 3:1 against the ground
     on their own — without this edge those pills would be shapes you have to
     hunt for. Every pill gets it, including the ones that don't need it, so the
     row reads as one system. */
  --muted-dim: rgba(244, 244, 244, 0.6);
  --hairline:  rgba(244, 244, 244, 0.16);

  /* Catalyst amber. Amber-on-teal measures 3.13:1 — it clears WCAG AA for LARGE
     text only, so it is used on display figures (≥24px) and never on body copy,
     small links, or pill labels. */
  --amber:     #f4a261;

  /* ── Pill fills, and why the ink is not brand charcoal ───────
     Event pills invert the page: brand hue as FILL, dark ink as TEXT. The obvious
     approach — brand hue as colored text on the teal ground — was measured and
     abandoned. On #216869, sage lands at 2.03:1 and coral at 2.09:1 as
     foreground. Both fail AA for every text size. Only --fg (5.87:1) and --muted
     (4.54:1) survive as text directly on the ground, which is not enough hues to
     carry a six-value taxonomy.

     The ink is #0e1614, not the brand kit's Charcoal #2d3436. Charcoal was tried
     first and reaches only 3.97:1 on sage and 4.10:1 on coral — under AA for the
     small text a pill label is. #0e1614 clears every fill with room to spare.
     Measured, against each fill:
       amber  #f4a261 → 8.91:1      gold  #EAAA00 → 8.96:1
       sage   #49a078 → 5.76:1      coral #e76f51 → 5.94:1
       fg     #f4f4f4 → 16.70:1
     If you add a fill, measure it against this ink before shipping it. */
  --ink:       #0e1614;
  --sage:      #49a078;
  --coral:     #e76f51;
  --gold:      #EAAA00;

  --pad: clamp(1.75rem, 5vw, 4.5rem);
}

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

body {
  background: var(--teal);
  color: var(--fg);
  font-family: 'Raleway', 'Raleway Fallback', system-ui, -apple-system, sans-serif;
  font-weight: 300;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ── Ghost watermark ──────────────────────────────────────────────────────────
   The master "?" mark, bleed-cropped off the lower-left, as texture rather than
   decoration. position:fixed so it stays put while the page scrolls past it.

   The main site uses 0.065 at this exact geometry. This page uses 0.045, and that
   is a deliberate re-derivation, not a copying error. Equal alpha is not equal
   presence: on the main site the mark sits behind a sparse hero, whereas here it
   passes behind a stat row and a six-column data table. Rendered at 0.065 the
   curve of the "?" cut visibly through the table's hairline rules and behind the
   tabular figures — texture becoming interference. 0.045 keeps it as ground.

   If you change the watermark's SIZE, or put denser content over it, re-render
   before trusting this number. */
.watermark {
  position: fixed;
  left: -8vmin;
  bottom: -18vmin;
  height: min(120vh, 160vw);
  width: auto;
  fill: var(--fg);
  opacity: 0.045;
  pointer-events: none;
  user-select: none;
  z-index: 0;
}

/* ── Nav, top right ───────────────────────────────────────────────────────────
   Quiet by design: findable when looked for, not announced. pointer-events:none
   on the container so the bar never eats clicks meant for the page beneath it;
   the links themselves re-enable it. */
.nav {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 10;
  display: flex;
  gap: clamp(1.1rem, 2.6vw, 2.25rem);
  padding: var(--pad);
  pointer-events: none;
}

.nav a {
  pointer-events: auto;
  color: var(--fg);
  text-decoration: none;
  font-weight: 300;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  opacity: 0.5;
  transition: opacity 200ms ease;
}

.nav a:hover,
.nav a:focus-visible,
.nav a[aria-current="page"] { opacity: 1; }

/* ── Focus ring, universal and deliberate ─────────────────────────────────── */
:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ── Layout ───────────────────────────────────────────────────────────────── */
main {
  position: relative;
  z-index: 1;
  padding: var(--pad);
  padding-top: clamp(5rem, 14vh, 8rem);
}

.wrap  { width: 100%; max-width: 76rem; margin-inline: auto; }

section + section {
  margin-top: clamp(3rem, 8vh, 5rem);
  padding-top: clamp(3rem, 8vh, 5rem);
  border-top: 1px solid var(--hairline);
}

/* ── Type scale ───────────────────────────────────────────────────────────── */
h1 {
  font-weight: 200;
  font-size: clamp(1.75rem, 4.5vw, 3rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

h2 {
  font-weight: 200;
  font-size: clamp(1.4rem, 3vw, 2rem);
  line-height: 1.15;
  letter-spacing: -0.015em;
}

h3 { font-weight: 400; font-size: 1rem; }

.lede {
  font-weight: 300;
  font-size: clamp(1.05rem, 2vw, 1.3rem);
  max-width: 44rem;
  color: var(--muted);
  margin-top: 0.75rem;
}

/* The single most repeated motif in the system: tiny, uppercase, wide-tracked. */
.eyebrow,
.section-label {
  font-size: 0.68rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--muted);
}

.section-label { display: block; margin-bottom: 0.9rem; }

/* ── The card: hairline box, no fill, no radius, no shadow ────────────────────
   This is the only card in the system. Resist adding a background or a radius —
   the whole visual identity is that surfaces are defined by a 1px line and
   nothing else. */
.box {
  border: 1px solid var(--hairline);
  padding: clamp(1.25rem, 3vw, 2rem);
}

/* ── Stat tiles ───────────────────────────────────────────────────────────── */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  border: 1px solid var(--hairline);
}

/* Hairline dividers via borders on the cells rather than gap-over-background:
   the grid reflows at every breakpoint and a faked-gap approach leaves stray
   lines dangling at the wrap points. */
.stat {
  padding: clamp(1.1rem, 3vw, 1.6rem);
  border-right: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}

.stat:last-child { border-right: none; }

.stat-figure {
  display: block;
  font-size: clamp(2rem, 6vw, 3.25rem);
  font-weight: 200;
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

/* Amber is legal here and nowhere else on this page: these figures are ≥32px,
   which is comfortably inside "large text" where 3.13:1 clears AA. */
.stat-figure.accent { color: var(--amber); }

/* An un-instrumented figure renders as an em dash. It is deliberately --muted-dim
   rather than --muted: the dash is not information the reader must extract, the
   footnote beneath it is. */
.stat-figure.unset { color: var(--muted-dim); font-weight: 100; }

.stat-label {
  display: block;
  margin-top: 0.6rem;
  font-size: 0.65rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted);
}

.stat-note {
  display: block;
  margin-top: 0.35rem;
  font-size: 0.65rem;
  color: var(--muted-dim);
  font-style: italic;
  letter-spacing: 0.02em;
}

/* ── Programme bands ──────────────────────────────────────────────────────────
   One band per challenge. The accent arrives as --program-accent from
   programs.json via renderPrograms(); the fallback keeps a band legible when a
   slug has no config entry, because a missing colour must not hide a person.

   Deliberately a left rule rather than a filled block: the accent has to work
   for an arbitrary future programme colour on this dark ground, and a hairline
   is the one treatment that survives any hue without a contrast calculation. */
.programs {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: clamp(1.25rem, 3vh, 1.75rem);
}

.program {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.9rem;
  padding: 0.6rem 0 0.6rem 0.9rem;
  border-left: 3px solid var(--program-accent, var(--sage));
}

.program-name {
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--fg);
}

.program-detail {
  font-size: 0.8rem;
  color: var(--muted);
  letter-spacing: 0.02em;
}

/* ── Roster: cohort grouping and enrolment states ─────────────────────────────
   The cohort header is the reason this table is readable at all once two start
   Mondays are live — see the grouping note in calendar.js. */
.cohort-head td {
  padding-top: 1.4rem;
  padding-bottom: 0.4rem;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted-dim);
  border-bottom: 1px solid var(--hairline);
}
tbody tr.cohort-head:first-child td { padding-top: 0; }

.badge {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border-radius: 2px;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.badge-active  { background: rgba(73, 160, 120, 0.18); color: var(--sage); }
.badge-waiting { background: rgba(244, 244, 244, 0.10); color: var(--muted); }
/* Late is the only state that asks for action, so it is the only one that gets
   an alarming colour. Overusing coral here would flatten that signal. */
.badge-late    { background: rgba(231, 111, 81, 0.18); color: var(--coral); }

.row-program {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.7rem;
  color: var(--muted-dim);
  letter-spacing: 0.02em;
}

.awaiting-cell { color: var(--muted); font-size: 0.85rem; }

/* Waiting rows are context, not the day's work. Dimming them keeps the active
   cohort dominant once both populations are on screen together. */
tr.state-waiting td { opacity: 0.72; }

/* ── Not in the study ─────────────────────────────────────────────────────────
   Store-review codes and unenrolled installs. These are NOT a fourth status on
   the same scale as active/waiting/late — they are outside the scale, and the
   styling has to say so, because reading them as "a kind of participant" is the
   exact error this section exists to correct.

   So they take no hue at all. Every real status owns a colour (sage active, muted
   waiting, coral late); withholding colour is the strongest available way to say
   "not on this axis" without inventing a sixth fill to measure. The dashed border
   carries the distinction that colour would otherwise carry. */
.badge-test,
.badge-unenrolled {
  background: none;
  /* --muted (4.54:1 on the ground), not --muted-dim (~3.4:1). The dashed border
     is what sets these apart; the text still has to be read. */
  color: var(--muted);
  border: 1px dashed var(--hairline);
  /* The dashed border adds 1px all round; pull the padding back so these pills
     sit on the same baseline as the solid ones rather than a pixel taller. */
  padding: calc(0.15rem - 1px) calc(0.5rem - 1px);
}

/* Dimmer than waiting (0.72), but not so dim the label stops passing AA —
   0.5 dragged --muted under the floor the palette note above sets. */
tr.state-test td,
tr.state-unenrolled td { opacity: 0.62; }

/* The group sits below every cohort and reads as an appendix, not a heading. */
.cohort-head-excluded td {
  color: var(--muted-dim);
  border-bottom-style: dashed;
}

/* ── Anomaly line ─────────────────────────────────────────────────────────────
   Coral, and its own line under the detail run-on. Coral is spent sparingly on
   this page — `late` is the only status that gets it — and this earns the same
   budget for the same reason: it is a statement that somebody is stuck and that
   only a person can unstick them. */
.anomaly {
  display: block;
  margin-top: 0.45rem;
  color: var(--coral);
  font-size: 0.85rem;
  font-weight: 500;
}

/* ── Em-dash list — the system's bullet ───────────────────────────────────── */
.contract { list-style: none; }
.contract li { position: relative; padding-left: 1.5rem; margin-bottom: 0.5rem; }
.contract li::before { content: "—"; position: absolute; left: 0; color: var(--muted); }
.contract li.pending::before { content: "○"; }

/* ── Ghost button ─────────────────────────────────────────────────────────── */
.btn {
  background: none;
  border: 1px solid var(--fg);
  color: var(--fg);
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  padding: 0.85rem 1.75rem;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  transition: background 180ms ease, color 180ms ease;
}

.btn:hover:not(:disabled) { background: var(--fg); color: var(--teal); }

/* ── Roster table ─────────────────────────────────────────────────────────── */
.roster-scroll { overflow-x: auto; }

.roster {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
  min-width: 44rem;
}

.roster th {
  text-align: left;
  font-size: 0.65rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted);
  padding: 0 0.9rem 0.7rem;
  border-bottom: 1px solid var(--hairline);
  white-space: nowrap;
}

.roster td {
  padding: 0.85rem 0.9rem;
  border-bottom: 1px solid var(--hairline);
  vertical-align: middle;
}

.roster tbody tr:last-child td { border-bottom: none; }

.roster .eid {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* The table already scrolls horizontally inside .roster-scroll, so cells should
   never wrap — a wrapped "Day 4 · non-Monday start" stacks into three lines and
   throws every other row's baseline out. Let the container scroll instead. */
.roster td { white-space: nowrap; }

/* ── Habit dots ───────────────────────────────────────────────────────────────
   Never the only channel: the numeric "3/4" sits beside them and carries the same
   fact for anyone who cannot resolve the dots. */
.dots { display: inline-flex; gap: 4px; vertical-align: middle; }

.dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid var(--muted-dim);
}

.dot.on { background: var(--fg); border-color: var(--fg); }

.dots-count {
  margin-left: 0.6rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  font-size: 0.85rem;
}

/* "Not asserted" is a third state, distinct from zero. The exporter emits null for
   presence-only check-ins (weekend rows carry no habit data), and rendering that as
   an empty row of dots would libel a compliant participant as idle. */
.not-asserted { color: var(--muted-dim); font-style: italic; font-size: 0.85rem; }

.yes { color: var(--fg); }
.no  { color: var(--muted-dim); }

/* ── Event pills ──────────────────────────────────────────────────────────────
   Fill carries the category, ink carries the label, and the --muted-dim edge
   guarantees the pill is perceivable as an object even where the fill itself is
   low-contrast against the ground. Contrast derivations are in the token block. */
.pill {
  display: block;
  font-size: 0.66rem;
  font-weight: 500;
  line-height: 1.25;
  padding: 3px 5px;
  margin-bottom: 2px;
  border: 1px solid var(--muted-dim);
  border-radius: 2px;
  color: var(--ink);
  background: var(--muted-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pill.evt-s3-session    { background: var(--amber); }
.pill.evt-look-back     { background: var(--sage); }
.pill.evt-pivot-meeting { background: var(--gold); }
.pill.evt-milestone     { background: var(--fg); }
.pill.evt-appearance    { background: var(--coral); }

/* Tabling shares appearance's coral on purpose. The brand kit has five usable
   hues and this taxonomy has six terms; the page it replaces invented
   --purple #312b7a to close that gap, which is a color that exists nowhere in
   WCN-Brand-Kit-v2.md. Reusing coral with a dashed edge keeps the palette honest
   and still distinguishes the two — unlike the old page, where evt-tabling and
   evt-appearance were the same solid coral and genuinely indistinguishable. */
.pill.evt-tabling {
  background: var(--coral);
  border-style: dashed;
  border-color: var(--ink);
}

/* General is the only unfilled pill: no category was asserted, so no hue is. */
.pill.evt-general {
  background: transparent;
  color: var(--muted);
  border-color: var(--hairline);
}

/* ── Legend ───────────────────────────────────────────────────────────────── */
.legend { display: flex; flex-wrap: wrap; gap: 0.6rem 1.4rem; margin-top: 1.25rem; }

.legend-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.68rem;
  color: var(--muted);
  letter-spacing: 0.06em;
}

/* 14px, not the 11px a swatch would normally want: Tabling is distinguished from
   Appearance by a DASHED edge, and at 11px a dashed 1px border on a rounded square
   degrades into a smudge that reads as a rendering artifact rather than a
   deliberate difference. The swatch has to be large enough to carry the one
   feature it exists to show. */
.legend-swatch {
  width: 14px;
  height: 14px;
  border-radius: 2px;
  border: 1px solid var(--muted-dim);
  flex: none;
}

/* ── Month grid ───────────────────────────────────────────────────────────── */
.months-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}

.month-nav { display: flex; gap: 0.5rem; }

.month-nav button {
  background: none;
  border: 1px solid var(--hairline);
  color: var(--fg);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1;
  width: 2.25rem;
  height: 2.25rem;
  cursor: pointer;
  transition: background 180ms ease, color 180ms ease, border-color 180ms ease;
}

.month-nav button:hover:not(:disabled) { background: var(--fg); color: var(--teal); border-color: var(--fg); }
.month-nav button:disabled { opacity: 0.3; cursor: default; }

.months-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2.5vw, 2rem);
}

@media (max-width: 62rem) { .months-row { grid-template-columns: 1fr; } }

.month { border: 1px solid var(--hairline); }

.month-title {
  padding: 0.9rem 1rem;
  font-size: 0.7rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--muted);
  border-bottom: 1px solid var(--hairline);
}

.dow-row,
.days-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }

.dow {
  text-align: center;
  padding: 0.5rem 0;
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-dim);
}

.day {
  min-height: 5.25rem;
  padding: 4px;
  border-top: 1px solid var(--hairline);
  border-right: 1px solid var(--hairline);
  overflow: hidden;
}

.day:nth-child(7n) { border-right: none; }
.day.other { opacity: 0.35; }

/* Today is marked by a fill AND a bolded numeral — the fill alone is a 1.09:1
   difference against the ground and would not survive a monochrome print or a
   low-vision reader. */
.day.today { background: rgba(244, 244, 244, 0.07); }
.day.today .day-num { font-weight: 600; color: var(--fg); }

.day-num {
  display: block;
  font-size: 0.7rem;
  color: var(--muted);
  margin-bottom: 3px;
  font-variant-numeric: tabular-nums;
}

.day-more { font-size: 0.6rem; color: var(--muted-dim); padding-left: 2px; }

/* ── Footer ───────────────────────────────────────────────────────────────── */
.foot {
  margin-top: clamp(3rem, 8vh, 5rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  align-items: baseline;
  font-size: 0.68rem;
  color: var(--muted-dim);
  letter-spacing: 0.06em;
}

.foot a { color: var(--muted); }
.foot .domain { margin-left: auto; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.err {
  border: 1px solid var(--amber);
  padding: 0.9rem 1.1rem;
  color: var(--muted);
  font-size: 0.85rem;
}

/* ── Motion and density guards ────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

@media (max-width: 40rem) {
  .nav { position: static; padding: 0 0 1.5rem; justify-content: flex-start; }
  main { padding-top: var(--pad); }
  .stat:nth-child(2n) { border-right: none; }
}
