/* ════════════════════════════════════════════════════════════════════════
   FAMILY CALENDAR — application stylesheet

   Consumes projects/studio-tool-kit/brand-foundation/calendar.tokens.json.
   There is no build step (tier 1/2, vanilla), so SECTION 1 below is the
   hand-authored mirror of that file's global tier. It is the ONLY place in
   this stylesheet where a raw value appears — everything after it references
   a custom property. Re-theming means editing the token file and this block,
   never hunting values through the rules.

   This is the Family Calendar's own skin. It does NOT use .bf-* (Brand
   Foundation) or .ds-* (BCG Design Studios) classes; mixing a brand's skin
   into another brand's work is a defect. Prefix is .fc-.

   Alpha-suffixed colours (a person's hue at 12%/26%/2e% etc.) are composed
   at render time in js/views/month.js from these same tokens and set as
   inline custom properties — that is why the fills below read var(--bar-c)
   rather than a literal.
   ════════════════════════════════════════════════════════════════════════ */

/* ── Plus Jakarta Sans. Pinned Google Fonts css2 request (tier 2).
      NOTE: this stylesheet is NOT safe to drop into a CSP-locked Claude
      Artifact as-is — the font request is blocked there and it will fall
      back to the system stack, which the Typography spec forbids for
      considered work. Inline the font or accept the fallback knowingly. ── */
/* FONTS.md §1: weights actually used are 500/600/700/800 — "the current files
   request 400 as well; it is never used, so drop it from the build request."
   Verified: nothing in this stylesheet resolves to 400 (--fc-weight-regular
   is 500). Dropped 2026-08-05. */
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&display=swap');


/* ════════════════════════════════════════════════════════════════════════
   SECTION 1 — GLOBAL TIER (mirror of calendar.tokens.json)
   ════════════════════════════════════════════════════════════════════════ */
:root {
  /* people — categorical, never a ramp. Colour answers WHO. */
  --fc-person-oli:    #FB6A2E;
  --fc-person-laura:  #EC4899;
  --fc-person-shared: #33518A;
  --fc-person-cosmo:  #7C3AED;

  /* point-event categories */
  --fc-cat-birthday:    #F59E0B;
  --fc-cat-appointment: #5C6679;
  --fc-cat-kid:         #7C3AED;

  /* Schengen — green is reserved for this and never colours a trip */
  --fc-eu-ink:      #0E7A55;
  --fc-eu-fill:     #10B981;
  --fc-eu-fill-2:   #34D399;
  --fc-eu-track:    #D9EEE4;
  --fc-eu-panel:    #F2FAF6;
  --fc-eu-chip:     #ECF9F2;
  --fc-eu-micro:    #7FB79E;
  --fc-eu-plan-a:   #93C5FD;
  --fc-eu-plan-b:   #DCE9FD;

  /* today / selection */
  --fc-today:         #4F7DF3;
  --fc-today-numeral: #2F5BD0;
  --fc-today-cell:    #F7FAFF;

  /* alert (not used by the month view; kept so Where does not re-derive) */
  --fc-warn-spine:  #F0A310;
  --fc-warn-button: #E08A00;
  --fc-warn-ground: #FFF7E8;
  --fc-breach:        #E5123F;
  --fc-breach-figure: #D3123C;
  --fc-breach-ground: #FFF0F3;

  /* text */
  --fc-text-ink:         #1A2233;
  --fc-text-ink-2:       #28313F;
  --fc-text-secondary:   #5C6679;
  --fc-text-past-label:  #3E4757;
  --fc-text-muted:       #8A94A6;
  --fc-text-muted-2:     #98A1AE;
  --fc-text-muted-3:     #9AA3B4;
  --fc-text-dow:         #AEB6C4;
  --fc-text-weekend-num: #7E8CA3;
  --fc-text-outside-num: #CBD1DB;
  --fc-text-faint:       #C3CAD8;
  --fc-text-on-fill:     #FFFFFF;

  /* surfaces */
  --fc-page-a: #EEF3FD;
  --fc-page-b: #F5F1FC;
  --fc-page-c: #FDF2F4;
  --fc-surface-card:          #FFFFFF;
  --fc-surface-rail:          #F7F9FD;
  --fc-hairline-card:         #EEF1F7;
  --fc-hairline-row:          #F0F2F7;
  --fc-cell-vertical:         #F3F5F9;
  --fc-divider:               #E4E9F2;
  --fc-divider-weekend:       #D9E1EE;
  --fc-weekend-cell:          #F4F7FC;
  --fc-weekend-chip:          #E7EDF9;
  --fc-weekday-chip:          #F5F7FB;
  --fc-holiday-cell:          #EEF9F2;
  --fc-holiday-chip:          #E3F4EA;
  --fc-holiday-chip-text:     #3E8C6C;
  --fc-holiday-chip-inspector: #EAF6EF;
  --fc-outside-cell:          #FCFCFD;
  /* ── promoted at integration, 2026-08-06 ───────────────────────────────
     These four were declared independently inside css/day.css, css/where.css
     and css/inspector.css, because each lane's write envelope excluded the
     token file and the mirror gate fails any `:root` name that has no token.
     So three stylesheets carried private copies of values DESIGN-DECISIONS
     names at brand level, and two of them (home-lane, weekend-lane) were
     proposed for promotion by two lanes independently. Promoted here, mirrored
     into brand-foundation/calendar.tokens.json, and the local copies now
     reference these. Source lines are on the token entries, not restated here.
     `#EDF1F8` — day.css's mobile weekend chip — was NOT promoted with them: it
     appears nowhere in DESIGN-DECISIONS and has exactly one consumer. */
  --fc-home-lane:             #FAFBFE;
  --fc-weekend-lane:          #F1F5FB;
  --fc-glyph-tile-empty:      #EEF3FE;
  /* ── promoted 2026-08-06, cross-screen convergence pass ────────────────
     The empty/error state card graduated from css/where.css into SECTION 5
     as the estate's one `.fc-state`, so the two values it needs graduate with
     it. Both were named as promotion candidates in the Where lane's report
     and had no second consumer until the promotion gave them three. */
  --fc-state-overlay:         rgba(250, 251, 254, 0.66);
  /* …and two more with the leg row, promoted the same day for the same
     reason: DESIGN-DECISIONS §1 names both verbatim ("Hairlines: … #F2F4F9
     (inspector legs)" and "terminal chip #E7ECF6 / text #5C6679"), and the
     component that used them now serves the Inspector AND Day. */
  --fc-hairline-leg:          #F2F4F9;
  --fc-terminal-chip:         #E7ECF6;
  --fc-neutral-chip:          #F1F4FA;
  --fc-pill-track:            #F3F5FA;
  --fc-rail-row-weekend:      #F2F5FA;
  --fc-rail-divider:          #E9EEF7;
  --fc-rail-hover:            #F1F5FC;
  --fc-outline-quiet:         #E2E7F0;
  --fc-skeleton-cell:         #FBFCFE;
  --fc-skeleton-bar:          #E9EDF4;
  --fc-skeleton-text:         #EDF0F6;

  /* alpha convention — a two-hex-digit suffix appended to a person/category
     colour at render time (DESIGN-DECISIONS §1). js/views/month.js composes
     them; they never appear as a literal in this file or in that one. */
  --fc-alpha-chip-fill:       12;
  --fc-alpha-chip-fill-strong:14;
  --fc-alpha-chip:            18;
  --fc-alpha-icon-tile:       1a;
  --fc-alpha-wash:            1f;
  --fc-alpha-tentative-fill:  26;
  --fc-alpha-badge-tentative: 2b;
  --fc-alpha-past-fill:       2e;
  --fc-alpha-row-wash:        0d;
  --fc-alpha-dashed-border:   b3;

  /* shadows */
  --fc-shadow-card:        0 18px 44px rgba(38, 50, 84, 0.09);
  --fc-shadow-inspector:   0 20px 48px rgba(38, 50, 84, 0.22);
  --fc-shadow-bar-hover:   0 4px 10px rgba(38, 50, 84, 0.18);
  --fc-shadow-pill-active: 0 1px 3px rgba(38, 50, 84, 0.14);
  /* the empty/error state card — DESIGN-DECISIONS §6, promoted with the
     component out of css/where.css on 2026-08-06 */
  --fc-shadow-state:       0 16px 40px rgba(38, 50, 84, 0.16);
  --fc-badge-ground:       rgba(255, 255, 255, 0.26);

  /* radii */
  --fc-radius-card:   22px;
  --fc-radius-panel:  18px;
  --fc-radius-rail:   16px;
  --fc-radius-grid:   14px;
  --fc-radius-pill:   11px;
  --fc-radius-row:    10px;
  --fc-radius-bar:    10px;
  --fc-radius-barcut: 3px;
  --fc-radius-chip:   7px;
  --fc-radius-tag:    8px;
  --fc-radius-badge:  5px;
  --fc-radius-dot:    2px;

  /* sizes */
  --fc-card-width:      1320px;
  --fc-card-pad-top:    26px;
  --fc-card-pad-side:   30px;
  --fc-board-height:    720px;
  --fc-rail-width:      320px;
  --fc-bar-height:      19px;
  --fc-bar-lane-pitch:  22px;
  --fc-bar-lane-top:    28px;
  --fc-bar-pad-reserve: 6px;
  --fc-bar-inset:       4px;
  --fc-cell-pad-y:      8px;
  --fc-cell-pad-x:      9px;
  --fc-chip-gap:        4px;
  --fc-chip-pad-y:      4px;
  --fc-chip-pad-x:      7px;
  --fc-dot:             6px;
  /* TAP TARGETS — the canonical name carries the canonical value.
     RENAMED 2026-08-09. This pair used to read --fc-tap: 24px (SECTION 1) and
     --fc-tap-mobile: 44px (SECTION 1b), so the general-sounding name held the
     desktop EXCEPTION and the special-sounding name held the RULE — a shape
     that invites a new stylesheet to reach for the wrong one and silently
     halve a control a thumb has to hit. The rule is 44: BUILD-ANSWERS-2026-08-04
     D3 ("hold 44 even though AA is 24×24") and FONTS.md §4 ("Tap targets on
     mobile are 44px minimum regardless of the label size inside them").
     So --fc-tap IS 44, and the 24px desktop pointer target is now named as the
     exception it is. Values are unchanged at every call site; only the names
     moved. --fc-tap-desktop is for a control that is display:none below 430px
     or overridden to --fc-tap there — never for one a thumb can reach. */
  --fc-tap:                     44px; /* the rule — D3 / FONTS §4 */
  --fc-tap-desktop:             24px; /* the fine-pointer exception; AA 2.5.8's floor is 24 */

  /* type */
  --fc-font: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --fc-size-screen-title: 26px;
  --fc-size-section:      19px;
  --fc-size-rail-title:   14px;
  --fc-size-rail-num:     15px;
  --fc-size-day-num:      14px;
  --fc-size-body:         12.5px;
  --fc-size-qualifier:    12px;
  --fc-size-pill:         11.5px;
  --fc-size-meta:         11px;
  --fc-size-bar-label:    11px;
  --fc-size-dow:          10.5px;
  --fc-size-chip:         10.5px;
  --fc-size-chip-line2:   9.5px;
  --fc-size-badge:        9px;
  --fc-size-hol-tag:      8.5px;
  --fc-size-rail-dow:     8px;
  --fc-weight-regular:  500;
  --fc-weight-medium:   600;
  --fc-weight-semibold: 700;
  --fc-weight-bold:     800;
  --fc-track-title: -0.03em;
  --fc-track-tight: -0.02em;
  --fc-track-micro: 0.04em;
  --fc-track-caps:  0.1em;
  --fc-leading-body: 1.6;
  --fc-leading-chip: 1.3;
  --fc-leading-flat: 1;

  /* motion — state + focus transitions only (clarity register) */
  --fc-duration-state: 120ms;
  --fc-ease-standard:  cubic-bezier(0.2, 0, 0.38, 0.9);
  --fc-hover-lift:     -1px;
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 1b — MOBILE TIER (≤ 430px)

   ⚠ NOT YET MIRRORED INTO calendar.tokens.json. That file lives at
   projects/studio-tool-kit/brand-foundation/calendar.tokens.json, which is
   OUTSIDE this build's write envelope, so these are declared here and the
   token file needs the same block adding. Flagged in the build report.

   Transcribed from 'design-export-2026-08-03/Family Calendar Mobile.dc.html'
   block "// ---- Month: October ----" and "// ---- tab bar ----" (the
   2026-08-04 pack did not re-export a Mobile.dc.html — only the desktop
   Views file carried the min-width bug, so the 08-03 mobile file is still
   the reference), with type overridden by design-export-2026-08-04/FONTS.md
   §3 "Mobile" and §4 floors where the two disagree.
   ════════════════════════════════════════════════════════════════════════ */
:root {
  /* grid geometry */
  --fc-m-grid-pad-x:     12px;
  --fc-m-grid-gap:       2px;
  --fc-m-cell-min-h:     58px;
  --fc-m-cell-pad-y:     5px;
  --fc-m-cell-pad-x:     4px;
  --fc-m-radius-cell:    9px;

  /* event dots — max 3, never a '•••' collapse (BUILD-ANSWERS C2) */
  --fc-m-dot:            5px;
  --fc-m-dot-gap:        2px;
  --fc-m-radius-dot:     1.5px;   /* kid-logistics square at this size */

  /* trip bars. Pitch is DERIVED: 13px bar + 2px gap. The design ships a
     single-lane example whose reserve is 21px, which is exactly
     1 × 15 + 6 — so reserve(lanes) = lanes × 15 + 6 is the mobile twin of
     the desktop lanes × 22 + 6. Reported as a derivation, not a transcription. */
  --fc-m-bar-height:      13px;
  --fc-m-bar-lane-pitch:  15px;
  --fc-m-bar-bottom:      4px;
  --fc-m-bar-inset:       3px;
  --fc-m-bar-pad-reserve: 6px;
  --fc-m-radius-bar:      7px;

  /* bottom tab bar */
  --fc-m-tabbar-h:      76px;
  --fc-m-tab-dot:       18px;
  --fc-m-radius-tabdot: 6px;

  /* the day sheet */
  --fc-m-sheet-max-h:   78%;
  --fc-m-grab-w:        36px;
  --fc-m-grab-h:        4px;
  --fc-radius-sheet:    26px;
  --fc-radius-sheet-row: 12px;

  /* Surfaces + marks that STARTED on mobile. Two of them are estate-wide now
     and keep the `-m-` prefix only because renaming a token costs more than
     the honesty of this line: --fc-m-today-ring is aliased by Year
     (--fc-y-today-ring, SECTION 10) and read directly by desktop Month's
     today mark (SECTION 4, 2026-08-07). --fc-m-today-cell is aliased by Year.
     Flagged as rename candidates rather than renamed here — they are mirrored
     into calendar.tokens.json and the mirror is outside this write envelope. */
  --fc-m-today-cell:  rgba(79, 125, 243, 0.16);
  --fc-m-today-ring:  rgba(79, 125, 243, 0.5);
  --fc-scrim:         rgba(26, 34, 51, 0.34);
  --fc-grab-handle:   #DDE3EE;
  --fc-tab-dot-off:   #E7ECF4;

  /* type — FONTS.md §3 "Mobile" is authoritative over the .dc.html */
  --fc-m-size-title:      22px;   /* §3 Mobile: screen title 22–28 */
  --fc-m-size-nav:        14px;
  --fc-m-size-day-num:    12.5px;
  --fc-m-size-dow:        9.5px;
  --fc-m-size-hol-tag:    7.5px;  /* .dc says 7px; §4 floor is 7.5 — floor wins */
  --fc-m-size-bar-label:  9.5px;  /* §3: label drops to 9.5/700 … */
  --fc-m-size-bar-badge:  8.5px;  /* … badge UP to 8.5/800/.06em */
  --fc-m-size-tab:        10px;
  --fc-m-size-sheet-title: 17px;
  --fc-m-size-sheet-sub:   12px;
  --fc-m-size-sheet-row:   13px;
  --fc-m-size-sheet-meta:  11.5px;
  --fc-m-size-sheet-note:  11px;
  --fc-m-size-eu:          13px;  /* BUILD-ANSWERS C8: figure at the normal 13/800 */
  --fc-track-badge:      0.06em;

  /* The 44px tap floor used to be declared HERE, as --fc-tap-mobile, which put
     the estate's actual rule in the mobile tier and left the desktop exception
     holding the general name in SECTION 1. Both now sit together in SECTION 1
     as --fc-tap (44) and --fc-tap-desktop (24) — renamed 2026-08-09; see the
     comment there. Nothing on this tier declares a tap token any more. */
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 2 — PAGE CHROME
   ════════════════════════════════════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  min-height: 100dvh;
  font-family: var(--fc-font);
  color: var(--fc-text-ink);
  background: linear-gradient(155deg, var(--fc-page-a) 0%, var(--fc-page-b) 52%, var(--fc-page-c) 100%);
  background-attachment: fixed;
  /* every numeral in this product is data — align it */
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

.fc-shell {
  padding: 40px 44px 20px;
  display: flex;
  flex-direction: column;
  gap: 30px;
  align-items: flex-start;
}

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

/* skip link — first focusable thing on the page */
.fc-skip {
  position: absolute;
  left: 8px; top: -60px;
  z-index: 60;
  padding: 10px 16px;
  border-radius: var(--fc-radius-tag);
  background: var(--fc-surface-card);
  color: var(--fc-text-ink);
  font: var(--fc-weight-semibold) var(--fc-size-body) / var(--fc-leading-body) var(--fc-font);
  box-shadow: var(--fc-shadow-card);
  transition: top var(--fc-duration-state) var(--fc-ease-standard);
}
.fc-skip:focus { top: 8px; }

/* one focus treatment everywhere — 3.78:1 against white */
:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--fc-today);
  outline-offset: 2px;
  border-radius: var(--fc-radius-badge);
}

