/* ============================================
   Meeting Calendar
   Moved verbatim out of public-portal.css so the dashboard can load it too —
   the dashboard does not load public-portal.css. A restyle is a separate
   decision; the only ADDITIONS here are .calendar-grid (which was missing
   entirely — the cells stacked as blocks) and the chip detail panel.
   ============================================ */

.calendar-wrapper {
  background: var(--card-bg, #fff);
  border: 1px solid var(--border-color, #dee2e6);
  border-radius: 0.5rem;
  /* MUST NOT be `overflow: hidden`. The chip detail panel (.calendar-event-detail)
     is positioned relative to its .calendar-day ancestor and can extend past the
     wrapper's edge; hiding overflow here clips the panel exactly like an
     unpositioned ancestor does. This does soften the grid's corner-clipping
     against the wrapper's border-radius — accepted tradeoff, do not "fix" by
     restoring overflow: hidden. */
}

/* --- Legacy token shadow, calendar scope only (B5, WCAG 1.4.3) -----------
   Three rules below read --text-muted / --text-secondary, and the values those
   tokens carry TODAY fail against this file's own backgrounds:

     --text-muted     #adb5bd on #f8f9fa (.calendar-day--adjacent)   1.9683:1
     --text-muted     #6e7681 on #252b3b (dark adjacent cell)        3.0735:1
     --text-secondary #6c757d on #f8f9fa (.calendar-day-header,
                                          .calendar-more)            4.4486:1

   Both tokens are declared in themes.css, which is owned by
   feat/visual-overhaul and must not be edited from this branch. Overriding
   from a later sheet is not available either: this stylesheet is loaded by the
   LOGGED-IN DASHBOARD (views/dashboard-home.ejs:15), which loads themes.css
   and no portal.css, so the calendar has to shadow the tokens on its own root.
   The embed inlines its own copies of both tokens (views/embed/calendar.ejs)
   and those are fixed there too; this block is what makes the DASHBOARD pass.

   Values are not a design choice: #5b6470 and #545c64 are the same
   replacements portal.css uses for these two tokens, and #a2abb6 is
   portal.css's own dark --text-muted. Delete this block once themes.css ships
   AA-clean tokens — nothing else here depends on it.

   THE DARK BLOCK MUST RESTATE BOTH TOKENS, including --text-secondary, whose
   dark value (#9ea7b8, 5.8314:1 on #252b3b) needs no fixing. A custom property
   set on .calendar-wrapper beats the one inherited from :root REGARDLESS of
   theme, so a light-only shadow does not just miss dark — it pushes #545c64
   onto the dark day-header row at 2.0787:1, worse than the failure being
   fixed. Same trap as the body.public-portal block in portal.css. */
.calendar-wrapper {
  --text-muted: #5b6470;      /* 5.6893:1 on #f8f9fa */
  --text-secondary: #545c64;  /* 6.4434:1 on #f8f9fa */
}
[data-theme="dark"] .calendar-wrapper {
  --text-muted: #a2abb6;      /* 6.0758:1 on #252b3b */
  --text-secondary: #9ea7b8;  /* unchanged from themes.css: 5.8314:1 on #252b3b */
}

/* --- Accent as INK vs accent as a SURFACE (WCAG 1.4.11 Non-text Contrast) --
   THE RULE THIS FILE NOW FOLLOWS:

     accent used as a SURFACE (something is drawn ON it)  -> --primary-color,
        with the text/glyph colour coming from --primary-text (textOn()).
     accent used as INK (label text, dot fill, border, focus outline, ring)
        -> --primary-fg, which src/routes/embed.js gates before emitting it.

   B4 applied the second half to TEXT only. The accent is also ink for several
   NON-TEXT indicators — the dot-tier dot fill, the cancelled dot's ring, the
   "+" overflow dot, the today ring, and every :focus-visible outline — and
   those are 1.4.11 (3:1), a different criterion from the 4.5:1 text work.

   MEASURED, default accent #667eea (i.e. what the dashboard renders, which
   this change leaves pixel-identical because every fallback below stays
   #667eea): 3.4723:1 on the #f8f9fa header, 3.6602:1 on a white cell,
   3.3918:1 on a .has-meeting cell (#f4f6fe), 3.1404:1 on a .today cell
   (#eaedfc), 4.7261:1 / 4.0117:1 / 3.4994:1 on the dark equivalents. Every one
   clears 3:1, so nothing fails TODAY WITHOUT A CUSTOM ACCENT.

   WHAT DOES FAIL is an org-supplied embed accent: ?accent=ffe680 renders dots
   and focus rings at ~1.2:1. Routing these through --primary-fg closes that,
   because --primary-fg is already gated per theme.

   WHY --primary-fg IS SAFE HERE AT 3:1 rather than needing its own gate:
   it is gated to >= 4.5:1 against #e7e9fd and #ffffff (light) / #262f4a
   (dark). Every light surface listed above has a HIGHER relative luminance
   than #e7e9fd (0.8236 vs 0.8509-1.0), so a passing light accent is >= 4.5:1
   on all of them; the lightest dark surface (#28314e, 0.0320) is only
   marginally lighter than #262f4a (0.0294), costing at most a 3.1% ratio drop
   — 4.5 -> 4.36, still comfortably over 3. That argument is what closes the
   gaps; a property test over 472 SAMPLED accents (not the full space) backs it
   up in tests/unit/portal/embed-accent.test.js. */

.calendar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  background: var(--bg-secondary, #f8f9fa);
  border-bottom: 1px solid var(--border-color, #dee2e6);
}

.calendar-title {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--text-primary, #212529);
  margin: 0;
}

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

.calendar-nav-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 0.25rem;
  border: 1px solid var(--border-color, #dee2e6);
  background: var(--card-bg, #fff);
  color: var(--text-primary, #212529);
  text-decoration: none;
  font-size: 1rem;
  line-height: 1;
}
.calendar-nav-btn:hover { background: var(--bg-secondary, #f8f9fa); }
.calendar-nav-btn:focus-visible { outline: 2px solid var(--primary-fg, #667eea); outline-offset: 1px; }

.calendar-day-header {
  text-align: center;
  padding: 0.5rem;
  font-weight: 600;
  font-size: 0.75rem;
  text-transform: uppercase;
  /* Fallback raised from #6c757d (4.4486:1 on this rule's own #f8f9fa
     background — a live 1.4.3 failure, not a theoretical one). See the token
     shadow at the top of this file. */
  color: var(--text-secondary, #545c64);
  background: var(--bg-secondary, #f8f9fa);
}

.calendar-day {
  position: relative;
  padding: 0.5rem;
  aspect-ratio: 1;
  container-type: inline-size;
  /* Flex column so .day-events can be flex:1/min-height:0 and take only the
     leftover height — the cell's height then comes purely from aspect-ratio
     (strictly square) instead of growing to fit stacked chips. The cell stays
     overflow:visible so the popover can still escape below it. */
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border-color, #dee2e6);
  border-right: 1px solid var(--border-color, #dee2e6);
}

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

.calendar-day-number {
  font-size: 0.875rem;
  font-weight: 500;
  /* Fallback raised from #6c757d: 4.6893:1 on a plain white cell but 4.3453:1
     on a .has-meeting cell (#f4f6fe) and 4.0230:1 on a .today cell (#eaedfc),
     both below 4.5. The token shadow at the top of this file is what fixes the
     rendered case; this keeps the fallback honest. */
  color: var(--text-secondary, #545c64);
}

.calendar-day.today .calendar-day-number {
  background: var(--primary-color, #667eea);
  /* Text ON the accent background, not a fixed "white" -- a pale org accent
     (embed only; dashboard has no per-org accent) needs dark text here. The
     embed template sets --primary-text via textOn(accent); everywhere else
     (no per-org accent) the fallback keeps today's badge exactly as before. */
  color: var(--primary-text, #ffffff);
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
}

/* Meeting days: a soft ambient tint so the whole cell reads as "something is
   here", without shouting over the event chip it already contains. */
.calendar-day.has-meeting {
  background: rgba(102, 126, 234, 0.07);
}

/* Current day: strong and unmistakable — a filled tint plus a solid inset ring.
   The ring is `inset` so it paints inside the cell and never nudges the grid.
   Declared AFTER .has-meeting so on a day that is BOTH today and a meeting day
   today wins (equal specificity, later source order). */
.calendar-day.today {
  background: rgba(102, 126, 234, 0.14);
  box-shadow: inset 0 0 0 2px var(--primary-fg, #667eea);
}

/* Adjacent-month padding days: present so every month is a stable six rows,
   but muted so "this month" clearly dominates. Never interactive. */
.calendar-day--adjacent {
  background: var(--bg-secondary, #f8f9fa);
}

/* Adjacent-month day numbers are real dates, not decoration, so 1.4.3 applies
   at 4.5:1. The shipped `var(--text-muted, #adb5bd)` measured 1.9683:1 against
   this cell's own #f8f9fa background — the worst text failure in the file. The
   dark rule is NOT optional: without it the light fallback (and, absent the
   token shadow above, themes.css's #6e7681 at 3.0735:1) would be what a
   dark-theme dashboard renders. */
.calendar-day--adjacent .calendar-day-number {
  color: var(--text-muted, #5b6470);      /* 5.6893:1 on #f8f9fa */
}

[data-theme="dark"] .calendar-day--adjacent .calendar-day-number {
  color: var(--text-muted, #a2abb6);      /* 6.0758:1 on #252b3b */
}

/* A non-link chip (a meeting with no public share_token yet) still reads as a
   chip but without the pointer affordance. */
.calendar-event--static {
  cursor: default;
}

[data-theme="dark"] .calendar-day--adjacent {
  background: var(--bg-tertiary, #252b3b);
}

.calendar-event {
  font-size: 0.75rem;
  padding: 0.125rem 0.25rem;
  border-radius: 0.25rem;
  background: var(--primary-bg, #e7e9fd);
  /* The accent used AS text, not text on the accent -- textOn() cannot help
     here (see src/routes/embed.js, safeForegroundAccent()). #1e5aa8 is the
     vetted light-theme default (5.67:1 / 6.81:1 against this rule's own
     tint / white); the embed sets --primary-fg per-request, gated against
     the SAME surfaces, per theme. The dashboard (no per-org accent, no
     --primary-fg) relies on this fallback directly -- #667eea/#2563eb (the
     embed's and dashboard's previous effective colours) both measured below
     4.5:1 against this tint, a live failure independent of any custom
     accent. */
  color: var(--primary-fg, #1e5aa8);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}

.calendar-event:hover {
  background: var(--primary-color, #667eea);
  /* Same guard as the today badge above: text drawn ON the accent. */
  color: var(--primary-text, #ffffff);
}

/* NEW. The seven day-headers and every day cell (blank padding cells included)
   must be direct children of this one element: .calendar-day:nth-child(7n)
   clears the Saturday right-border by counting position within the parent.
   With 7 headers first, day cell i is child 7+i, so 7n lands on every Saturday.
   Wrapping the headers separately silently breaks the border grid. */
.calendar-grid {
  display: grid;
  /* minmax(0, 1fr), NOT bare 1fr. A bare `1fr` track is `minmax(auto, 1fr)`,
     whose `auto` minimum is the cell's min-content width — and .calendar-event
     is `white-space: nowrap`, so a long committee name makes that min-content
     wider than 1/7 of the row. The tracks then refuse to shrink, the seven
     columns push past the calendar's grid column, and the whole page scrolls
     sideways (the event chip's `text-overflow: ellipsis` never gets to engage).
     minmax(0, 1fr) lets each column shrink below its content so the ellipsis
     truncates instead. Same fix the outer .featured-calendar-row row uses. */
  grid-template-columns: repeat(7, minmax(0, 1fr));
}

/* The chip opens a real element rather than a CSS-only :hover reveal: there is
   no hover on touch, and WCAG 1.4.13 requires the content be dismissible. The
   detail panel (.calendar-event-detail) is a SIBLING of the chip, not a child,
   so it cannot anchor to the chip itself. It anchors to their shared parent,
   .calendar-day (`position: relative` above), which is why the panel's
   `top: 100%; left: 0` reads as "just below this day cell". */
.calendar-event {
  /* flex + align-items:center, not block: the 24px slot floor (2.5.8, below) is
     taller than the label's line box, and a block would leave the text hugging
     the top of the box (3px above, 10px below at the 560px preset). Purely
     optical — the box geometry is identical either way. Same reasoning as the
     .portal-hero__ics group in portal.css. */
  display: flex;
  align-items: center;
  width: 100%;
  text-align: left;
  border: none;
  font: inherit;
}

/* With .calendar-event as a flex container, `text-overflow: ellipsis` on it no
   longer truncates the label — the label is a flex ITEM, so the truncation has
   to move onto it (min-width:0 lets the item shrink below its content width,
   which flex items refuse to do by default). Without this a long committee
   name is hard-clipped mid-glyph instead of ellipsised. */
.calendar-event-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.calendar-event:focus-visible {
  outline: 2px solid var(--primary-fg, #667eea);
  outline-offset: 1px;
}

.calendar-event-detail {
  display: none;
  position: absolute;
  z-index: 20;
  left: 0;
  top: 100%;
  min-width: 15rem;
  padding: 0.75rem;
  white-space: normal;
  background: var(--card-bg, #fff);
  border: 1px solid var(--border-color, #dee2e6);
  border-radius: 0.375rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  color: var(--text-primary, #212529);
  font-size: 0.75rem;
}

/* Saturday cells sit at the grid's right edge, so a left-anchored 15rem panel
   overhangs the wrapper and can push horizontal scroll onto the whole page.
   Hang the last column's panels leftward instead. Same nth-child(7n) counting
   as the Saturday border rule: with 7 day-headers first, day cell i is child
   7+i, so 7n lands on each Saturday. */
.calendar-day:nth-child(7n) .calendar-event-detail {
  left: auto;
  right: 0;
}

.calendar-event-detail.is-open {
  display: block;
}

/* Moved verbatim from the mobile breakpoint in public-portal.css. */
@media (max-width: 768px) {
  .calendar-event {
    font-size: 0.625rem;
  }
}

/* Moved verbatim from the dark-theme block in public-portal.css. The dashboard
   has a theme toggle, so dropping these would give it a light calendar in dark
   mode — easy to miss when you only ever test one theme. */
[data-theme="dark"] .calendar-header {
  background: var(--bg-tertiary, #252b3b);
}

[data-theme="dark"] .calendar-day-header {
  background: var(--bg-tertiary, #252b3b);
}

[data-theme="dark"] .calendar-nav-btn {
  background: var(--bg-tertiary, #252b3b);
}

[data-theme="dark"] .calendar-event {
  background: rgba(102, 126, 234, 0.2);
  /* Dark gets its OWN fallback, not #1e5aa8 above -- that colour is 1.94:1
     against this rule's actual rendered surface (this rgba flattened over
     --card-bg dark #161b22 is ~#262f4a). #6ea8f0 is 5.37:1 here and is the
     dark-theme half of the same portal.css-matched pair (src/routes/embed.js,
     SAFE_ACCENT_FG_DARK). The embed's own --primary-fg is already gated for
     whichever theme is being rendered, so this only fires for the dashboard
     (no per-org accent, no --primary-fg). */
  color: var(--primary-fg, #6ea8f0);
}

[data-theme="dark"] .calendar-event:hover {
  background: var(--primary-color, #667eea);
  /* MUST restate the colour. `[data-theme="dark"] .calendar-event` above is
     (0,2,0) and so is `.calendar-event:hover` at :253 -- equal specificity, and
     the dark rule comes LATER, so without this line the dark hover chip draws
     the ACCENT-coloured text (#6ea8f0) on the ACCENT fill (#667eea): 1.4854:1.
     Restoring --primary-text puts white back on the fill (3.6602:1 at the
     hardcoded dashboard fallback, and textOn()-computed on the embed, where
     --primary-color is the real accent). Worse than the ratio, a pale org
     accent that clears the embed's dark 4.5:1 gate makes --primary-fg EQUAL
     --primary-color, i.e. 1:1 and literally invisible. Pinned by the ':hover'
     rows in tests/unit/portal/portal-contrast.test.js -- do not delete. */
  color: var(--primary-text, #ffffff);
}

/* NEW, and required by the addition above: the detail panel must follow the
   theme too, or it renders as a white card over a dark calendar. */
[data-theme="dark"] .calendar-event-detail {
  background: var(--bg-tertiary, #252b3b);
  border-color: var(--border-color, #3a4152);
}

/* Day highlights need a touch more alpha to register on the darker cell fill;
   the today ring keeps its primary color, so it carries over unchanged. */
[data-theme="dark"] .calendar-day.has-meeting {
  background: rgba(102, 126, 234, 0.13);
}

[data-theme="dark"] .calendar-day.today {
  background: rgba(102, 126, 234, 0.22);
}

/* ============================================================
   Chip tiers (container-query driven per day cell) + dot mode
   ============================================================ */

/* Chips live in their own flex container so the dot tier can lay them out in a
   row while the day number stays above. */
.day-events {
  display: flex;
  flex-direction: column;
  /* gap 0, not 0.15rem: every slot is exactly 24px (see the 2.5.8 block below)
     and 2.5.8 wants adjacent target centres >= 24px apart, so a 24px box with a
     zero gap gives a pitch of exactly 24. Any gap on top of that would spend
     vertical room the shorter cells do not have. Touching chips still read as
     separate: they keep their own background and 0.25rem corner radius. */
  gap: 0;
  /* 2px, was 0.25rem. Bought back for the 24px slots -- see the 2.5.8 block. */
  margin-top: 2px;
  overflow: hidden;
  /* Take only the cell's leftover height (below the day number) and clip extra
     chips, so a busy meeting day never grows its cell taller than square. */
  flex: 1;
  min-height: 0;
}

/* Cancelled: struck through in the text tiers, a hollow ring in the dot tier.
   Styling alone is not a status announcement (WCAG 1.4.1) - the chip's
   accessible name also carries a "Cancelled: " text equivalent via .sr-only
   below (meeting-calendar.ejs, link mode only). */
.calendar-event--cancelled .calendar-event-label {
  text-decoration: line-through;
}

/* Visually hidden but exposed to assistive tech. The embed inlines only this
   stylesheet, so it cannot rely on portal.css's copy. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* The overflow link reads as a muted sibling of the chips in the text tiers.
   The dot tier restyles it as a small "+" dot further down. */
.calendar-more {
  /* flex + centred for the same reason as .calendar-event above: the 24px slot
     floor is taller than the label's line box. */
  display: flex;
  align-items: center;
  width: 100%;
  padding: 0.125rem 0.25rem;
  border-radius: 0.25rem;
  background: var(--bg-secondary, #f8f9fa);
  /* Fallback raised from #6c757d — 4.4486:1 on this rule's own #f8f9fa
     background. Same pair as .calendar-day-header above. */
  color: var(--text-secondary, #545c64);
  font-size: 0.75rem;
  white-space: nowrap;
  overflow: hidden;
  text-decoration: none;
  border: 1px solid var(--border-color, #dee2e6);
}
.calendar-more__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Same accent-as-foreground guard as .calendar-event above.
   A DARK OVERRIDE IS REQUIRED, and the comment that used to sit here claimed
   the opposite -- it asserted "no `[data-theme=\"dark\"]` override of ITS OWN
   exists for this selector" while that very override sat two lines below.
   `:hover` adds a pseudo-CLASS, so `.calendar-more:hover` is (0,2,0) -- exactly
   the same specificity as `[data-theme="dark"] .calendar-more`, which comes
   LATER and therefore wins the background. Dark hover consequently painted the
   LIGHT accent fallback #1e5aa8 on --bg-tertiary #252b3b: 2.0729:1. The dark
   rule below restores the pairing at 5.7298:1 with the same dark accent
   .calendar-event uses. Pinned by the ':hover' rows in
   tests/unit/portal/portal-contrast.test.js. */
.calendar-more:hover { background: var(--primary-bg, #e7e9fd); color: var(--primary-fg, #1e5aa8); }
.calendar-more:focus-visible { outline: 2px solid var(--primary-fg, #667eea); outline-offset: 1px; }
[data-theme="dark"] .calendar-more { background: var(--bg-tertiary, #252b3b); }
/* (0,3,0), so it beats BOTH rules above regardless of source order. The
   background is deliberately left as the dark rule paints it (--bg-tertiary):
   the hover affordance in dark theme is the text going from --text-secondary
   grey to the accent, and measuring the fallback against --bg-tertiary is what
   the 5.7298:1 above refers to. */
[data-theme="dark"] .calendar-more:hover { color: var(--primary-fg, #6ea8f0); }

/* --- Tier 2: smaller font when the cell gets tight --- */
@container (max-width: 4.9rem) {
  .calendar-event { font-size: 0.62rem; }
  .calendar-more { font-size: 0.62rem; }
  /* line-height 1 and no top margin on .day-events: at the 560px "Large"
     preset the day number plus cell padding was eating 37.8px of an 80px cell,
     leaving 42.2px — not enough for two 24px slots. Tightening the number's
     leading and the chip gutter yields 52px, which holds two. Tier 2 only, so
     the roomy dashboard cells keep their original spacing. */
  .calendar-day-number { font-size: 0.75rem; line-height: 1; }
  .day-events { margin-top: 0; }
}

/* ========================================================================
   WCAG 2.5.8 Target Size (Minimum) — 24px slots plus a per-width chip budget
   ========================================================================

   THE RULE. Every target in a day cell is a 24x24 box and the gap is 0, so
   adjacent target centres are exactly 24px apart. That satisfies 2.5.8 on the
   size requirement itself, with the Spacing exception (24px-diameter circles
   centred on each bounding box must not intersect) as the belt-and-braces
   check across cell boundaries. Nothing here leans on the exception alone.

   WHY A BUDGET IS NEEDED. Every cell has identical geometry, so if n targets in
   one cell sit s apart, the distance to the next cell's first target is
   cell - (n-1)s. Requiring both >= 24 forces cell >= n * 24. The grid is 7
   columns of the widget width, and .calendar-day is aspect-ratio: 1, so both
   axes are bounded by the same number:

     dot tier  (targets in a row)     budget = floor(cell / 24)
     text tier (targets in a column)  budget = floor(.day-events height / 24)

   A day with more meetings than its budget therefore CANNOT show them all. It
   shows budget-1 chips plus one "All N meetings" link (.calendar-more) that
   carries the rest — a real 24x24 target to an existing listing page. This also
   satisfies 2.5.8's Equivalent exception, and it is why .calendar-more is now a
   link with an accessible name instead of the decorative "+" span it was.

   THIS REPLACES THE B2 CLIPPING. The previous pass kept over-budget chips in
   the accessibility tree by clipping them (visually hidden but focusable) and
   revealing them on focus. That is superseded: a focusable-but-invisible chip
   is a target a pointer user cannot see, and reachability now comes from the
   link. Over-budget chips are display:none — genuinely gone from the tree, with
   the link's "All N meetings" naming what was dropped. Do not reinstate the
   clip/reveal rules alongside this.

   WHY THE BUDGET IS CSS AND THE LINK IS SERVER-RENDERED. The budget depends on
   the RENDERED cell width, which the server cannot know: the embed iframe is
   width:100%, so a council's narrow sidebar can render a "Large" widget at
   320px. Container queries are the only thing that sees the real width. The
   server therefore renders every chip plus, for any day with 2+ meetings, one
   link naming the day's TOTAL count — a string that stays true at every width —
   and CSS decides how many chips survive.

   Thresholds below are container inline-size (the cell's CONTENT box);
   cell border box = content + 17px (0.5rem padding each side + 1px border).
   Verified in headless Chromium at 320 / 420 / 560 and at a dashboard width.
   ------------------------------------------------------------------------ */

/* The slot. Both target kinds, every tier. The VISIBLE chip/dot is unchanged —
   in the dot tier the box is transparent and only the ::before dot paints — so
   this grows hit areas, not the design. */
.calendar-event,
.calendar-more {
  min-width: 24px;
  min-height: 24px;
}

/* Widest cells: 3 slots => 2 chips + the link. */
.day-events--capped .calendar-event:nth-child(n+3) { display: none; }

/* content < 98.4px (cell < 115px): the column holds 2 slots => 1 chip + link. */
@container (max-width: 98.3px) {
  .day-events--capped .calendar-event:nth-child(n+2) { display: none; }
}

/* content < 62px (cell < 79px): the column holds 1 slot => the link only.
   The dot tier overrides this again below — it lays targets out in a ROW, so a
   narrow-but-not-tiny cell fits two of them side by side even though it is too
   short to stack two. */
@container (max-width: 61.9px) {
  .day-events--capped .calendar-event { display: none; }
}

/* --- Tier 3: dots. Text hidden (kept for assistive tech); each dot sized
   relative to the cell (cqmin) so it scales and never clips; one row. --- */
@container (max-width: 3.25rem) {
  .day-events {
    flex-direction: row;
    flex-wrap: nowrap;
    /* gap 0 for the same reason as the column: the slots are exactly 24px wide,
       so a zero gap puts their centres exactly 24px apart. */
    gap: 0;
    align-items: center;
    overflow: visible;
  }
  .calendar-event {
    width: auto;
    padding: 0;                  /* the 24px slot IS the tap target now */
    background: transparent;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    overflow: visible;
  }
  .calendar-event:hover { background: transparent; }
  .calendar-event-label {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  .calendar-event::before {
    content: "";
    width: min(8px, 17cqmin);
    height: min(8px, 17cqmin);
    border-radius: 50%;
    background: var(--primary-fg, #667eea);
  }
  .calendar-event--cancelled::before {
    background: transparent;
    border: 1.5px solid var(--primary-fg, #667eea);
  }
  /* The overflow link becomes the same kind of transparent 24px slot as a chip,
     with the shipped "+" dot drawn inside it by ::before. Its label is hidden
     the same way a chip's is; the .sr-only half of its accessible name is not
     affected, so it still announces "All N meetings on <date>".
     The ::before below uses `content: "+" / ""` — generated content lands in
     the accessible name in Chromium, so without the empty alt the name reads
     "+ All 2 meetings on ..." with a stray plus in front of it. */
  .calendar-more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: auto;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 50%;
    overflow: visible;
  }
  .calendar-more:hover { background: transparent; }
  .calendar-more__label {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  .calendar-more::before {
    content: "+" / "";      /* empty alt: decorative, keep it out of the name */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: min(8px, 17cqmin);
    height: min(8px, 17cqmin);
    border-radius: 50%;
    background: var(--primary-fg, #667eea);
    /* --primary-fg-text, NOT --primary-text. The circle above is now filled
       with --primary-fg (the 1.4.11 change), and --primary-text is
       textOn(ACCENT) -- for an accent that failed the gate the two disagree
       and you get e.g. black-on-navy. --primary-fg-text is textOn(--primary-fg)
       and is emitted alongside it (src/routes/embed.js). It is guaranteed
       >= 4.5:1 by construction: --primary-fg passes 4.5:1 against #ffffff in
       light (so white sits on it at >= 4.5:1) and against #262f4a in dark (so
       anything at least that dark, black included, does).
       Was var(--card-bg, #fff) originally -- see git history. */
    color: var(--primary-fg-text, #ffffff);
    font-size: min(8px, 17cqmin);
    font-weight: 800;
    line-height: 1;
  }

  /* Budget in the dot tier: targets sit in a ROW, so capacity comes from the
     cell's WIDTH, floor(cell / 24). Content <= 52px means cell <= 69px, so this
     tier holds at most 2 -- and the (max-width: 61.9px) column rule above has
     already hidden every chip, so chip 1 is put back here. */
  .day-events--capped .calendar-event:nth-child(1) { display: inline-flex; }
  .day-events--capped .calendar-event:nth-child(n+2) { display: none; }
}

/* content < 31px (cell < 48px): one 24px slot, full stop — the link only.
   Two 24px slots need cell >= 48, and cell = content + 17, so the boundary is
   content >= 31 — NOT 30. Written as `max-width: 30px` this rule missed the
   47.14-47.86px cells that widget widths 330-335px produce: the dot tier
   re-showed chip 1, two 24px boxes were laid in a sub-48px cell, and they
   physically OVERLAPPED across the cell boundary (minPair 23.14px), so a
   pointer near a cell's right edge hit the next day's chip instead of this
   day's link. An 8px-step sweep straddles that 6px band, which is how it
   shipped; sweep at 1px steps. Reachable on the responsive dashboard too.

   `:nth-child(n+1)` rather than a bare `.calendar-event` is equally
   load-bearing: a container query does not add specificity, so this has to
   MATCH the `:nth-child(1)` re-show above (0,3,0) to beat it on source order.
   Written without it, this rule is (0,2,0), silently loses, and the 320px
   preset ships two targets 21.7px apart. Both traps are pinned by tests in
   tests/unit/portal/portal-contrast.test.js — do not loosen those regexes. */
@container (max-width: 30.99px) {
  .day-events--capped .calendar-event:nth-child(n+1) { display: none; }
}