.fc-card {
  position: relative;
  width: var(--fc-card-width);
  max-width: 100%;
  padding: var(--fc-card-pad-top) var(--fc-card-pad-side) var(--fc-card-pad-side);
  background: var(--fc-surface-card);
  border-radius: var(--fc-radius-card);
  box-shadow: var(--fc-shadow-card);
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 3 — HEADER: title · qualifier · Schengen chip · view pills
   ════════════════════════════════════════════════════════════════════════ */
.fc-header {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

.fc-title {
  margin: 0;
  font-size: var(--fc-size-screen-title);
  font-weight: var(--fc-weight-bold);
  letter-spacing: var(--fc-track-title);
  line-height: var(--fc-leading-flat);
}
.fc-title .fc-title-year { color: var(--fc-text-faint); }

.fc-qualifier {
  font-size: var(--fc-size-qualifier);
  font-weight: var(--fc-weight-medium);
  color: var(--fc-text-secondary);
  padding-top: 3px;
}

/* ── Schengen chip — NO-DATA state, ruling C8 (2026-08-04) ───────────────
   PRD §6.1 puts the count in a separate authority (a Cloud Function) that
   is not built, so there is no number to show and a wrong one is worse
   than none. This replaces the previous `UNAVAILABLE` word, which was too
   loud at 13px and shoved the header layout around.

   Claude Design's ruling, verbatim: reuse the LOADING treatment, not a
   word — `EU —/90`, ground #F1F4FA (NOT the green #ECF9F2: green implies a
   real, safe count), figure `—` at 13/800 #8A94A6, `/90` at #AEB6C4, drop
   the "+N planned" line entirely, not clickable, tooltip "Schengen count
   unavailable". Geometry (radius 12, padding 7/13) is transcribed from the
   live chip in Views.dc.html L93; the 150×6 progress track it carries is
   dropped with the figure — an unfilled track reads as "0 of 90 used",
   which is a number we do not have.

   ACCESSIBILITY, stated plainly: #8A94A6 on #F1F4FA is 2.78:1 and fails AA
   at this size. It is kept because it is an explicit design ruling AND
   because the em-dash and the "/90" are marked aria-hidden with the whole
   fact carried losslessly in visually-hidden prose plus the title — so the
   sub-AA glyph is never the sole carrier. The "EU" label IS substituted to
   --fc-text-secondary (6.2:1), because that one is real text. */
.fc-eu-chip {
  display: inline-flex;
  align-items: baseline;
  /* no gap: the design sets "EU {n}" as ONE run with only "/90" recoloured,
     so the space belongs before the figure and nowhere else */
  gap: 0;
  padding: 7px 13px;
  border-radius: 12px;
  background: var(--fc-neutral-chip);
}
.fc-eu-chip__label,
.fc-eu-chip__figure,
.fc-eu-chip__of {
  font-size: var(--fc-m-size-eu);
  font-weight: var(--fc-weight-bold);
  line-height: var(--fc-leading-flat);
}
.fc-eu-chip__label  { color: var(--fc-text-secondary); }
.fc-eu-chip__figure { color: var(--fc-text-muted); margin-left: 5px; }
.fc-eu-chip__of     { color: var(--fc-text-dow); }

.fc-pills {
  margin-left: auto;
  display: flex;
  gap: 3px;
  padding: 3px;
  background: var(--fc-pill-track);
  border-radius: var(--fc-radius-pill);
}
.fc-pill {
  appearance: none;
  border: 0;
  font-family: inherit;
  font-size: var(--fc-size-pill);
  font-weight: var(--fc-weight-medium);
  padding: 5px 13px;
  min-height: var(--fc-tap-desktop);
  border-radius: 9px;
  background: transparent;
  color: var(--fc-text-secondary);
  cursor: pointer;
  transition: background var(--fc-duration-state) var(--fc-ease-standard),
              color var(--fc-duration-state) var(--fc-ease-standard);
}
.fc-pill:hover { background: var(--fc-surface-card); color: var(--fc-text-ink); box-shadow: var(--fc-shadow-pill-active); }
.fc-pill[aria-current='page'] {
  font-weight: var(--fc-weight-semibold);
  background: var(--fc-surface-card);
  color: var(--fc-text-ink);
  box-shadow: var(--fc-shadow-pill-active);
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 4 — MONTH BOARD: week rail (left) + month grid (right)
   ════════════════════════════════════════════════════════════════════════ */
.fc-board {
  display: flex;
  gap: 24px;
  align-items: stretch;
  height: var(--fc-board-height);
}

/* ── the live week rail ── */
.fc-rail {
  flex: 0 0 var(--fc-rail-width);
  /* min-width:0 is the actual fix. Flex items default to min-width:auto, so the
     rail's content floor pushes it past its 320px basis regardless of
     box-sizing — it measured 347 against a specified 320, stealing ~4px from
     every column. Claude Design confirmed this is a bug in their own
     Views.dc.html too (BUILD-ANSWERS B2) and patched it their side. */
  min-width: 0;
  /* border-box, or the 15px side padding is ADDED to the 320px basis and the
     rail renders 350 — stealing ~4px from every one of the seven columns and
     truncating chip text the design shows in full. The design's own container
     carries box-sizing:border-box for the same reason. Caught by /visual-qa
     2026-08-04 measuring 347 against a specified 320. */
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  padding: 14px 15px;
  background: var(--fc-surface-rail);
  border-radius: var(--fc-radius-rail);
}
.fc-rail__head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 9px;
}
.fc-rail__title {
  font-size: var(--fc-size-rail-title);
  font-weight: var(--fc-weight-bold);
  letter-spacing: var(--fc-track-tight);
}
.fc-rail__range {
  font-size: var(--fc-size-pill);
  font-weight: var(--fc-weight-medium);
  color: var(--fc-text-secondary);
}
.fc-rail__list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.fc-rail__day {
  display: flex;
  gap: 12px;
  align-items: stretch;
  width: 100%;
  padding: 10px 0;
  border: 0;
  border-top: 1px solid var(--fc-rail-divider);
  border-radius: var(--fc-radius-row);
  background: transparent;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.fc-rail__day:first-child { border-top: 0; }
.fc-rail__day:hover { background: var(--fc-rail-hover); }
.fc-rail__day--weekend { background: var(--fc-rail-row-weekend); }
.fc-rail__day--holiday { background: var(--fc-holiday-cell); }
.fc-rail__date {
  flex: 0 0 32px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.fc-rail__num {
  font-size: var(--fc-size-rail-num);
  font-weight: var(--fc-weight-bold);
  letter-spacing: var(--fc-track-tight);
  line-height: var(--fc-leading-flat);
  color: var(--fc-text-ink);
}
/* design value: --fc-text-weekend-num (3.19:1 on the weekend ground). AA. */
.fc-rail__day--weekend .fc-rail__num { color: var(--fc-text-secondary); }
.fc-rail__day--today  .fc-rail__num { color: var(--fc-today-numeral); }
.fc-rail__day--past   .fc-rail__num,
.fc-rail__day--past   .fc-rail__dow { opacity: 0.5; }
.fc-rail__dow {
  font-size: var(--fc-size-rail-dow);
  font-weight: var(--fc-weight-semibold);
  letter-spacing: var(--fc-track-caps);
  color: var(--fc-text-secondary);
  margin-top: 3px;
}

/* the trip spine — a continuous vertical bar down the days of one trip */
.fc-rail__spine {
  width: 6px;
  flex: 0 0 6px;
  align-self: stretch;
  border-radius: 0;
  background: transparent;
}
.fc-rail__spine--on { background: var(--bar-c); }
.fc-rail__spine--on.fc-rail__spine--tentative {
  background: repeating-linear-gradient(180deg, var(--bar-c) 0 6px, transparent 6px 11px);
}
.fc-rail__spine--on            { margin: -10px 0; }
.fc-rail__spine--first         { margin-top: 2px; border-radius: 3px 3px 0 0; }
.fc-rail__spine--last          { margin-bottom: 2px; border-radius: 0 0 3px 3px; }
.fc-rail__spine--first.fc-rail__spine--last { border-radius: 3px; }
.fc-rail__day--past .fc-rail__spine--on { opacity: 0.45; }

.fc-rail__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  justify-content: center;
}
.fc-rail__hol {
  align-self: flex-start;
  font-size: var(--fc-size-dow);
  font-weight: var(--fc-weight-semibold);
  /* design value: --fc-holiday-chip-text (3.59:1). Substituted for AA. */
  color: var(--fc-eu-ink);
  background: var(--fc-holiday-chip);
  border-radius: var(--fc-radius-chip);
  padding: 3px 9px;
}
.fc-rail__event {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 7px 10px;
  border-radius: 0 9px 9px 0;
  border-left: 3px solid var(--bar-c);
  background: var(--bar-fill);
}
.fc-rail__day--past .fc-rail__event { opacity: 0.55; }
.fc-rail__event-top { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.fc-rail__event-title {
  font-size: var(--fc-size-pill);
  font-weight: var(--fc-weight-semibold);
  color: var(--fc-text-ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fc-rail__event-meta {
  margin-left: auto;
  flex-shrink: 0;
  font-size: var(--fc-size-meta);
  font-weight: var(--fc-weight-semibold);
  /* design value: --fc-text-muted (2.8:1). Substituted for AA. */
  color: var(--fc-text-secondary);
}
.fc-rail__event-sub {
  font-size: 10px;
  font-weight: var(--fc-weight-regular);
  /* design value: --fc-text-muted-2 (2.6:1). Substituted for AA. */
  color: var(--fc-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Layout only — the chip itself is `.fc-empty-chip` in SECTION 5. This rule
   used to carry `nothing on` at weight 500 in muted text, one of four
   different treatments of one string across the product. */
.fc-rail__empty { margin-top: 3px; }


/* ── the month grid ── */
.fc-grid-wrap {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.fc-dow {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
  margin-bottom: 8px;
}
.fc-dow__cell {
  text-align: center;
  font-size: var(--fc-size-dow);
  font-weight: var(--fc-weight-semibold);
  letter-spacing: var(--fc-track-micro);
  padding: 5px 0;
  border-radius: var(--fc-radius-tag);
  background: var(--fc-weekday-chip);
  /* design value: --fc-text-muted-3 (2.38:1). Substituted for AA. */
  color: var(--fc-text-secondary);
}
.fc-dow__cell--weekend { background: var(--fc-weekend-chip); }

.fc-grid {
  flex: 1;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--fc-hairline-card);
  border-radius: var(--fc-radius-grid);
  overflow: hidden;
}

.fc-week {
  flex: 1 1 0;
  min-height: 0;
  position: relative;
  border-top: 1px solid var(--fc-hairline-row);
  cursor: pointer;
}
.fc-week:hover { background: var(--fc-surface-rail); }
/* The design specifies `inset 0 0 0 2px #4F7DF3` on the row. An inset shadow
   paints beneath the row's own children, and the seven day cells are opaque
   and fill the row edge to edge, so in practice it does not show — verified
   in the render, where only fragments of it survived. The ring is therefore
   drawn as its own non-interactive overlay above the cells: identical 2px,
   identical colour, identical radius, but deterministically visible. */
.fc-week__ring {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  border: 2px solid var(--fc-today);
  border-radius: var(--fc-radius-row);
}
.fc-week__days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  height: 100%;
}

.fc-cell {
  position: relative;
  overflow: hidden;
  padding: var(--fc-cell-pad-y) var(--fc-cell-pad-x);
  display: flex;
  flex-direction: column;
  gap: var(--fc-chip-gap);
  border: 0;
  border-right: 1px solid var(--fc-cell-vertical);
  background: var(--fc-surface-card);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.fc-cell:last-child { border-right: 0; }
.fc-cell:hover { filter: brightness(0.98); }
.fc-cell--weekend { background: var(--fc-weekend-cell); }
.fc-cell--holiday { background: var(--fc-holiday-cell); }   /* holiday beats weekend */
.fc-cell--outside { background: var(--fc-outside-cell); }
.fc-cell--past    { opacity: 0.55; }
/* ── TODAY, RESTORED AS A MARK — 2026-08-07 ────────────────────────────────
   The history, because it matters and it reverses twice. Views.dc.html L809
   has no today branch in the cell ground, so this file shipped weight-only.
   A #F7FAFF ground was then added and REMOVED on 2026-08-04 by the
   close-strong council, for two stated reasons: #F7FAFF was the design's
   SELECTED-day wash elsewhere in the product, and — declared last at equal
   specificity — it overrode the weekend, holiday and out-of-month grounds, so
   a weekend-today broke the column banding.

   Both reasons have since expired, and weight-only measured as a real defect.

     • The collision is gone. The selected day is `.fc-cell--selected::after`,
       a 2px ink border, and has been since 2026-08-06. #F7FAFF is nobody
       else's wash on this surface any more.
     • Weight-only does not work. Measured at 1440 with focus on 20 Aug: today
       (2 Aug, a Sunday) computed rgb(244,247,252), byte-identical to its
       non-today weekend sibling 9 Aug, and both numerals computed
       rgb(92,102,121). The ONLY difference between today and an ordinary
       weekend on the surface people use to find today was font-weight 800 vs
       600 — while the selected day shouted with a 2px ink ring. The hierarchy
       was inverted.

   The mark now matches the two surfaces that already had one — Year
   (`.fc-year__cell--today`) and mobile Month (`.fc-m-cell--today`): a ground,
   a 1.5px inset ring, and the numeral in --fc-today-numeral. Bold weight
   stays, so today is distinguishable on three channels, none of them colour
   alone.

   ONE GROUND ACROSS THE ESTATE — corrected 2026-08-08 by the visual gate.
   The first pass reached for --fc-today-cell (#F7FAFF) here while the other
   five cell surfaces all use --fc-m-today-cell (rgba(79,125,243,.16)), and
   argued that the ring alone could carry it. Measured, it cannot: #F7FAFF is
   1.05:1 against the white cell and 1.03:1 against the #F4F7FC weekend band,
   and the fixture's today (2 Aug) IS a Sunday — so the today wash rendered
   LIGHTER than the weekend banding it was overriding, while mobile Month at
   the same date drew a plainly filled blue cell. Same view, two widths, two
   different marks, which is the divergence this section spent a batch
   removing. The ground is --fc-m-today-cell now, exactly as it is on Year,
   mobile Month, the Day column header and the mobile day chip.

   Desktop Where keeps --fc-today-cell for its seven-column today WASH, and
   that stays: a .16 tint run down a whole column is a different quantity of
   colour from a .16 tint on one discrete cell.

   `:not(--selected)` mirrors mobile Month exactly: when today IS the selected
   day the ink ring is the specific mark and owns the perimeter, rather than
   two rings stacking concentrically. Declared after --weekend / --holiday /
   --outside / --past so it wins the tie on source order, which is deliberate:
   today beats the banding, and now the ground says so on its own. */
.fc-cell--today:not(.fc-cell--selected) {
  background: var(--fc-m-today-cell);
  box-shadow: inset 0 0 0 1.5px var(--fc-m-today-ring);
}
.fc-cell--past:hover { opacity: 1; }                        /* hover restores full colour */
/* THE SELECTED DAY — DESIGN-DECISIONS §1 "inset 0 0 0 2px #1A2233 (ink
   outline, not blue, so it doesn't read as 'today')".
   Drawn as its own overlay rather than the cell's inset shadow. The shadow
   computed correctly and rendered as two vertical rules with no horizontals:
   the selected day always sits inside the selected week, and `.fc-week__ring`
   (2px --fc-today, z-index 3, inset 0 on the row) covers the cell's top and
   bottom edges, while the trip bars (z-index 2) cut the verticals. Measured at
   1440px, 13 Oct selected, 2026-08-06.
   The two marks now have an ORDER instead of being stacked concentrically at
   equal weight — §1 gives both rings and never says they coexist on one cell.
   The day outline is the specific mark and owns its own perimeter at z-index
   4; the blue week ring is the ambient row frame and reads as passing behind
   it. Same 2px, same ink, same colour for the ring: only the z-order is new.
   `.fc-cell` is position:relative with z-index:auto, so it creates no stacking
   context and this pseudo-element really does compete with the ring. A
   `z-index` on the CELL itself would raise it too — and would hide the trip
   bars behind the selected day, which is why the mark is a pseudo-element and
   not the cell. */
.fc-cell--selected::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  border: 2px solid var(--fc-text-ink);
}

.fc-cell__head { display: flex; align-items: baseline; gap: 5px; }
.fc-cell__num {
  font-size: var(--fc-size-day-num);
  font-weight: var(--fc-weight-medium);
  line-height: var(--fc-leading-flat);
  color: var(--fc-text-ink);
}
/* design value: --fc-text-weekend-num (3.19:1). Substituted for AA. */
.fc-cell--weekend .fc-cell__num { color: var(--fc-text-secondary); }
/* NOT substituted — see the build report: raising this to an AA-passing ink
   would flatten the in-month / out-of-month hierarchy. The full date is
   carried losslessly on the cell's aria-label instead. */
.fc-cell--outside .fc-cell__num { color: var(--fc-text-outside-num); }
/* Weight AND colour, matching mobile Month and Year exactly. Weight alone was
   the whole mark until 2026-08-07 and measured indistinguishable — see the
   --today ground rule above. #2F5BD0 on #F7FAFF computes 5.69:1, AA.
   THE NUMERAL IS THE ACCESSIBLE CARRIER of this state, not the ring: the
   1.5px rgba(79,125,243,.5) ring composites to #A3BCF9 and is 1.81:1 against
   the cell ground, below the 3:1 non-text floor. That ring is Year's and
   mobile Month's shipped treatment and is left identical rather than
   strengthened on one surface — divergence is the defect the convergence pass
   existed to kill. It is decoration on top of three carriers that do pass:
   the AA numeral colour, the 800 weight, and the cell's aria-label. Named in
   the build report as an estate-wide residual, not a Month one. Declared after
   --weekend and --outside so today's numeral wins on a weekend today, which is
   the exact case that was broken. */
.fc-cell--today   .fc-cell__num {
  font-weight: var(--fc-weight-bold);
  color: var(--fc-today-numeral);
}

.fc-cell__hol {
  font-size: var(--fc-size-hol-tag);
  font-weight: var(--fc-weight-medium);
  letter-spacing: var(--fc-track-micro);
  /* design value: --fc-eu-micro (2.13:1 on the holiday ground). AA. */
  color: var(--fc-eu-ink);
}

/* the reserved band: lanes × 22 + 6 px, so a chip never sits under a bar */
.fc-cell__reserve { flex: 0 0 auto; }

.fc-chip {
  display: flex;
  flex: 0 0 auto;
  align-items: flex-start;
  gap: 6px;
  /* NO min-height here. A chip's height is its content: 10.5px text at 1.3
     line-height (13.65) + 4px padding top and bottom = 21.65 ≈ 22px, which is
     what the design measures. Pinning it to the tap-target token made every
     chip 24px, and with a 2-lane trip reserve that 2px is the difference
     between fitting TWO chips in the 52.3px band and fitting one — the exact
     "one fewer chip than the design" defect. Tap sizing is a MOBILE rule
     (FONTS.md §4: 44px minimum) and belongs in the mobile block, not here.
     Claude Design BUILD-ANSWERS B1, 2026-08-04. */
  padding: var(--fc-chip-pad-y) var(--fc-chip-pad-x);
  border: 0;
  border-radius: var(--fc-radius-chip);
  background: var(--bar-fill);
  font-family: inherit;
  font-size: var(--fc-size-chip);
  line-height: var(--fc-leading-chip);
  color: var(--fc-text-past-label);
  text-align: left;
  overflow: hidden;
  cursor: pointer;
}
.fc-chip:hover { filter: brightness(0.95); }
.fc-chip__dot {
  width: var(--fc-dot);
  height: var(--fc-dot);
  margin-top: 4px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--bar-c);
}
/* the ONLY thing separating Cosmo's trips from Cosmo's logistics is shape */
.fc-chip__dot--square { border-radius: var(--fc-radius-dot); }
/* Birthday takes the confirmed icon-set glyph rather than a third coloured
   circle, so amber is not the only thing separating a birthday from an
   appointment. Text character, per UI-RULES §8 — no icon font, no emoji. */
.fc-chip__glyph {
  flex-shrink: 0;
  width: var(--fc-dot);
  margin-top: 1px;
  font-size: var(--fc-size-badge);
  line-height: var(--fc-leading-chip);
  color: var(--bar-c);
}
.fc-chip__text { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.fc-chip__main { font-weight: var(--fc-weight-semibold); min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fc-chip--roomy .fc-chip__main { white-space: normal; overflow: visible; overflow-wrap: anywhere; }
.fc-chip__line2 {
  font-size: var(--fc-size-chip-line2);
  font-weight: var(--fc-weight-medium);
  /* design value: --fc-text-muted (2.8:1). Substituted for AA. */
  color: var(--fc-text-secondary);
  white-space: nowrap;
}
.fc-chip__meta {
  margin-left: auto;
  flex-shrink: 0;
  font-weight: var(--fc-weight-regular);
  /* design value: --fc-text-muted-2 (2.6:1). Substituted for AA. */
  color: var(--fc-text-secondary);
}

.fc-more {
  font-size: var(--fc-size-chip-line2);
  font-weight: var(--fc-weight-semibold);
  /* design value: --fc-text-muted (2.8:1). Substituted for AA. */
  color: var(--fc-text-secondary);
  padding: 1px 4px;
  border: 0;
  background: transparent;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

/* ── the trip bar. ONE continuous bar per trip, labelled once. ──
   Geometry from 'Family Calendar Views.dc.html' V2:
     top: 28 + lane × 22 · height 19 · left calc(L% + 4px) · width calc(W% - 8px)
   L and W come from engine.js's segment (startCol, span) — never recomputed. */
.fc-bar {
  position: absolute;
  top: calc(var(--fc-bar-lane-top) + (var(--lane) * var(--fc-bar-lane-pitch)));
  left: calc(var(--seg-left) + var(--fc-bar-inset));
  width: calc(var(--seg-width) - (2 * var(--fc-bar-inset)));
  height: var(--fc-bar-height);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--fc-radius-bar);
  overflow: hidden;
  white-space: nowrap;
  font-family: inherit;
  font-size: var(--fc-size-bar-label);
  font-weight: var(--fc-weight-regular);
  z-index: 2;
  cursor: pointer;
  background: var(--bar-c);
  /* --bar-ink is set per bar by the view (schema.inkFor): whichever of ink or
     white clears WCAG AA on THIS fill. White alone failed on the light hues —
     Oli #FB6A2E at 2.92:1, Laura #EC4899 at 3.53:1 — and the fills are Oli's
     signed-off design decision, so the LABEL moves and the palette does not.
     The fallback keeps the old behaviour for any bar built before this. */
  color: var(--bar-ink, var(--fc-text-on-fill));
  transition: transform var(--fc-duration-state) var(--fc-ease-standard),
              box-shadow var(--fc-duration-state) var(--fc-ease-standard);
}
/* a cut edge means "this trip continues into the next/previous week row" */
.fc-bar--cont-left  { border-top-left-radius: var(--fc-radius-barcut);  border-bottom-left-radius: var(--fc-radius-barcut); }
.fc-bar--cont-right { border-top-right-radius: var(--fc-radius-barcut); border-bottom-right-radius: var(--fc-radius-barcut); }

.fc-bar:hover { transform: translateY(var(--fc-hover-lift)); box-shadow: var(--fc-shadow-bar-hover); filter: none; opacity: 1; }

/* tentative — four simultaneous signals: tint fill, dashed border,
   label in the colour (not white), and a trailing ' ?' on the label. */
.fc-bar--tentative {
  background: var(--bar-tentative-fill);
  border: 1.5px dashed var(--bar-dashed);
  color: var(--bar-c);
}
/* past — a tint plus a dark neutral label. Never blanket opacity, which
   makes the label unreadable (DESIGN-DECISIONS §1). */
.fc-bar--past {
  background: var(--bar-past-fill);
  color: var(--fc-text-past-label);
}

.fc-bar__badge {
  flex-shrink: 0;
  padding: 1px 5px;
  border-radius: var(--fc-radius-badge);
  font-size: var(--fc-size-badge);
  font-weight: var(--fc-weight-semibold);
  background: var(--fc-badge-ground);
}
.fc-bar--tentative .fc-bar__badge,
.fc-bar--past .fc-bar__badge { background: var(--bar-badge-ground); color: var(--bar-c); }
.fc-bar--past .fc-bar__badge { color: var(--fc-text-past-label); }
.fc-bar__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


/* ════════════════════════════════════════════════════════════════════════
   SECTION 5 — STATES: loading · empty · error · not-built
   Chrome always renders; only the data area changes (DESIGN-DECISIONS §6).

   ONE COMPONENT, promoted here 2026-08-06 in the cross-screen convergence
   pass. Two existed. Year, Day and the shell's not-built placeholder drew a
   left-aligned, dashed, full-board panel; the Where board drew §6's actual
   specified pattern — a centred 430px card, radius 18, a 38px glyph tile,
   title, body and two buttons, over the still-rendered grid at a .66 ground.
   §6 specifies one pattern for the whole estate ("Empty / loading / error —
   one shared pattern"), and the Where lane is the one that built it, so the
   Where lane's is the one that survives. The divergent rule is gone.

   Two layers, because the same card is used two ways:
     `.fc-state-layer--overlay` — over a board that is still rendered (Where's
        empty and error). Error NEVER blanks the calendar.
     `.fc-state-layer--board`   — in the flow, where there is no board to keep
        (Year's and Day's "cannot be drawn", the shell's not-built).
   ════════════════════════════════════════════════════════════════════════ */
.fc-state-layer {
  display: grid;
  place-items: center;
  padding: 16px;
}
.fc-state-layer--board { min-height: var(--fc-board-height); }
.fc-state-layer--overlay {
  position: absolute;
  inset: 0;
  background: var(--fc-state-overlay);
  border-radius: var(--fc-radius-grid);
  /* Above the day cells and any positioned band. Without this the layer takes
     `z-index: auto` and the positioned cells paint straight over it: the
     card's white ground rendered, and its title and body were buried under
     three lanes of `Home`. It measured perfectly and was only visible in the
     pixels. Verified at 1440px, 2026-08-06. */
  z-index: 5;
}
.fc-state {
  width: min(var(--fc-state-w), 100%);
  padding: 24px 26px;
  border-radius: var(--fc-radius-panel);
  background: var(--fc-surface-card);
  box-shadow: var(--fc-shadow-state);
  text-align: center;
  /* the card's own scale — component-scoped, not brand tokens */
  --fc-state-w: 430px;      /* DESIGN-DECISIONS §6 "card is 430px" */
  --fc-state-tile: 38px;    /* …"38px glyph tile (radius 12)" */
  /* …and the radius of that tile, which is the SAME spec line and was being
     served by --fc-radius-grid — the 14px grid radius, measured at 14 by the
     visual gate against a spec that says 12. There is no 12px in the estate
     radius ramp (22/18/16/14/11/10/8/5), so it is declared here on the
     component root rather than invented as a tenth global step. Promotion
     candidate: if a second component ever wants 12, that is the moment it
     becomes --fc-radius-tile, not now. */
  --fc-state-tile-r: 12px;  /* …"38px glyph tile (radius 12)" */
  --fc-state-title: 15.5px; /* …"title 15.5/800/-0.02em" */
}
.fc-state__glyph {
  display: grid;
  place-items: center;
  width: var(--fc-state-tile);
  height: var(--fc-state-tile);
  margin: 0 auto 12px;
  border-radius: var(--fc-state-tile-r);
  font: var(--fc-weight-bold) 17px / var(--fc-leading-flat) var(--fc-font);
}
/* §6: "empty ＋ on #EEF3FE in #4F7DF3; error ! on #FFF0F3 in #E5123F" */
.fc-state__glyph--empty { background: var(--fc-glyph-tile-empty); color: var(--fc-today); }
.fc-state__glyph--error { background: var(--fc-breach-ground);    color: var(--fc-breach); }
.fc-state__title {
  margin: 0 0 6px;
  font: var(--fc-weight-bold) var(--fc-state-title) / 1.25 var(--fc-font);
  letter-spacing: var(--fc-track-tight);
  color: var(--fc-text-ink);
}
.fc-state__body {
  margin: 0;
  font: var(--fc-weight-medium) var(--fc-size-qualifier) / var(--fc-leading-body) var(--fc-font);
  color: var(--fc-text-muted);
  text-wrap: pretty;
}
.fc-state__actions {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 16px;
  flex-wrap: wrap;
}
.fc-state__btn {
  min-height: var(--fc-tap-desktop);
  padding: 8px 14px;
  border-radius: var(--fc-radius-tag);
  border: 1px solid transparent;
  font: var(--fc-weight-semibold) var(--fc-size-meta) / var(--fc-leading-flat) var(--fc-font);
  cursor: pointer;
  transition: background 160ms ease-out, border-color 160ms ease-out; /* TODO(motion-tokens) */
}
/* Primary is ink in BOTH empty and error — retry is not destructive (§6). */
.fc-state__btn--ink { background: var(--fc-text-ink); color: var(--fc-text-on-fill); }
.fc-state__btn--ink:hover { background: var(--fc-text-ink-2); }
.fc-state__btn--quiet {
  background: var(--fc-surface-card);
  border-color: var(--fc-outline-quiet);
  color: var(--fc-text-ink);
  font-weight: var(--fc-weight-medium);
}
.fc-state__btn--quiet:hover { background: var(--fc-rail-hover); }
/* UNAVAILABLE, not broken. An action whose handler does not exist yet renders
   at the quiet outline with muted ink and no hover response, so it reads as a
   control that cannot act rather than one that failed. The reason why always
   sits next to it (below); a disabled control with no explanation is the thing
   this avoids, not the thing it is. Declared after --ink and --quiet so it
   wins on either.

   FAR ENOUGH APART TO TELL APART — 2026-08-08, the visual gate. The first pass
   left the dead button and the live one at the same 29px box, the same
   `1px solid #E2E7F0`, `opacity: 1` and only the label ink differing, and
   measured them side by side on Where's empty card as near-identical. Two
   changes, both cheap:
     · `not-allowed`, not `default` — the pointer is the one channel that
       reports availability before the click;
     · the label drops from --fc-text-secondary (#5C6679, 6.55:1) to
       --fc-text-muted (#8A94A6, 3.32:1). That is deliberately BELOW AA and it
       is the one place in this estate where that is right: WCAG 2.2 1.4.3
       exempts inactive components by name, and the whole job of this rule is
       to say "not available". The live sibling now wins on weight as well as
       colour, and the on-screen reason line carries the same information at
       --fc-text-secondary for anyone the contrast drop loses. */
.fc-state__btn[disabled] {
  background: var(--fc-surface-card);
  border-color: var(--fc-outline-quiet);
  color: var(--fc-text-muted);
  font-weight: var(--fc-weight-medium);
  cursor: not-allowed;
}
.fc-state__btn[disabled]:hover { background: var(--fc-surface-card); }
/* The on-screen half of an unavailable action. #fc-status is clipped to 1×1,
   so a message sent only there is invisible to the person who clicked. */
.fc-state__reason {
  margin: 10px 0 0;
  font: var(--fc-weight-medium) var(--fc-size-meta) / var(--fc-leading-body) var(--fc-font);
  color: var(--fc-text-secondary);
  text-wrap: pretty;
}
/* THE STATE CARD'S BUTTONS ON MOBILE — 44px, not 29. Measured 2026-08-08 at
   29px: `min-height: 24px` (the fine-pointer target) plus 8/14 padding and an
   11px label, which clears WCAG 2.2 AA 2.5.8's 24×24 and is still short of
   FONTS.md §4's own numbered rule — "Tap targets on mobile are 44px minimum
   regardless of the label size inside them" — restated by BUILD-ANSWERS-2026-08-04
   D3, which holds 44 *knowing* AA is 24. A numbered source in the pack outranks
   the built artefact, so the artefact moves. These are the ONLY controls on an
   empty or error card, one of them the single live way out of it, and the card
   is centred at 430px on a 390px screen — the worst place in the product to
   hand someone a 29px target. min-height only: the label, padding, radius and
   colour are untouched, so the desktop card is byte-identical. Batch 1.3 item 4. */
@media (max-width: 430px) {
  .fc-state__btn { min-height: var(--fc-tap); }
}

/* ── the LOADING… pill ─────────────────────────────────────────────────────
   §6: "no spinner. `LOADING…` pill in the header row (10/800/.1em, #8A94A6 on
   #F1F4FA, radius 8, padding 5/10)". The header is the frozen shell's and no
   view may write it (contract §2), so each screen puts the pill at the head of
   its own first row — same reading order, same treatment. Built by
   js/views/state-card.js.

   GRADUATED from css/where.css 2026-08-07, where it was
   `.fc-where__loading-pill`. Year, Month and Day all needed the identical pill
   the moment they grew a loading state, and one spec line transcribed four
   times is how a shared component becomes four. Its own 10px caps size is
   declared HERE, on the component, not in the global tier: it is this pill's
   scale and nothing else reads it. */
.fc-loading-pill {
  --fc-loading-pill-size: 10px;   /* §6 "10/800/.1em" */
  display: inline-block;
  padding: 5px 10px;
  border-radius: var(--fc-radius-tag);
  background: var(--fc-neutral-chip);
  color: var(--fc-text-muted);
  font: var(--fc-weight-bold) var(--fc-loading-pill-size) / var(--fc-leading-flat) var(--fc-font);
  letter-spacing: var(--fc-track-caps);
}

/* ── the empty DAY, as opposed to the empty BOARD ──────────────────────────
   DESIGN-DECISIONS §5: "Empty day copy is `Nothing on` (neutral chip), not
   'No events'". One chip, one string, converged 2026-08-06 — the product
   shipped `nothing scheduled` (Day week list + mobile timeline, plain muted
   text), `nothing on` (Month rail, weight 500), `Nothing on` (day sheet) and
   `Nothing on this day` (Inspector). The string itself is owned by
   js/app.js's FamCalApp.COPY.EMPTY_DAY; this is its one treatment.
   Distinct from `.fc-state` above: that is a whole SCREEN with no data, this
   is one row in a screen that has plenty. */
.fc-empty-chip {
  display: inline-block;
  padding: 4px 9px;
  border-radius: var(--fc-radius-chip);
  background: var(--fc-neutral-chip);
  color: var(--fc-text-secondary);
  font-size: var(--fc-size-meta);
  font-weight: var(--fc-weight-semibold);
  line-height: var(--fc-leading-flat);
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 5b — LEG ROWS (flight · accommodation · trip · item)

   DESIGN-DECISIONS §3's "Day-view / inspector flight-leg field order", once,
   for both screens. Lifted VERBATIM out of css/inspector.css SECTION 6 on
   2026-08-06 — same elements, same order, same values — and renamed from
   `fc-insp__leg*` because a Day row carrying an `fc-insp` class would be the
   same fragmentation with a tidier diff. js/views/leg.js builds it.

   The gate found one flight (`FR 8102`) rendering three ways: this anatomy in
   the Inspector, one run-together line in desktop Day, and a solid graphite
   full-bleed block in mobile Day. The Inspector's was the one §3 describes, so
   it is the one that survived. Nothing here is new.

   Its four numbers are COMPONENT-SCOPED on `.fc-leg`, not at :root — they are
   this component's proportions, not brand values, and the standing ruling is
   that only brand-level names reach :root and the token file.
   ════════════════════════════════════════════════════════════════════════ */
.fc-leg {
  --fc-leg-tile:          22px;    /* §3 field order 1: "icon tile (22px…)" */
  --fc-leg-detail-indent: 31px;    /* …5: "indented 31px to align under the title" */
  --fc-leg-detail-arrow:  26px;    /* …5: the `1fr 26px 1fr` middle column */
  --fc-leg-size-code:     13px;    /* §2: airport code 13/800, time 13/700 */
  --fc-leg-size-airport:  10px;    /* §2: airport name 10/500 */

  display: block;
  width: 100%;
  margin-top: 14px;
  padding: 14px 8px 0;
  border: 0;
  border-top: 1px solid var(--fc-hairline-leg);
  border-radius: var(--fc-radius-row);
  background: transparent;
  font-family: inherit;
  text-align: left;
  color: inherit;
  transition: background 160ms ease-out, transform 160ms ease-out; /* TODO(motion-tokens) */
}
/* an interactive leg opens another object; a static one does not */
button.fc-leg { cursor: pointer; }
button.fc-leg:hover { background: var(--fc-surface-rail); transform: translateY(var(--fc-hover-lift)); }
button.fc-leg:active { transform: none; }

.fc-leg__top {
  display: grid;
  grid-template-columns: var(--fc-leg-tile) minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 9px;
}
/* 22px icon tile, radius 7, ground `<colour>1a` — §3 field order 1. The two
   custom properties are set per instance by the CALLER, which owns the
   palette; js/views/leg.js composes no colour of its own. */
.fc-leg__tile {
  inline-size: var(--fc-leg-tile);
  block-size: var(--fc-leg-tile);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--fc-radius-chip);
  background: var(--fc-leg-tile-ground, var(--fc-neutral-chip));
  color: var(--fc-leg-tile-ink, var(--fc-text-secondary));
  font-size: 11px;
  line-height: 1;
}
.fc-leg__title {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: var(--fc-size-body);         /* 12.5px */
  font-weight: var(--fc-weight-semibold);
  color: var(--fc-text-ink);
}
.fc-leg__name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* flight-number chip — §3 field order 3 */
.fc-leg__num {
  flex: 0 0 auto;
  padding: 3px 7px;
  border-radius: var(--fc-radius-badge);
  background: var(--fc-weekend-chip);
  color: var(--fc-text-secondary);
  font-size: var(--fc-size-chip-line2); /* 9.5px */
  font-weight: var(--fc-weight-semibold);
  letter-spacing: 0.03em;
}
/* duration / N nights, right-aligned — §3 field order 4 */
.fc-leg__meta {
  font-size: var(--fc-size-meta);         /* 11px */
  font-weight: var(--fc-weight-semibold);
  /* design value: --fc-text-muted #8A94A6 (3.06:1). Substituted for AA. */
  color: var(--fc-text-secondary);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;     /* `2h 25m` / `3 nights` line up */
}
.fc-leg__sub {
  margin: 5px 0 0;
  padding-left: calc(var(--fc-leg-tile) + 9px);
  font-size: var(--fc-size-meta);
  font-weight: var(--fc-weight-regular);
  /* design value: --fc-text-muted-2 #98A1AE (2.54:1). Substituted for AA. */
  color: var(--fc-text-secondary);
  text-wrap: pretty;
}

/* ── the flight detail panel — §3 field order 5 ────────────────────────────
   Three columns at EVERY width. Measured 2026-08-06: the narrowest surface is
   mobile Day at 284px (390px viewport), which leaves ~117px a side after the
   padding and the arrow — a 13px code + 13px time is ~75px of that, the 10px
   airport name already ellipses, and the 9px terminal chip sits under them.
   Desktop Day is 518px, WIDER than the Inspector's 320px panel. */
.fc-leg__detail {
  margin: 10px 0 0 var(--fc-leg-detail-indent);
  padding: 11px 12px;
  border-radius: var(--fc-radius-row);
  background: var(--fc-surface-rail);     /* #F7F9FD */
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--fc-leg-detail-arrow) minmax(0, 1fr);
  /* START, not center. The two sides are unequal-height whenever only one
     airport carries a terminal chip (STN has none, MAD has T1) — centring then
     pushed the shorter side down and the two airport codes lost their shared
     baseline by 10px on all three surfaces at once: Day desktop (STN 492 /
     MAD 482), the Inspector (301 / 291) and mobile Day (488 / 498). The
     designed panel aligns them at the top (exports/inspector-flight.png:
     `LGW 07:10` and `MAD 10:35` on one baseline, `North` and `T1` hanging at
     different depths below). One property, three surfaces. */
  align-items: start;
  column-gap: 4px;
}
.fc-leg__side { min-width: 0; }
.fc-leg__side--arr { text-align: left; }
.fc-leg__code-row {
  display: flex;
  align-items: baseline;
  gap: 7px;
  min-width: 0;
}
.fc-leg__code {
  font-size: var(--fc-leg-size-code);     /* 13px */
  font-weight: var(--fc-weight-bold);
  letter-spacing: -0.01em;
  color: var(--fc-text-ink);
}
/* tabular figures on every clock time — a column of times must not shimmer */
.fc-leg__time {
  font-size: var(--fc-leg-size-code);     /* 13px */
  font-weight: var(--fc-weight-semibold);
  color: var(--fc-text-past-label);       /* #3E4757 */
  font-variant-numeric: tabular-nums;
}
.fc-leg__airport {
  margin: 2px 0 0;
  font-size: var(--fc-leg-size-airport);  /* 10px */
  font-weight: var(--fc-weight-regular);
  /* design value: --fc-text-muted-2 #98A1AE (2.48:1 on the detail panel).
     Substituted for AA. */
  color: var(--fc-text-secondary);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* Terminal chips are the smallest type in the product (9/700). Spec 09 §7
   requires ≥4.5:1 against the chip ground and forbids shrinking further.
   Measured: `--fc-text-secondary` on `--fc-terminal-chip` = 4.88:1. */
.fc-leg__term {
  display: inline-block;
  margin-top: 5px;
  padding: 2px 6px;
  border-radius: var(--fc-radius-badge);
  background: var(--fc-terminal-chip);
  color: var(--fc-text-secondary);
  font-size: var(--fc-size-badge);        /* 9px */
  font-weight: var(--fc-weight-semibold);
  letter-spacing: 0.02em;
}
/* Decorative — aria-hidden, because each side of the panel is already named
   "Departure" / "Arrival" for a screen reader. Still darkened past the
   design's #CBD1DB (1.51:1) so a sighted low-vision reader can see it. */
.fc-leg__arrow {
  text-align: center;
  color: var(--fc-text-secondary);
  font-size: 12px;
  line-height: 1;
}
@media (max-width: 430px) {
  button.fc-leg { min-height: var(--fc-tap); }
}
@media (prefers-reduced-motion: reduce) {
  .fc-leg, button.fc-leg { transition: none; }
  button.fc-leg:hover { transform: none; }
}

.fc-status {
  /* Screen-reader-only (Claude Design ruling D1, 2026-08-04): "Don't render it
     visibly. The visible equivalent already exists — the selected day drives the
     rail heading. A second description line duplicates it in the visual layer."
     It was rendering as a visible footer under the grid. */
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 6 — REDUCED MOTION
   Every effect in this stylesheet degrades to instant-legible-no-travel.
   Nothing here hijacks scroll, so there is nothing to disable beyond this.
   ════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .fc-bar:hover { transform: none; }   /* no travel — the shadow still reads */
  .fc-skip { transition: none; }
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 7 — NARROW WIDTHS
   The design is specified at 1320px and acceptance-tested at 1280/1440.
   Below that the board stops being a fixed 720px and the rail drops under
   the grid rather than squeezing it — the seven columns stay equal.
   ════════════════════════════════════════════════════════════════════════ */
@media (max-width: 1180px) {
  .fc-shell { padding: 24px 16px 16px; }
  .fc-card  { padding: 20px 18px 22px; }
  .fc-board { flex-direction: column-reverse; height: auto; }
  .fc-grid  { min-height: 560px; }
  .fc-rail  { flex: 1 1 auto; }
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 8 — THE DAY SHEET  (mobile-only component, unconditional CSS)

   Ruling C3/C4: on mobile the desktop week rail is REPLACED by this, not
   stacked below it. It is a true sheet — scrim, grab handle, drag, dismiss
   — and there is exactly ONE of it: tapping a different day re-populates
   the open sheet rather than stacking a second one.

   The rules live outside the @media block on purpose. The sheet element is
   only ever created by js/views/day-sheet.js while the month view is in
   its mobile branch, so it cannot leak onto desktop — but if the viewport
   is dragged wider, the sheet must stay fully styled for the ~120ms until
   the debounced re-render closes it, rather than flashing unstyled.
   ════════════════════════════════════════════════════════════════════════ */
/* Two rulings pull against each other and this is how they are reconciled.
   C3 says the sheet is a TRUE sheet — scrim rgba(26,34,51,.34) — and ALSO
   that it stays open and re-populates as you tap different days. A scrim
   that eats taps makes the second impossible, and a scrim painted over a
   live grid turns its 2px gutters into a dark lattice (measured in the
   first render, 2026-08-05).

   So the stack is: scrim 40 · app card 41 · sheet 42 · tab bar 43. The card
   and the tab bar sit ABOVE the scrim, so the calendar stays bright and
   tappable and the four views stay reachable with the sheet open — which is
   also what the design's own mobile-month export shows. The scrim keeps its
   specified colour and its dismiss role; on this surface the card is
   full-bleed, so there is nothing left for it to darken. That is a real
   deviation from "true sheet" as a look, and it is reported as one. */
/* CLOSED MEANS GONE. Both of these are built once and live in the body for
   the life of the page, so "closed" has to remove them from the hit-test and
   the paint, not just make them see-through. `opacity:0` alone left a
   full-viewport invisible button on z-40 swallowing every click on the page
   the moment the sheet closed — and the sheet itself, translated down by its
   own height from `bottom:76px`, still poked ~76px into a 900px desktop
   viewport. Both caught by screenshotting the desktop after a mobile visit,
   2026-08-05. `visibility` is delayed by exactly the transition so the fade
   still plays on the way out. */
.fc-sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  border: 0;
  padding: 0;
  background: var(--fc-scrim);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--fc-duration-state) var(--fc-ease-standard),
              visibility 0s linear var(--fc-duration-state);
}
.fc-sheet-scrim--open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--fc-duration-state) var(--fc-ease-standard),
              visibility 0s linear 0s;
}
body.fc-sheet-open { overflow: hidden; }
/* .fc-card is position:relative already; this is the raise, and it is wired
   — the class goes on <body> in day-sheet.js show()/close(). */
body.fc-sheet-open .fc-card { z-index: 41; }

.fc-sheet {
  position: fixed;
  left: 0; right: 0;
  /* sits ON TOP of the tab bar's band, not over it: the four views stay
     reachable while the sheet is open, which is what the design's own
     mobile-month export shows */
  bottom: calc(var(--fc-m-tabbar-h) + env(safe-area-inset-bottom, 0px));
  z-index: 42;
  /* percentage heights on a fixed element resolve against the viewport */
  max-height: var(--fc-m-sheet-max-h);
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* the safe-area inset is the tab bar's job now — see `bottom` above */
  padding: 12px 20px 26px;
  background: var(--fc-surface-card);
  border-radius: var(--fc-radius-sheet) var(--fc-radius-sheet) 0 0;
  box-shadow: var(--fc-shadow-inspector);
  transform: translateY(100%);
  visibility: hidden;
  pointer-events: none;
  transition: transform var(--fc-duration-state) var(--fc-ease-standard),
              visibility 0s linear var(--fc-duration-state);
}
.fc-sheet--open {
  transform: translateY(0);
  visibility: visible;
  pointer-events: auto;
  transition: transform var(--fc-duration-state) var(--fc-ease-standard),
              visibility 0s linear 0s;
}
/* while a drag is in flight the transform is written frame by frame from JS,
   so the transition has to be off or every frame fights the easing */
.fc-sheet--dragging { transition: none; }

/* the 36×4 grab handle, and the whole head is the drag surface */
.fc-sheet__grab {
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 0 0 auto;
  /* the handle owns the gesture; the list below still scrolls normally */
  touch-action: none;
  cursor: grab;
}
.fc-sheet__grab:active { cursor: grabbing; }
.fc-sheet__handle {
  width: var(--fc-m-grab-w);
  height: var(--fc-m-grab-h);
  align-self: center;
  border-radius: 2px;
  background: var(--fc-grab-handle);
}
.fc-sheet__head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.fc-sheet__title {
  margin: 0;
  font-size: var(--fc-m-size-sheet-title);
  font-weight: var(--fc-weight-bold);
  letter-spacing: var(--fc-track-tight);
}
.fc-sheet__sub {
  font-size: var(--fc-m-size-sheet-sub);
  font-weight: var(--fc-weight-medium);
  /* design value: --fc-text-muted-3 (2.38:1). Substituted for AA, as the
     desktop rail does for the same pairing. */
  color: var(--fc-text-secondary);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fc-sheet__close {
  margin-left: auto;
  flex-shrink: 0;
  min-width: var(--fc-tap);
  min-height: var(--fc-tap);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--fc-radius-chip);
  background: transparent;
  font-family: inherit;
  font-size: var(--fc-m-size-nav);
  font-weight: var(--fc-weight-semibold);
  color: var(--fc-text-secondary);
  cursor: pointer;
}

.fc-sheet__list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-bottom: 12px;
}

/* a trip: a filled card in the trip's colour, the same object the grid bar is */
.fc-sheet__trip {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  border-radius: var(--fc-radius-sheet-row);
  background: var(--bar-c);
  color: var(--fc-text-on-fill);
}
/* tentative and past keep the grid's own two treatments rather than opacity
   on the label — FONTS.md §4 / C7: never dim type in isolation from its ground */
.fc-sheet__trip--tentative {
  background: var(--bar-tentative-fill);
  border: 1.5px dashed var(--bar-dashed);
  color: var(--bar-c);
}
.fc-sheet__trip--past {
  background: var(--bar-past-fill);
  color: var(--fc-text-past-label);
}
.fc-sheet__row-top { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.fc-sheet__row-title {
  font-size: var(--fc-m-size-sheet-row);
  font-weight: var(--fc-weight-semibold);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fc-sheet__row-meta {
  margin-left: auto;
  flex-shrink: 0;
  font-size: var(--fc-m-size-sheet-meta);
  font-weight: var(--fc-weight-semibold);
}
.fc-sheet__row-sub {
  font-size: var(--fc-m-size-sheet-note);
  font-weight: var(--fc-weight-regular);
  line-height: 1.4;
}
/* white-on-hue inside a saturated fill: the palette's accepted below-AA
   exception (tests/contrast.check.js, Oli 2026-08-03) covers exactly this
   pairing. The design's .85/.82 opacities are kept ONLY here, where ground
   and type are one object and the alternative is a dull palette. */
.fc-sheet__trip .fc-sheet__row-meta { opacity: 0.85; }
.fc-sheet__trip .fc-sheet__row-sub  { opacity: 0.82; }
.fc-sheet__trip--tentative .fc-sheet__row-meta,
.fc-sheet__trip--tentative .fc-sheet__row-sub,
.fc-sheet__trip--past .fc-sheet__row-meta,
.fc-sheet__trip--past .fc-sheet__row-sub { opacity: 1; }

/* a point event / flight: a tint with the category's colour on its left edge */
.fc-sheet__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 9px 12px;
  border-left: 3px solid var(--bar-c);
  border-radius: 0 var(--fc-radius-sheet-row) var(--fc-radius-sheet-row) 0;
  background: var(--bar-fill);
}
.fc-sheet__item .fc-sheet__row-title { color: var(--fc-text-ink-2); }
/* design values: --fc-text-muted (2.8:1) and --fc-text-muted-2 (2.6:1) on a
   12%-tint ground. Both substituted for AA, matching the desktop rail. */
.fc-sheet__item .fc-sheet__row-meta,
.fc-sheet__item .fc-sheet__row-sub { color: var(--fc-text-secondary); }

/* the empty day. A composed line that says what to do next, never "No data". */
.fc-sheet__empty {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px 14px;
  border: 1px dashed var(--fc-divider);
  border-radius: var(--fc-radius-panel);
  background: var(--fc-surface-rail);
}
.fc-sheet__empty-title {
  font-size: var(--fc-m-size-sheet-row);
  font-weight: var(--fc-weight-bold);
  letter-spacing: var(--fc-track-tight);
}
.fc-sheet__empty-body {
  font-size: var(--fc-m-size-sheet-note);
  font-weight: var(--fc-weight-medium);
  line-height: var(--fc-leading-body);
  color: var(--fc-text-secondary);
  text-wrap: pretty;
}

/* the sheet travels; with reduced motion it is simply there, no travel */
@media (prefers-reduced-motion: reduce) {
  .fc-sheet, .fc-sheet-scrim { transition: none; }
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 9 — MOBILE MONTH  (≤ 430px)

   Ruling C2: events collapse to DOTS, max 3, and there is no overflow
   indicator — depth beyond three is the sheet's job. Ruling C4: no rail.
   The inline view pills become a bottom tab bar.

   Declared after SECTION 7 so it wins at equal specificity. Inside it the
   cell grounds are ordered out-of-month → holiday → weekend → plain, with
   today and selected as RINGS declared last — the desktop build's `today`
   ground was declared last at equal specificity and silently overrode the
   weekend and holiday grounds (close-strong council, 2026-08-04). Selected
   beats today, exactly as the .dc.html's ternary does.
   ════════════════════════════════════════════════════════════════════════ */
@media (max-width: 430px) {
  /* the phone frame IS the card: full bleed, no shell padding, no shadow */
  .fc-shell { padding: 0; gap: 0; }
  .fc-card {
    width: 100%;
    min-height: 100dvh;
    border-radius: 0;
    box-shadow: none;
    padding: 8px 0 calc(var(--fc-m-tabbar-h) + 12px);
  }

  /* ── header: ‹ October 2026 ›  + the Schengen chip ── */
  .fc-header {
    gap: 6px;
    flex-wrap: nowrap;
    margin: 0 0 10px;
    padding: 0 18px;
  }
  .fc-title { font-size: var(--fc-m-size-title); }
  .fc-qualifier { display: none; }   /* desktop-only copy: "click a week…" */
  .fc-pills { display: none; }       /* replaced by the bottom tab bar */
  .fc-nav {
    flex: 0 0 auto;
    min-width: var(--fc-tap);
    min-height: var(--fc-tap);
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: var(--fc-radius-chip);
    background: transparent;
    font-family: inherit;
    font-size: var(--fc-m-size-nav);
    font-weight: var(--fc-weight-semibold);
    /* design value: --fc-text-dow #AEB6C4 (2.2:1). Substituted for AA — it
       is the only control for changing month. */
    color: var(--fc-text-secondary);
    cursor: pointer;
  }
  .fc-eu-chip { margin-left: auto; flex-shrink: 0; }
  /* The UA's [hidden]{display:none} loses to any author `display` rule, so a
     hidden ‹ › or tab bar would still paint. Author-level, higher specificity,
     wired to the same attribute app.js sets. */
  .fc-nav[hidden], .fc-tabbar[hidden] { display: none; }

  /* the live region still announces; it just stops taking vertical space
     that the sheet needs. Never display:none — that silences it. */
  .fc-status {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* ── weekday strip ── */
  .fc-m-dow {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: var(--fc-m-grid-gap);
    padding: 0 var(--fc-m-grid-pad-x);
    margin-bottom: 4px;
  }
  .fc-m-dow__cell {
    text-align: center;
    padding: 2px 0;
    font-size: var(--fc-m-size-dow);
    font-weight: var(--fc-weight-semibold);
    /* design values #C3CAD8 / #AEB6C4 are 1.65:1 and 2.2:1. Substituted for
       AA; the weekday/weekend split they carried is already carried louder
       by the cell grounds below. */
    color: var(--fc-text-secondary);
  }

  /* ── the grid ── */
  .fc-m-grid { padding: 0 var(--fc-m-grid-pad-x); }
  .fc-m-week { position: relative; margin-bottom: var(--fc-m-grid-gap); }
  .fc-m-week__days {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: var(--fc-m-grid-gap);
  }

  .fc-m-cell {
    /* min-height, not height: a 2-lane day reserves 36px and its content
       needs 58.5. Grid items stretch, so the whole ROW grows together and
       stays internally equal — which is the rule UI-RULES §4 protects. */
    min-height: var(--fc-m-cell-min-h);
    /* Positioned so `--selected`'s ::after overlay below has a box to sit on.
       z-index stays auto, so the cell creates NO stacking context and the
       trip bars — later in DOM, also z-index auto — still paint over the
       cell's own ground exactly as before. Only the ::after rises. */
    position: relative;
    /* padding-bottom is written per day from JS: lanes × 15 + 6 */
    padding: var(--fc-m-cell-pad-y) var(--fc-m-cell-pad-x);
    display: flex;
    flex-direction: column;
    align-items: center;
    border: 0;
    border-radius: var(--fc-m-radius-cell);
    background: var(--fc-surface-card);
    font-family: inherit;
    cursor: pointer;
  }
  .fc-m-cell--weekend { background: var(--fc-weekend-cell); }
  .fc-m-cell--holiday { background: var(--fc-holiday-cell); }  /* beats weekend */
  .fc-m-cell--outside { background: var(--fc-outside-cell); }
  .fc-m-cell--past    { opacity: 0.5; }   /* C7: a wash over the WHOLE cell */
  .fc-m-cell--today:not(.fc-m-cell--selected) {
    background: var(--fc-m-today-cell);
    box-shadow: inset 0 0 0 1.5px var(--fc-m-today-ring);
  }
  /* INK, not blue — the same mark the desktop grid draws, and for the reason
     DESIGN-DECISIONS §1 gives in the same breath as the value: "so it doesn't
     read as 'today'". This rule shipped in --fc-today, which put the today
     accent around a non-today day and made the two states indistinguishable on
     the one surface where they sit 2px apart (the today ring above is 1.5px of
     --fc-m-today-ring). Corrected 2026-08-06 in the convergence pass: four
     surfaces carried three treatments for one state.

     DRAWN AS AN OVERLAY, 2026-08-07. This rule shipped as
     `box-shadow: inset 0 0 0 2px` on the strength of a claim written here —
     "the mobile grid has no week ring and no bars over the cell, so the inset
     shadow renders on all four sides". The second half of that is false.
     `.fc-m-bars` is `position:absolute; inset:0` on `.fc-m-week` and sits
     AFTER `.fc-m-week__days` in the DOM, so a trip bar crossing the selected
     day severs both verticals — measured at 390×844, 13 Oct selected, the
     orange Madrid bar cutting the ink outline in two. Identical defect, and
     identical cause, to the desktop cell above; it was one breakpoint behind
     the fix. Same treatment now: a pseudo-element that owns its own perimeter
     at z-index 4, with the host cell's 9px radius so the mark follows the
     rounded corner instead of squaring it off. */
  .fc-m-cell--selected::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 4;
    pointer-events: none;
    border: 2px solid var(--fc-text-ink);
    border-radius: var(--fc-m-radius-cell);
  }

  .fc-m-cell__num {
    font-size: var(--fc-m-size-day-num);
    font-weight: var(--fc-weight-medium);
    line-height: var(--fc-leading-flat);
    color: var(--fc-text-ink);
  }
  /* design value: --fc-text-weekend-num (3.19:1). Substituted for AA. */
  .fc-m-cell--weekend .fc-m-cell__num { color: var(--fc-text-secondary); }
  /* NOT substituted — raising this to an AA ink would flatten the
     in-month / out-of-month hierarchy. The full date is carried losslessly
     on the cell's aria-label. Same call as the desktop cell. */
  .fc-m-cell--outside .fc-m-cell__num { color: var(--fc-text-outside-num); }
  .fc-m-cell--today   .fc-m-cell__num { font-weight: var(--fc-weight-bold); color: var(--fc-today-numeral); }

  .fc-m-cell__hol {
    margin-top: 1px;
    font-size: var(--fc-m-size-hol-tag);
    font-weight: var(--fc-weight-semibold);
    /* design value: --fc-holiday-chip-text (3.59:1). Substituted for AA. */
    color: var(--fc-eu-ink);
  }

  /* max 3, centred, pushed to the bottom of the cell (C2) */
  .fc-m-cell__dots {
    margin-top: auto;
    display: flex;
    gap: var(--fc-m-dot-gap);
    justify-content: center;
  }
  .fc-m-cell__dot {
    width: var(--fc-m-dot);
    height: var(--fc-m-dot);
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--bar-c);
  }
  /* the ONLY thing separating Cosmo's trips from Cosmo's logistics is shape */
  .fc-m-cell__dot--square { border-radius: var(--fc-m-radius-dot); }

  /* ── trip bars: one lane per engine lane, sitting in the cells' reserve ── */
  .fc-m-bars {
    position: absolute;
    inset: 0;
    /* the bar is a restatement of what the cell's accessible name already
       says, and the trip inspector is not in this slice — so taps fall
       THROUGH to the day underneath rather than hitting a dead target */
    pointer-events: none;
  }
  .fc-m-bar {
    position: absolute;
    left: calc(var(--seg-left) + var(--fc-m-bar-inset));
    width: calc(var(--seg-width) - (2 * var(--fc-m-bar-inset)));
    bottom: calc(var(--fc-m-bar-bottom) + (var(--lane) * var(--fc-m-bar-lane-pitch)));
    height: var(--fc-m-bar-height);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    padding: 0 5px;
    border-radius: var(--fc-m-radius-bar);
    overflow: hidden;
    white-space: nowrap;
    background: var(--bar-c);
    color: var(--fc-text-on-fill);
    font-size: var(--fc-m-size-bar-label);
    font-weight: var(--fc-weight-semibold);   /* §3 Mobile: 9.5 / 700 */
  }
  /* a cut edge means "this trip continues into the next/previous week row" —
     the same convention and the same token as the desktop bar. renderMobileBar()
     applies these classes; without the rules they were applied and did nothing,
     which is the dead-CSS defect pointing the other way (caught by the class
     sweep, 2026-08-05). */
  .fc-m-bar--cont-left  { border-top-left-radius: var(--fc-radius-barcut);  border-bottom-left-radius: var(--fc-radius-barcut); }
  .fc-m-bar--cont-right { border-top-right-radius: var(--fc-radius-barcut); border-bottom-right-radius: var(--fc-radius-barcut); }
  .fc-m-bar--tentative {
    background: var(--bar-tentative-fill);
    border: 1.2px dashed var(--bar-dashed);
    color: var(--bar-c);
  }
  .fc-m-bar--past {
    background: var(--bar-past-fill);
    color: var(--fc-text-past-label);
  }
  .fc-m-bar__badge {
    flex-shrink: 0;
    padding: 0 4px;
    border-radius: 4px;
    font-size: var(--fc-m-size-bar-badge);   /* §3 Mobile: 8.5 / 800 / .06em */
    font-weight: var(--fc-weight-bold);
    letter-spacing: var(--fc-track-badge);
    background: var(--fc-badge-ground);
  }
  .fc-m-bar--tentative .fc-m-bar__badge,
  .fc-m-bar--past .fc-m-bar__badge { background: var(--bar-badge-ground); color: var(--bar-c); }
  .fc-m-bar--past .fc-m-bar__badge { color: var(--fc-text-past-label); }
  /* NEVER the two-line overflow-wrap:anywhere treatment — that is a desktop
     Month roomy-day exception only. On mobile a bar label truncates. */
  .fc-m-bar__label {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ── the bottom tab bar ── */
  .fc-tabbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    /* above the sheet's scrim AND above the sheet: switching view is always
       one tap, never dismiss-then-tap */
    z-index: 43;
    height: calc(var(--fc-m-tabbar-h) + env(safe-area-inset-bottom, 0px));
    display: flex;
    align-items: flex-start;
    gap: 4px;
    padding: 9px 12px env(safe-area-inset-bottom, 0px);
    background: var(--fc-surface-card);
    border-top: 1px solid var(--fc-hairline-card);
  }
  .fc-tab {
    flex: 1;
    min-height: var(--fc-tap);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 5px;
    padding: 6px 0;
    text-decoration: none;
    font-size: var(--fc-m-size-tab);
    font-weight: var(--fc-weight-medium);
    /* design value: --fc-text-dow #AEB6C4 (2.2:1). Substituted for AA. */
    color: var(--fc-text-secondary);
  }
  .fc-tab[aria-current='page'] {
    font-weight: var(--fc-weight-bold);
    color: var(--fc-text-ink);
  }
  .fc-tab__dot {
    width: var(--fc-m-tab-dot);
    height: var(--fc-m-tab-dot);
    border-radius: var(--fc-m-radius-tabdot);
    background: var(--fc-tab-dot-off);
  }
  .fc-tab[aria-current='page'] .fc-tab__dot { background: var(--fc-text-ink); }

  /* the state card has no 720px board to fill down here — the LAYER carries
     the height now, not the card, so this targets the layer */
  .fc-state-layer--board { min-height: 320px; padding: 0 18px; }
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 10 — YEAR TOKENS

   ⚠ NOT YET MIRRORED into projects/studio-tool-kit/brand-foundation/
   calendar.tokens.json — that file is outside this build's write envelope,
   exactly as the mobile tier in SECTION 1b is. Both blocks need adding there.
   Flagged in the build report.

   Every value below was MEASURED off design-export-2026-08-04/exports/
   desktop-year.png and mobile-year.png at 2×, and cross-checked against
   DESIGN-DECISIONS.md §1/§3 and FONTS.md §3 "Year". Where the two disagree
   the measurement is quoted on the line.
   ════════════════════════════════════════════════════════════════════════ */
:root {
  /* ── desktop grid geometry (DESIGN-DECISIONS §3 "Year view") ── */
  --fc-y-gutter:          44px;   /* month-label column; measured: grid starts 74px in, card pad 30 */
  --fc-y-col:             32px;
  --fc-y-cell-h:          60px;
  --fc-y-dow-h:           24px;   /* measured: letters centred at 93.25px, grid top at 106px */
  --fc-y-num-top:          4px;   /* numeral ink lands 5.5px down — §3 says "5/6px inset" */
  --fc-y-num-right:        6px;   /* measured 6.5px to the ink; digit side-bearing is the rest */

  /* ── the three reserved bands per cell (§3, verbatim) ── */
  --fc-y-bar-top:         19px;
  --fc-y-bar-lane-pitch:  15px;
  --fc-y-bar-h:           13px;
  --fc-y-bar-inset:        2px;   /* each side of the day span */
  --fc-y-bar-label-min:   44px;   /* FONTS.md §4: no label at all below this, never an ellipsis */
  --fc-y-strip-h:          3px;
  --fc-y-strip-bottom:     5px;
  --fc-y-strip-pitch:      5px;
  --fc-y-strip-inset:      4px;   /* strip = the day column minus 4px each side */

  --fc-y-radius-bar:       7px;   /* real trip end (clamps to a pill on a 13px bar) */
  --fc-y-radius-barcut:    2px;   /* the trip continues off this row */
  --fc-y-radius-strip:     2px;

  /* ── grounds. Future cells reuse the shared tokens; the past ramp is Year's
        own, per §1 "Past-day colour (year view)" — a muted GROUND per cell
        type, never an opacity layer over the cell. ── */
  --fc-y-holiday-cell:    #E5F6EC;
  --fc-y-past-weekend:    #EFF1F5;
  --fc-y-past-holiday:    #EFF6F1;
  --fc-y-past-plain:      #FBFCFD;
  --fc-y-past-hol-tag:    #C3CFC7;
  --fc-y-strip-past:      0.32;   /* §1: past POINT strips do take opacity (no type on them) */
  /* the same two values the mobile Month cell already carries — aliased, not
     restated, so there is one rgba() in the file per design value */
  --fc-y-today-cell:      var(--fc-m-today-cell);
  --fc-y-today-ring:      var(--fc-m-today-ring);

  /* ── type (FONTS.md §3 "Year", transcribed) ── */
  --fc-y-size-month:      11px;   /* row label, 800 */
  --fc-y-size-dow:         9px;   /* weekday letters, 600 */
  --fc-y-size-num:         9.5px; /* day numeral, 500 — measured 7.0px of digit ink at 2× */
  --fc-y-size-num-today:  10px;   /* 800 */
  --fc-y-size-hol:         7.5px; /* holiday tag, 600 / .04em — the product's smallest type */
  --fc-y-size-bar-label:   9px;   /* 500 */

  /* ── the mobile ribbon (≤430px) ── */
  --fc-y-m-pad-x:         17px;   /* measured: 7 pills of 49px + 6 gaps of 2 = 355 in 390 */
  --fc-y-m-gap:            2px;
  --fc-y-m-pill-h:        19px;
  /* The tap slot, NOT the paint: the pill is 19px, this is the row it sits in.
     23 + the 1px week hairline = 24px centre-to-centre between vertically
     adjacent days, and 49 + 2 = 51px horizontally — which is what satisfies
     WCAG 2.2 AA 2.5.8 here, through its target-OFFSET exception rather than
     through 24×24 size. FONTS.md §4 D3's 44px is NOT reachable on this
     surface: a 44px row would almost double the ribbon's height and the
     design's own measured pitch is 23. Named in the build report, not hidden. */
  --fc-y-m-row-h:         23px;
  --fc-y-m-radius-pill:    5px;
  --fc-y-m-bar-h:         15px;
  --fc-y-m-radius-bar:     7px;
  --fc-y-m-dot:            3.5px; /* ruling C2's mobile-Year clause; the PNG measures 3.0 */
  --fc-y-m-dot-gap:        2px;
  --fc-y-m-past:           0.6;   /* whole-cell wash, ground and type together (C7) */
  --fc-y-m-size-sec:      14px;   /* ribbon month divider label, 800 / -0.02em */
  --fc-y-m-size-bar-label: 9.5px; /* the one place weight goes UP as size goes down: 700 */
  --fc-y-m-size-bar-badge: 8.5px; /* 800 / .06em */
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 11 — YEAR, DESKTOP  (twelve month rows × 37 day columns)

   All twelve rows share the same 37 weekday columns — every month row starts
   at its own weekday offset — so the weekend shading forms continuous
   vertical bands down the whole year. That is the only reason the grid is 37
   wide (max offset 6 + 31 days) rather than 31.

   CONTRAST, STATED PLAINLY. The whole Year type ramp is below AA at these
   sizes: day numeral #AEB6C4 on white is 2.2:1, weekend #7E8CA3 on #E7EDF9 is
   3.1:1, weekday letters #C3CAD8 is 1.65:1, holiday tag #7FB79E is 2.4:1.
   They are kept because they are explicit design values (DESIGN-DECISIONS §1
   and the 08-04 render, which is the acceptance target) AND because nothing
   here is the sole carrier: the weekday strip is aria-hidden, and every cell
   speaks its whole day — date, holiday, who is away, every event — on its
   accessible name, then drills to Month where the same day is set at 14/600
   in full ink. Substituting the ramp upward would flatten the past/future
   hierarchy the view exists to show. Reported as a live conflict between the
   design ruling and the studio's AA standard; it is /ui's call, not the
   builder's, and it has not been silently resolved either way.
   ════════════════════════════════════════════════════════════════════════ */
.fc-year__scroll {
  /* 44 + 37×32 = 1228px of grid. It fits the 1320 card; below ~1250 of card
     width it scrolls sideways rather than squeezing the day columns, because
     a 28px column stops being a column. */
  overflow-x: auto;
  overflow-y: hidden;
}

.fc-year__head,
.fc-year__row {
  display: grid;
  grid-template-columns: var(--fc-y-gutter) repeat(37, var(--fc-y-col));
  width: 100%;
  min-width: calc(var(--fc-y-gutter) + 37 * var(--fc-y-col));
}

.fc-year__head { height: var(--fc-y-dow-h); align-items: center; }
.fc-year__dow {
  text-align: center;
  line-height: var(--fc-leading-flat);
  font-size: var(--fc-y-size-dow);
  font-weight: var(--fc-weight-medium);
  color: var(--fc-text-faint);
}
/* measured #B0BBCE against the weekday letters' #C8CFDA — the weekend columns
   are keyed a step darker, matching their darker cell ground below */
.fc-year__dow--weekend { color: var(--fc-text-dow); }

.fc-year__grid { border-top: 1px solid var(--fc-hairline-row); }

.fc-year__row {
  position: relative;
  /* measured: every horizontal divider is #F0F2F7 and spans the full card
     content width, including under the month label. DESIGN-DECISIONS §1 lists
     a "grid divider #E4E9F2 / weekend divider #D9E1EE" pair; neither appears
     anywhere in desktop-year.png. The render wins. */
  border-bottom: 1px solid var(--fc-hairline-row);
}

.fc-year__label {
  display: flex;
  align-items: center;
  font-size: var(--fc-y-size-month);
  font-weight: var(--fc-weight-bold);
  letter-spacing: var(--fc-track-tight);
  color: var(--fc-text-secondary);   /* measured #5C6679 */
}

/* ── one day cell ── */
.fc-year__cell {
  position: relative;
  height: var(--fc-y-cell-h);
  padding: 0;
  border: 0;
  border-right: 1px solid var(--fc-cell-vertical);
  background: var(--fc-surface-card);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  overflow: hidden;
}
.fc-year__cell--empty { background: var(--fc-outside-cell); cursor: default; }
.fc-year__cell--weekend { background: var(--fc-weekend-chip); }
.fc-year__cell--holiday { background: var(--fc-y-holiday-cell); }   /* holiday beats weekend */
.fc-year__cell--past { background: var(--fc-y-past-plain); }
.fc-year__cell--past.fc-year__cell--weekend { background: var(--fc-y-past-weekend); }
.fc-year__cell--past.fc-year__cell--holiday { background: var(--fc-y-past-holiday); }
/* two classes so it wins the tie against --past.--weekend on source order.
   Today is a full-cell ring, square to the cell edge — measured, no radius. */
.fc-year__cell.fc-year__cell--today {
  background: var(--fc-y-today-cell);
  box-shadow: inset 0 0 0 1.5px var(--fc-y-today-ring);
}
button.fc-year__cell:hover { filter: brightness(0.97); }

.fc-year__cellhead {
  position: absolute;
  top: var(--fc-y-num-top);
  right: var(--fc-y-num-right);
  display: flex;
  align-items: baseline;
  gap: 3px;
}
.fc-year__num {
  font-size: var(--fc-y-size-num);
  font-weight: var(--fc-weight-regular);
  line-height: var(--fc-leading-flat);
  color: var(--fc-text-dow);
}
.fc-year__cell--weekend .fc-year__num { color: var(--fc-text-weekend-num); }
/* declared after --weekend so a past weekend takes the past numeral, which is
   what the render shows */
.fc-year__cell--past .fc-year__num { color: var(--fc-text-outside-num); }
.fc-year__cell--today .fc-year__num {
  font-size: var(--fc-y-size-num-today);
  font-weight: var(--fc-weight-bold);
  color: var(--fc-today-numeral);
}

/* weekday-only, single-country tag; both countries → no tag at all */
.fc-year__hol {
  font-size: var(--fc-y-size-hol);
  font-weight: var(--fc-weight-medium);
  letter-spacing: var(--fc-track-micro);
  line-height: var(--fc-leading-flat);
  color: var(--fc-eu-micro);
}
.fc-year__cell--past .fc-year__hol { color: var(--fc-y-past-hol-tag); }

/* ── point events: full-width day strips, never dots (§5 — "a dot at 32px
      column width was unreadable"). Stacked upward from the bottom. ── */
.fc-year__strip {
  position: absolute;
  left: var(--fc-y-strip-inset);
  width: calc(var(--fc-y-col) - 2 * var(--fc-y-strip-inset));
  height: var(--fc-y-strip-h);
  bottom: calc(var(--fc-y-strip-bottom) + var(--i) * var(--fc-y-strip-pitch));
  border-radius: var(--fc-y-radius-strip);
  background: var(--strip-c);
}
/* §1: past point strips DO take opacity — unlike a trip bar, there is no type
   on them for the wash to destroy */
.fc-year__strip--past { opacity: var(--fc-y-strip-past); }

/* ── trip bars ──
   The layer starts after the month-label gutter, so a bar's x is pure column
   arithmetic: startCol × 32 + 2, width span × 32 − 4. startCol and span come
   from engine.js and are never recomputed here. */
.fc-year__bars {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--fc-y-gutter);
  right: 0;
  /* the bars sit over the cells; only the bars themselves take a click, so a
     tap on bare cell ground still reaches the day underneath */
  pointer-events: none;
}
.fc-year__bar {
  position: absolute;
  left: var(--bar-x);
  width: var(--bar-w);
  top: calc(var(--fc-y-bar-top) + var(--lane) * var(--fc-y-bar-lane-pitch));
  height: var(--fc-y-bar-h);
  display: flex;
  align-items: center;
  padding: 0 7px;
  border: 0;
  border-radius: var(--fc-y-radius-bar);
  background: var(--bar-c);
  color: var(--fc-text-on-fill);
  font-family: inherit;
  font-size: var(--fc-y-size-bar-label);
  font-weight: var(--fc-weight-regular);
  line-height: var(--fc-leading-flat);
  overflow: hidden;
  white-space: nowrap;
  pointer-events: auto;
  cursor: pointer;
  transition: box-shadow var(--fc-duration-state) var(--fc-ease-standard);
}
/* a cut edge means "this trip continues into the previous/next month row" */
.fc-year__bar--cont-left  { border-top-left-radius: var(--fc-y-radius-barcut);  border-bottom-left-radius: var(--fc-y-radius-barcut); }
.fc-year__bar--cont-right { border-top-right-radius: var(--fc-y-radius-barcut); border-bottom-right-radius: var(--fc-y-radius-barcut); }
.fc-year__bar:hover { box-shadow: var(--fc-shadow-bar-hover); }
/* tentative — three simultaneous signals plus the trailing ' ?' on the label */
.fc-year__bar--tentative {
  background: var(--bar-tentative-fill);
  border: 1.4px dashed var(--bar-dashed);
  color: var(--bar-c);
}
/* past — a tint plus a dark neutral label. Never blanket opacity, which at
   9px destroys the stroke (FONTS.md §4 / ruling C7). */
.fc-year__bar--past {
  background: var(--bar-past-fill);
  color: var(--fc-text-past-label);
}
.fc-year__bar-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }


/* ════════════════════════════════════════════════════════════════════════
   SECTION 12 — YEAR, MOBILE RIBBON  (≤ 430px)

   One row per WEEK, flowing continuously top to bottom; a month is a heading
   and a hairline, not a break in the run of dates. A boundary week renders
   ONCE, in the section of the month it ends in — 26 Jan – 1 Feb under
   "February", 23 Feb – 1 Mar under "March", both verified in mobile-year.png.
   Days outside the year (the leading December cells, the trailing January
   ones) render blank, so every date in 2026 appears exactly once.

   Declared last so it wins at equal specificity, exactly as SECTION 9 does.
   ════════════════════════════════════════════════════════════════════════ */
@media (max-width: 430px) {
  /* ── the sticky weekday key ── */
  .fc-y-m-dow {
    position: sticky;
    top: 0;
    z-index: 5;
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    column-gap: var(--fc-y-m-gap);
    padding: 5px var(--fc-y-m-pad-x) 6px;
    background: var(--fc-surface-card);
    border-bottom: 1px solid var(--fc-hairline-card);
  }
  .fc-y-m-dow__cell {
    text-align: center;
    font-size: var(--fc-m-size-dow);
    font-weight: var(--fc-weight-medium);
    /* design value #AEB6C4 is 2.2:1. Substituted for AA, exactly as the mobile
       Month strip above does — this one is a key, not a date. */
    color: var(--fc-text-secondary);
  }

  /* ── a month section ── */
  .fc-y-m-sec { padding: 0 var(--fc-y-m-pad-x); }
  .fc-y-m-sec:first-of-type { margin-top: 11px; }
  .fc-y-m-sec + .fc-y-m-sec { margin-top: 16px; }
  .fc-y-m-sec__head {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 6px;
  }
  .fc-y-m-sec__title {
    margin: 0;
    font-size: var(--fc-y-m-size-sec);
    font-weight: var(--fc-weight-bold);
    letter-spacing: var(--fc-track-tight);
    color: var(--fc-text-ink);
  }
  .fc-y-m-sec__rule { flex: 1; height: 1px; background: var(--fc-hairline-card); }

  /* ── one week row ── */
  /* no padding: the 23px day row already carries the 2px of air above and
     below its 19px pill, so the week pitch is exactly 23 + the hairline */
  .fc-y-m-week { border-bottom: 1px solid var(--fc-hairline-row); }
  .fc-y-m-week:last-child { border-bottom: 0; }
  .fc-y-m-week__days {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    column-gap: var(--fc-y-m-gap);
  }

  .fc-y-m-cell {
    /* 24px, not the pill's 19 — the tap target is the ROW slot, not the paint.
       See --fc-y-m-row-h for why this is 24 and not FONTS.md §4 D3's 44. */
    height: var(--fc-y-m-row-h);
    display: flex;
    align-items: center;
    padding: 0;
    border: 0;
    background: transparent;
    font-family: inherit;
    cursor: pointer;
  }
  .fc-y-m-cell--blank { cursor: default; }
  .fc-y-m-cell__pill {
    flex: 1;
    min-width: 0;
    height: var(--fc-y-m-pill-h);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1px;
    border-radius: var(--fc-y-m-radius-pill);
  }
  .fc-y-m-cell__pill--weekend { background: var(--fc-weekend-chip); }
  .fc-y-m-cell__pill--holiday { background: var(--fc-y-holiday-cell); }
  /* Ruling C7 allows a uniform wash over a whole cell — ground, numeral and
     dots dim together, which is what the render measures (the ribbon's past
     weekday numeral resolves to #5C6679 at exactly 60%). It sits on the PILL,
     not the button, so the focus ring never dims with it. */
  .fc-y-m-cell__pill--past { opacity: var(--fc-y-m-past); }
  .fc-y-m-cell__pill--today {
    background: var(--fc-y-today-cell);
    box-shadow: inset 0 0 0 1.5px var(--fc-y-today-ring);
  }

  .fc-y-m-cell__num {
    font-size: var(--fc-y-size-num);
    font-weight: var(--fc-weight-regular);
    line-height: var(--fc-leading-flat);
    color: var(--fc-text-secondary);
  }
  .fc-y-m-cell__pill--weekend .fc-y-m-cell__num { color: var(--fc-text-weekend-num); }
  /* design value measures ≈#3E8C6C (3.6:1). Substituted for AA, as every other
     holiday label in this stylesheet is. */
  .fc-y-m-cell__pill--holiday .fc-y-m-cell__num { color: var(--fc-eu-ink); }
  .fc-y-m-cell__pill--today .fc-y-m-cell__num {
    font-size: var(--fc-y-size-num-today);
    font-weight: var(--fc-weight-bold);
    color: var(--fc-today-numeral);
  }

  /* max 3, centred under the numeral, NO overflow indicator (ruling C2) */
  .fc-y-m-cell__dots { display: flex; gap: var(--fc-y-m-dot-gap); }
  .fc-y-m-cell__dot {
    width: var(--fc-y-m-dot);
    height: var(--fc-y-m-dot);
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--strip-c);
  }
  /* the ONLY thing separating Cosmo's trips from Cosmo's logistics is shape */
  .fc-y-m-cell__dot--square { border-radius: 1px; }

  /* ── trip bars: the same seven-column grid as the pills, so a bar lines up
        with its days across the 2px gutters with no percentage arithmetic ── */
  .fc-y-m-week__bars {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    column-gap: var(--fc-y-m-gap);
    row-gap: 2px;
    margin: 2px 0 5px;
  }
  .fc-y-m-bar {
    height: var(--fc-y-m-bar-h);
    margin: 0 2px;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 6px;
    border: 0;
    border-radius: var(--fc-y-m-radius-bar);
    background: var(--bar-c);
    color: var(--fc-text-on-fill);
    font-size: var(--fc-y-m-size-bar-label);
    font-weight: var(--fc-weight-semibold);   /* §3 Mobile: 9.5 / 700 */
    line-height: var(--fc-leading-flat);
    overflow: hidden;
    white-space: nowrap;
    cursor: pointer;
  }
  .fc-y-m-bar--cont-left  { border-top-left-radius: var(--fc-y-radius-barcut);  border-bottom-left-radius: var(--fc-y-radius-barcut); }
  .fc-y-m-bar--cont-right { border-top-right-radius: var(--fc-y-radius-barcut); border-bottom-right-radius: var(--fc-y-radius-barcut); }
  .fc-y-m-bar--tentative {
    background: var(--bar-tentative-fill);
    border: 1.3px dashed var(--bar-dashed);
    color: var(--bar-c);
  }
  .fc-y-m-bar--past {
    background: var(--bar-past-fill);
    color: var(--fc-text-past-label);
  }
  .fc-y-m-bar__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  /* the who-badge rides EVERY week segment, right-aligned: a ribbon is
     scrolled through, so a continuation row without one is an unattributed bar */
  .fc-y-m-bar__badge {
    margin-left: auto;
    flex-shrink: 0;
    padding: 1px 5px;
    border-radius: var(--fc-radius-badge);
    font-size: var(--fc-y-m-size-bar-badge);
    font-weight: var(--fc-weight-bold);
    letter-spacing: var(--fc-track-badge);
    background: var(--fc-badge-ground);
  }
  .fc-y-m-bar--tentative .fc-y-m-bar__badge { background: var(--bar-badge-ground); color: var(--bar-c); }
  /* measured: a PAST bar carries no badge pill at all in mobile-year.png —
     just the initials in the past-label ink. Desktop Month's C6 does tint the
     pill; the render is the acceptance target here, and the difference is
     reported rather than quietly reconciled either way. */
  .fc-y-m-bar--past .fc-y-m-bar__badge { background: transparent; color: var(--fc-text-past-label); }
}


/* ════════════════════════════════════════════════════════════════════════
   SECTION 13 — DATA STATES ON YEAR AND MONTH: loading skeletons + card host
   Added 2026-08-07. calendar.css SECTION 5 has carried the `.fc-state` card
   since the 08-06 convergence, and js/app.js has passed `opts.loading`,
   `opts.empty` and `opts.error` to EVERY view since the same day. Only Where
   consumed them. `?state=error#/month/2026-10` and `?state=loading#/year/2026`
   drew the ordinary populated grid, silently, at both breakpoints.

   DECLARED LAST, deliberately. The mobile tiers in SECTIONS 9 and 12 reach
   `.fc-m-cell--today:not(.fc-m-cell--selected)` and
   `.fc-y-m-cell__pill--today`, which are (0,3,0) and (0,2,0) — the same or
   near the loading masks below. A media query adds no specificity, so the tie
   is broken on source order and the mask has to come after them. This is the
   same reason SECTION 9 is declared after SECTION 7, and the file says so
   there.

   THE RULE THE MASK FOLLOWS, lifted verbatim from css/where.css SECTION 6 so
   the estate has one: anything DERIVED FROM THE DATA is masked; the chrome §6
   keeps in every state — weekday letters, month labels, the section headings,
   the holiday tags — is not. Geometry never changes, so nothing moves when the
   real data lands. Skeleton bars sit at the real bar geometry, which they do
   for free here: the mask recolours the bar that is already positioned rather
   than drawing a stand-in.
   ════════════════════════════════════════════════════════════════════════ */

/* ── the pill's row ────────────────────────────────────────────────────────
   §6 puts `LOADING…` in the header row. The header is the frozen shell's and
   no view may write it (contract §2), so each screen opens with this line
   instead. It is the ONLY element the loading state adds; everything else is
   the real render, recoloured. */
.fc-state-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

/* ── the card host ─────────────────────────────────────────────────────────
   `.fc-state-layer--overlay` is `position:absolute; inset:0`, so its nearest
   positioned ancestor decides what the card is centred over and what the .66
   ground covers. Applied ONLY in the two states that use it: a blanket
   `position: relative` on `.fc-board` would change the containing block for
   every absolutely-positioned descendant in the default render, which is the
   whole trip-bar layer. */
.fc-year[data-state='empty'],    .fc-year[data-state='error'],
.fc-y-m[data-state='empty'],     .fc-y-m[data-state='error'],
.fc-board[data-state='empty'],   .fc-board[data-state='error'],
.fc-m-month[data-state='empty'], .fc-m-month[data-state='error'] {
  position: relative;
}
/* The mobile year ribbon is twelve month sections tall — several screens. A
   card centred in it lands below the fold, so on this one surface the card
   goes to the top of the ribbon where it is on screen at rest. Same card, same
   ground, different anchor. */
.fc-y-m[data-state='empty']  .fc-state-layer--overlay,
.fc-y-m[data-state='error']  .fc-state-layer--overlay {
  align-items: start;
  padding-top: 24px;
}

/* ── YEAR, desktop ───────────────────────────────────────────────────────── */
/* Grounds: every day cell drops to the flat skeleton ground, and the today
   ring goes with it — today is redrawn when the data lands. */
.fc-year[data-state='loading'] .fc-year__cell {
  background: var(--fc-skeleton-cell);
  box-shadow: none;
}
/* exports/loading-year.png carries NO day numerals and no holiday tags at all
   — at 37 columns a text placeholder per day is noise, and the reference is
   explicit. `visibility` rather than `display`, so the absolutely-positioned
   head keeps its box and nothing reflows when the data lands. */
.fc-year[data-state='loading'] .fc-year__cellhead { visibility: hidden; }
.fc-year[data-state='loading'] .fc-year__bar {
  background: var(--fc-skeleton-bar);
  box-shadow: none;
  border: 0;
}
.fc-year[data-state='loading'] .fc-year__bar-label,
.fc-year[data-state='loading'] .fc-year__bar .fc-bar__badge { color: transparent; background: transparent; }
/* the point-item ticks — a flight or an event is data */
.fc-year[data-state='loading'] .fc-year__strip { background: var(--fc-skeleton-bar); }

/* ── YEAR, mobile ribbon ─────────────────────────────────────────────────── */
.fc-y-m[data-state='loading'] .fc-y-m-cell__pill {
  background: var(--fc-skeleton-cell);
  box-shadow: none;
}
.fc-y-m[data-state='loading'] .fc-y-m-cell__num { color: transparent; }
.fc-y-m[data-state='loading'] .fc-y-m-cell__dot { background: var(--fc-skeleton-bar); }
/* THE DASHED TENTATIVE OUTLINE, closed 2026-08-08 by the CHROMA sweep.
   `--tentative` draws a 1.3px dashed BORDER in the trip colour rather than a
   background, so recolouring the ground left `Morocco ?` outlined in
   rgba(51,81,138,.7) — the family trip colour, under a LOADING… pill, on a
   product whose first rule is that bar colour says who. The text sweep could
   not see it: the label was already transparent, and the ring around the
   nothing still said "the family are away". The desktop Month equivalent was
   caught and fixed on 08-07 with the border-color line further down this
   section; the two MOBILE surfaces were missed. Same defect, same line.
   Kept outside the block on purpose — the assertion that guards this reads the
   rule with `\{([^}]*)\}`, and a brace inside a comment inside the block ends
   the capture early. */
.fc-y-m[data-state='loading'] .fc-y-m-bar {
  background: var(--fc-skeleton-bar);
  box-shadow: none;
  border-color: var(--fc-skeleton-bar);
}
.fc-y-m[data-state='loading'] .fc-y-m-bar__label,
.fc-y-m[data-state='loading'] .fc-y-m-bar__badge { color: transparent; background: transparent; }

/* ── MONTH, desktop: the grid ────────────────────────────────────────────── */
.fc-board[data-state='loading'] .fc-cell {
  background: var(--fc-skeleton-cell);
  box-shadow: none;
}
/* §6: "text placeholders #EDF0F6 at 8px tall, radius 4". The numerals are
   masked as pills here and NOT hidden, because exports/loading-month.png shows
   exactly that — one grey pill per cell at the numeral's own position. */
.fc-board[data-state='loading'] .fc-cell__num,
.fc-board[data-state='loading'] .fc-more,
.fc-board[data-state='loading'] .fc-rail__num,
.fc-board[data-state='loading'] .fc-rail__event-title,
.fc-board[data-state='loading'] .fc-rail__event-sub,
.fc-board[data-state='loading'] .fc-rail__event-meta {
  color: transparent;
  background: var(--fc-skeleton-text);
  border-radius: 4px;
  min-height: 8px;
}
/* The holiday tag STAYS — loading-month.png keeps the green `UK` on 26 Oct. A
   bank holiday is not fetched with the week; it is the same fact in every
   state, which is exactly §6's chrome rule. */
.fc-board[data-state='loading'] .fc-chip {
  background: var(--fc-skeleton-bar);
  box-shadow: none;
}
.fc-board[data-state='loading'] .fc-chip__text,
.fc-board[data-state='loading'] .fc-chip__main,
.fc-board[data-state='loading'] .fc-chip__meta,
.fc-board[data-state='loading'] .fc-chip__line2,
.fc-board[data-state='loading'] .fc-chip__glyph { color: transparent; }
.fc-board[data-state='loading'] .fc-chip__dot { background: transparent; }
.fc-board[data-state='loading'] .fc-bar {
  background: var(--fc-skeleton-bar);
  box-shadow: none;
}
.fc-board[data-state='loading'] .fc-bar__label,
.fc-board[data-state='loading'] .fc-bar__badge { color: transparent; background: transparent; }
/* A tentative trip draws a dashed BORDER, not a background, so recolouring the
   ground left a dashed pill outline sitting over the skeleton at 1440. */
.fc-board[data-state='loading'] .fc-bar { border-color: transparent; }
/* `Nothing on` is an ANSWER about a day, and while the month is loading the
   answer is not known — a chip that says nothing is on states a fact the
   screen does not have yet. */
.fc-board[data-state='loading'] .fc-empty-chip {
  background: var(--fc-skeleton-text);
  color: transparent;
}
/* ── MONTH, desktop: the week rail ───────────────────────────────────────── */
/* The rail is the selected week's events, which is as much data as the grid.
   It leaked in the first pass at 1440 — a fully legible list of appointments
   under a LOADING… pill. The head (`This week`, the range) is chrome. */
.fc-board[data-state='loading'] .fc-rail__event {
  background: var(--fc-skeleton-cell);
  box-shadow: none;
  /* Every rail row carries a 3px `border-left: solid var(--bar-c)` — the WHO
     stripe. Repainting the row's ground left the stripe at full strength, so
     the masked row for `Cosmo — pick-up — Laura` was still rgb(124,58,237),
     Cosmo's colour, in a 3px band the eye reads before any label. Invisible to
     the text sweep, which is why the chroma sweep exists. 2026-08-08. */
  border-left-color: var(--fc-skeleton-bar);
}
/* Both spine rules, not just `--on`: `--on.--tentative` is (0,2,0) and ties
   with a single-descendant mask, and the trip colour is the most data-like
   thing on the rail. */
.fc-board[data-state='loading'] .fc-rail__spine,
.fc-board[data-state='loading'] .fc-rail__spine--on { background: var(--fc-skeleton-bar); }
/* `.fc-rail__hol` is NOT masked, and neither is the grid's `.fc-cell__hol` —
   loading-month.png keeps the green `UK` tag on 26 Oct, and a bank holiday is
   the same fact whether the week loaded or not. Chrome, by §6's own rule. */

/* ── MONTH, mobile ───────────────────────────────────────────────────────── */
.fc-m-month[data-state='loading'] .fc-m-cell {
  background: var(--fc-skeleton-cell);
  box-shadow: none;
}
.fc-m-month[data-state='loading'] .fc-m-cell__num {
  color: transparent;
  background: var(--fc-skeleton-text);
  border-radius: 4px;
  min-height: 8px;
}
.fc-m-month[data-state='loading'] .fc-m-cell__dot { background: var(--fc-skeleton-bar); }
.fc-m-month[data-state='loading'] .fc-m-bar {
  background: var(--fc-skeleton-bar);
  box-shadow: none;
  /* the same dashed tentative border as the mobile year ribbon above —
     `FAMMorocco ?` was ringed in rgba(51,81,138,.7) at 390px (chroma sweep,
     2026-08-08) */
  border-color: var(--fc-skeleton-bar);
}
.fc-m-month[data-state='loading'] .fc-m-bar__label,
.fc-m-month[data-state='loading'] .fc-m-bar__badge { color: transparent; background: transparent; }
