/**
 * VolunteerPress opportunities-calendar view styles.
 *
 * Loaded only on pages that contain the calendar block, on top of the shared
 * design-token layer (assets/css/tokens.css, the `vpress-tokens` dependency).
 *
 * Direction: clean, minimal, restrained — closer to a native Apple calendar than
 * to a colourful dashboard. VolunteerPress owns the structure (compact type scale,
 * spacing rhythm, shapes, states) while colour and typography come from the host
 * theme through the tokens: theme-derived accent, inherited font family, an OWNED
 * (deliberately small) type size, and surfaces/borders tinted from the local text
 * colour so they adapt to light and dark themes. Hierarchy is carried by weight,
 * size, and whitespace — not by boxes and fills. Colour is a restrained accent,
 * never a fill: availability is one small coloured signal (a dot + text), not a
 * pill.
 *
 * Availability is driven by the `data-state` the server already emits
 * (open/unlimited/full) plus the closed treatment; the state is never recomputed
 * here. The theme-adaptive colour derivation (semantic hues and accent roles
 * blended toward the local text colour, translucent tints) lives in the token
 * layer (tokens.css) — this sheet only consumes the finished tokens, which carry
 * their own plain fallbacks for browsers without color-mix. The few remaining
 * color-mix washes here are paired with a preceding plain-token fallback.
 */

.vpress-calendar {
	display: flex;
	flex-direction: column;
	gap: var( --vpress-space-6, 2rem );
	font-family: var( --vpress-font, inherit );
	font-size: var( --vpress-font-size, 0.875rem );
	line-height: var( --vpress-line, 1.45 );
}

/*
 * Honour the `hidden` attribute. The Interactivity API toggles regions (loading,
 * error, the closed/available signals, the pre-hydration toolbar) by adding and
 * removing `hidden`; the server render also ships those regions hidden. Any
 * `display` an author rule sets would otherwise win over the UA
 * `[hidden] { display: none }` rule and defeat the hide — which is exactly what
 * showed the loading + error panels at once, and put a "closed" label, a spots
 * count, and a signup link on the same row. This keeps hidden meaning hidden,
 * including on the no-JS server render.
 */
.vpress-calendar [hidden] {
	display: none !important;
}

/* -------------------------------------------------------------------------- */
/* Toolbar — one quiet control cluster (nav + view toggle).                   */
/* -------------------------------------------------------------------------- */

.vpress-calendar__toolbar {
	display: flex;
	flex-wrap: wrap;
	gap: var( --vpress-space-3, 0.75rem ) var( --vpress-space-4, 1rem );
	align-items: center;
	justify-content: space-between;
}

.vpress-calendar__nav {
	display: flex;
	gap: var( --vpress-space-2, 0.5rem );
}

/* The view toggle reads as one segmented control: a bordered track holding both
   options, the pressed one lifted onto a subtle raised surface. */
.vpress-calendar__views {
	display: flex;
	padding: 0.15rem;
	border: 1px solid var( --vpress-border );
	border-radius: var( --vpress-radius );
	background: var( --vpress-surface );
}

/* Shared, colour-neutral chrome for the calendar controls. */
.vpress-calendar__nav-button,
.vpress-calendar__view-button,
.vpress-calendar__retry {
	cursor: pointer;
	font: inherit;
	font-size: var( --vpress-text-md, 0.8125rem );
	font-weight: 500;
	line-height: var( --vpress-line-tight, 1.25 );
	color: inherit;
	padding: 0.4em 0.85em;
	border-radius: var( --vpress-radius-sm, 0.375rem );
	border: 1px solid var( --vpress-border );
	background: transparent;
	transition: background-color 0.15s ease, border-color 0.15s ease,
		color 0.15s ease;
}

.vpress-calendar__nav-button:hover,
.vpress-calendar__retry:hover {
	background: var( --vpress-surface );
	border-color: var( --vpress-border-strong );
}

/* View toggle segments sit flush inside the track: no individual border. */
.vpress-calendar__view-button {
	border: 0;
	background: transparent;
	border-radius: calc( var( --vpress-radius ) - 0.15rem );
	padding: 0.35em 0.9em;
}

.vpress-calendar__view-button:hover {
	background: var( --vpress-surface );
}

/* The pressed view is the current view: a raised segment and a heavier label
   (weight + surface, not colour alone; aria-pressed carries it to assistive tech). */
.vpress-calendar__view-button[aria-pressed="true"] {
	color: var( --vpress-accent-text );
	background: var( --vpress-surface-raised );
	font-weight: 600;
}

.vpress-calendar__nav-button[disabled] {
	cursor: default;
	opacity: 0.4;
}

.vpress-calendar__nav-button[disabled]:hover {
	background: transparent;
	border-color: var( --vpress-border );
}

/* Focus-visible is part of the design: a clear accent ring on every control. */
.vpress-calendar__nav-button:focus-visible,
.vpress-calendar__view-button:focus-visible,
.vpress-calendar__retry:focus-visible,
.vpress-calendar__signup:focus-visible,
.vpress-calendar__signup-button:focus-visible,
.vpress-calendar__upcoming:focus-visible,
.vpress-calendar__opportunity-link:focus-visible,
.vpress-calendar__cell-link:focus-visible,
.vpress-calendar__cell-more:focus-visible,
.vpress-calendar__day-panel-close:focus-visible {
	outline: 2px solid var( --vpress-focus, currentColor );
	outline-offset: 2px;
	border-radius: var( --vpress-radius-sm, 0.375rem );
}

/* -------------------------------------------------------------------------- */
/* Async + empty states — quiet and designed, never default.                  */
/* -------------------------------------------------------------------------- */

.vpress-calendar__loading {
	display: flex;
	align-items: center;
	gap: var( --vpress-space-2, 0.5rem );
	margin: 0;
	padding: var( --vpress-space-2, 0.5rem ) 0;
	font-size: var( --vpress-text-md, 0.8125rem );
	opacity: 0.7;
}

/* A small pulsing dot so a loading region reads as active, honouring reduced motion. */
.vpress-calendar__loading::before {
	content: "";
	width: 0.5em;
	height: 0.5em;
	border-radius: 50%;
	background: var( --vpress-accent );
	animation: vpress-calendar-pulse 1.2s ease-in-out infinite;
}

@keyframes vpress-calendar-pulse {
	0%,
	100% {
		opacity: 0.3;
	}
	50% {
		opacity: 1;
	}
}

@media ( prefers-reduced-motion: reduce ) {
	.vpress-calendar__loading::before {
		animation: none;
	}
}

.vpress-calendar__error {
	display: flex;
	flex-wrap: wrap;
	gap: var( --vpress-space-2, 0.5rem ) var( --vpress-space-3, 0.75rem );
	align-items: center;
	padding: var( --vpress-space-3, 0.75rem );
	border-radius: var( --vpress-radius );
	border: 1px solid var( --vpress-border );
	border-color: color-mix( in srgb, var( --vpress-error ) 35%, transparent );
	background: transparent;
}

.vpress-calendar__error-text {
	margin: 0;
	font-size: var( --vpress-text-md, 0.8125rem );
	color: var( --vpress-error );
}

.vpress-calendar__truncated {
	margin: 0 0 var( --vpress-space-3, 0.75rem );
	font-size: var( --vpress-text-sm, 0.75rem );
	opacity: 0.65;
}

/* The empty-month state reads as deliberate, not a stray sentence: it follows
   the grid in the DOM (caption first in reading order) and takes the same calm,
   centred, token-spaced treatment as the agenda's empty state. */
.vpress-calendar__month-empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var( --vpress-space-3, 0.75rem );
	margin: var( --vpress-space-4, 1rem ) 0 0;
	padding: var( --vpress-space-5, 1.5rem ) var( --vpress-space-4, 1rem );
	text-align: center;
	font-size: var( --vpress-text-md, 0.8125rem );
	line-height: var( --vpress-line, 1.45 );
}

/* The empty state is a calm, centred message with generous whitespace — considered
   through restraint, not decoration. The window-aware message variants and the
   route back to the upcoming window (M9.7d) stack on the same centred axis. */
.vpress-calendar__empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var( --vpress-space-3, 0.75rem );
	margin: 0;
	padding: var( --vpress-space-6, 2rem ) var( --vpress-space-4, 1rem );
	text-align: center;
	font-size: var( --vpress-text-md, 0.8125rem );
	line-height: var( --vpress-line, 1.45 );
}

/* The muting lives on the message text, not the empty-state container, so the
   "View upcoming shifts" action inside keeps its full-strength affordance. */
.vpress-calendar__empty-text {
	margin: 0;
	opacity: 0.65;
}

/* -------------------------------------------------------------------------- */
/* Agenda — quiet rows, one availability signal, one action, room to breathe.  */
/* -------------------------------------------------------------------------- */

.vpress-calendar__agenda {
	display: flex;
	flex-direction: column;
	gap: var( --vpress-space-4, 1rem );
}

/* The agenda's month caption (M9.7f/M9.7h) sits at the grid caption's type
   level — the same string at the same size, so toggling views keeps one
   consistent window indicator. */
.vpress-calendar__heading {
	margin: 0;
	font-size: var( --vpress-text-xl, 1.0625rem );
	font-weight: 600;
	line-height: var( --vpress-line-tight, 1.25 );
}

.vpress-calendar__days {
	display: flex;
	flex-direction: column;
	gap: var( --vpress-space-5, 1.5rem );
}

.vpress-calendar__day {
	display: flex;
	flex-direction: column;
	gap: var( --vpress-space-2, 0.5rem );
}

/* Date group heading: a small, quiet label over a hairline for rhythm. */
.vpress-calendar__date {
	margin: 0;
	padding-bottom: var( --vpress-space-1, 0.25rem );
	border-bottom: 1px solid var( --vpress-border );
	font-size: var( --vpress-text-sm, 0.75rem );
	font-weight: 600;
	letter-spacing: 0.01em;
	opacity: 0.7;
}

.vpress-calendar__shifts {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var( --vpress-space-2, 0.5rem );
}

/* The shift row: a light hairline card, no fill at rest, a subtle surface on hover.
   Whitespace and weight do the work. The M16.4 hierarchy (from the real-device
   pass, documented in DESIGN.md): title one type-scale step above the body base
   at the row's heaviest weight, datetime at body size with secondary emphasis,
   and the availability signal set apart by weight plus whitespace — the space-2
   row gap is what separates the wrapped meta unit from the title/time cluster
   on narrow screens. */
.vpress-calendar__shift {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var( --vpress-space-2, 0.5rem ) var( --vpress-space-4, 1rem );
	padding: var( --vpress-space-3, 0.75rem ) var( --vpress-space-4, 1rem );
	border-radius: var( --vpress-radius );
	border: 1px solid var( --vpress-border );
	background: transparent;
	transition: border-color 0.15s ease, background-color 0.15s ease;
}

.vpress-calendar__shift:hover {
	background: var( --vpress-surface );
}

/* Fixed-width, tabular time column so start times line up down the page.
   Body size at normal weight, muted a step (M16.4): clearly legible, clearly
   subordinate to the title beside it. */
.vpress-calendar__time {
	flex: 0 0 auto;
	min-width: 7.5rem;
	font-variant-numeric: tabular-nums;
	font-size: var( --vpress-font-size, 0.875rem );
	font-weight: 400;
	white-space: nowrap;
	opacity: 0.75;
}

/* The opportunity title is the clear anchor of the row and takes the slack:
   one type-scale step above the body base, the row's only weight-700 text
   (M16.4). */
.vpress-calendar__opportunity {
	flex: 1 1 12rem;
	font-size: var( --vpress-text-lg, 0.9375rem );
	font-weight: 700;
	line-height: var( --vpress-line-tight, 1.25 );
}

.vpress-calendar__opportunity-link {
	color: inherit;
	text-decoration: none;
}

.vpress-calendar__opportunity-link:hover {
	text-decoration: underline;
}

/* Availability as ONE restrained signal: a small coloured dot + coloured text, no
   pill or fill. The state tokens arrive pre-blended toward the local text colour
   (tokens.css), so the signal stays legible on light and dark themes. Its
   distinction in the M16.4 hierarchy is weight (600 against the datetime's 400)
   plus whitespace — the end-aligned meta unit and the row's space-2 gaps keep it
   clear of the title/time cluster — never a fill. */
.vpress-calendar__availability {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	gap: 0.4em;
	font-size: var( --vpress-text-sm, 0.75rem );
	font-weight: 600;
	white-space: nowrap;
}

.vpress-calendar__availability::before {
	content: "";
	width: 0.45em;
	height: 0.45em;
	border-radius: 50%;
	background: currentColor;
	flex: 0 0 auto;
}

.vpress-calendar__availability[data-state="open"],
.vpress-calendar__availability[data-state="unlimited"] {
	color: var( --vpress-state-available );
}

.vpress-calendar__availability[data-state="full"] {
	color: var( --vpress-state-limited );
}

/* Closed reads as muted grey text — not a chip. */
.vpress-calendar__status--closed {
	flex: 0 0 auto;
	font-size: var( --vpress-text-sm, 0.75rem );
	font-weight: 500;
	color: var( --vpress-state-closed );
}

/* Status + action read as ONE unit (M9.7b): the availability signal (or the
   muted closed label) and the row's action share this wrapper, so they stay
   grouped, centre-aligned, and token-spaced whichever state the shift is in —
   no layout jump between open, full, and closed. margin-inline-start:auto is
   inert while the row fits on one line (the growing title column absorbs the
   slack first) but, when the row wraps, it keeps the whole unit pinned to the
   row's end edge instead of letting it slump to the start under the time.
   min-width:0 releases the flex min-content floor and max-width:100% caps the
   unit at the row width, so a long status label can never push the action past
   the card border — the unit wraps internally instead (status above, action
   below, both still end-aligned via justify-content). */
.vpress-calendar__shift-meta {
	flex: 0 0 auto;
	min-width: 0;
	max-width: 100%;
	display: inline-flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: flex-end;
	gap: var( --vpress-space-2, 0.5rem ) var( --vpress-space-3, 0.75rem );
	margin-inline-start: auto;
}

/* The row's action, strengthened into a compact secondary button (M9.7b):
   accent-derived border and label over a transparent background, filling with
   an accent tint on hover and focus-visible. Since M10.3 the primary carrier
   of the treatment is the dialog-launching signup button; the "View details"
   anchor shares it only until hydration (the no-JS state — see the hydration
   split below), and the past-empty state's "View upcoming shifts" action
   (M9.7d) shares it always. */
.vpress-calendar__shift .vpress-calendar__signup-button,
.vpress-calendar__shift .vpress-calendar__signup,
.vpress-calendar__upcoming {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: var( --vpress-space-1, 0.25rem ) var( --vpress-space-3, 0.75rem );
	border: 1px solid var( --vpress-accent-border );
	border-radius: var( --vpress-radius-sm, 0.375rem );
	background: transparent;
	font-size: var( --vpress-text-sm, 0.75rem );
	font-weight: 600;
	line-height: var( --vpress-line-tight, 1.25 );
	white-space: nowrap;
	text-decoration: none;
	color: var( --vpress-accent-text );
	transition: background-color 0.15s ease, border-color 0.15s ease;
}

/* The navigating affordances carry the arrow; the signup button acts in
   place (it opens the dialog), so its label stays bare. */
.vpress-calendar__shift .vpress-calendar__signup::after,
.vpress-calendar__upcoming::after {
	content: " \2192"; /* → */
}

.vpress-calendar__shift .vpress-calendar__signup-button:hover,
.vpress-calendar__shift .vpress-calendar__signup-button:focus-visible,
.vpress-calendar__shift .vpress-calendar__signup:hover,
.vpress-calendar__shift .vpress-calendar__signup:focus-visible,
.vpress-calendar__upcoming:hover,
.vpress-calendar__upcoming:focus-visible {
	background: var( --vpress-accent-tint );
	border-color: var( --vpress-accent-border-strong );
}

/* The shared rule above assumes an anchor; the signup button and the "View
   upcoming shifts" action are real <button>s (they act in the page, not
   navigate it), so they reset the UA button chrome the anchor never had. */
.vpress-calendar__shift .vpress-calendar__signup-button,
.vpress-calendar__upcoming {
	cursor: pointer;
	font-family: inherit;
}

/* -------------------------------------------------------------------------- */
/* Hydration split (M10.3) — progressive enhancement for the row's actions.   */
/* -------------------------------------------------------------------------- */

/* The signup button opens the store-driven dialog, so it cannot work before
   the view module runs: until callbacks.init puts the hydrated class on the
   root, the button never displays — the server ships it in the markup (with
   its `hidden` binding carrying actionability) but this gate keeps it out of
   the layout, the tab order, and the accessibility tree on the no-JS render.
   Gating on the root class (rather than flipping `hidden` in init) lets the
   per-shift `hidden` binding keep one meaning — actionability — while the
   [hidden]-reset at the top of this sheet still wins inside the hydrated
   state, so a non-actionable shift's button stays hidden either way. */
.vpress-calendar:not( .vpress-calendar--hydrated ) .vpress-calendar__signup-button {
	display: none;
}

/* Once the button is live it takes over as the row's primary affordance, and
   the "View details" anchor demotes to a plain, smaller text link beside it —
   still a real URL, still the navigation path, just no longer dressed as the
   row's action. */
.vpress-calendar--hydrated .vpress-calendar__shift .vpress-calendar__signup {
	border: 0;
	padding: 0;
	font-size: var( --vpress-text-xs, 0.6875rem );
	font-weight: 500;
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

.vpress-calendar--hydrated .vpress-calendar__shift .vpress-calendar__signup:hover,
.vpress-calendar--hydrated .vpress-calendar__shift .vpress-calendar__signup:focus-visible {
	background: transparent;
	text-decoration-thickness: 2px;
}

/* A closed shift is genuinely, visibly muted: dimmed, on a dashed hairline, flat.
   A past shift (M9.7e, status "Ended") shares the exact treatment — both are
   non-actionable rows that stay listed but read as inert. */
.vpress-calendar__shift--closed,
.vpress-calendar__shift--past {
	opacity: 0.55;
	background: transparent;
	border-style: dashed;
}

.vpress-calendar__shift--closed:hover,
.vpress-calendar__shift--past:hover {
	background: transparent;
}

/* -------------------------------------------------------------------------- */
/* Month grid — the agenda's restraint in a fixed 7-column calendar.           */
/* -------------------------------------------------------------------------- */

.vpress-calendar__grid {
	width: 100%;
	table-layout: fixed;
	border-collapse: separate;
	border-spacing: 0;
	font-variant-numeric: tabular-nums;
	border: 1px solid var( --vpress-border );
	border-radius: var( --vpress-radius );
	overflow: hidden;
}

.vpress-calendar__grid-caption {
	caption-side: top;
	text-align: start;
	margin-bottom: var( --vpress-space-3, 0.75rem );
	font-size: var( --vpress-text-xl, 1.0625rem );
	font-weight: 600;
	line-height: var( --vpress-line-tight, 1.25 );
}

.vpress-calendar__weekday {
	padding: var( --vpress-space-2, 0.5rem );
	text-align: start;
	font-size: var( --vpress-text-xs, 0.6875rem );
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	opacity: 0.6;
	background: var( --vpress-surface );
}

/* The abbreviation carries the full weekday name for assistive tech; no underline. */
.vpress-calendar__weekday abbr[title] {
	text-decoration: none;
	cursor: help;
}

.vpress-calendar__cell {
	vertical-align: top;
	width: calc( 100% / 7 );
	height: 6rem;
	padding: var( --vpress-space-1, 0.25rem );
	border-top: 1px solid var( --vpress-border );
	border-left: 1px solid var( --vpress-border );
	overflow: hidden;
	transition: background-color 0.15s ease;
}

/* The first column meets the grid frame; drop the doubled edge. */
.vpress-calendar__cell:first-child {
	border-left: 0;
}

.vpress-calendar__cell:hover {
	background: var( --vpress-surface );
}

/* Roving-tabindex focus must be clearly visible; honour the theme's focus token. */
.vpress-calendar__cell:focus {
	outline: 2px solid var( --vpress-focus, currentColor );
	outline-offset: -2px;
}

.vpress-calendar__cell:focus:not( :focus-visible ) {
	outline: none;
}

.vpress-calendar__cell-date {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 1.5em;
	height: 1.5em;
	padding: 0 0.3em;
	margin-bottom: var( --vpress-space-1, 0.25rem );
	font-size: var( --vpress-text-sm, 0.75rem );
	font-weight: 600;
	opacity: 0.8;
}

/* Days from the neighbouring months fill the grid edges but are not primary. */
.vpress-calendar__cell--outside {
	background: var( --vpress-surface );
	opacity: 0.45;
}

/* Today: a subtle accent date badge plus a tinted cell, echoed via aria-current. */
.vpress-calendar__cell--today {
	background: var( --vpress-today-bg, rgba( 0, 0, 0, 0.04 ) );
}

.vpress-calendar__cell--today .vpress-calendar__cell-date {
	opacity: 1;
	font-weight: 700;
	color: var( --vpress-accent-text );
	background: var( --vpress-surface-raised );
	background: color-mix( in srgb, var( --vpress-accent-text ) 16%, transparent );
	border-radius: var( --vpress-radius-pill );
}

.vpress-calendar__cell-shifts {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var( --vpress-space-1, 0.25rem );
}

/* In-cell entry (M9.6b): ONE quiet line per shift — availability dot, compact
   start time, truncating title link. Stacked entries on a busy day stay
   distinguishable by their time. The dot carries the same state-hue system the
   narrow dot mode uses, so both widths speak one colour language; the full
   time range and spot count live in the link's accessible name, not the cell. */
.vpress-calendar__cell-shift {
	display: flex;
	align-items: center;
	gap: 0.35em;
	/* The entry is bounded by the fixed cell: min-width:0 lets this nested
	   flex item shrink below its content, and max-width:100% keeps the line
	   from ever spilling past the cell's inner edge into the next column.
	   Together they let the truncating title link resolve to an ellipsis
	   instead of overflowing. */
	min-width: 0;
	max-width: 100%;
	/* Containing block for the title link's stretched-link overlay below. */
	position: relative;
	font-size: var( --vpress-text-xs, 0.6875rem );
	line-height: var( --vpress-line-tight, 1.25 );
	padding: 0.15em 0.35em;
	border-radius: var( --vpress-radius-sm, 0.375rem );
	background: var( --vpress-surface );
}

/* The availability signal: one small dot, hue by state. */
.vpress-calendar__cell-dot {
	flex: 0 0 auto;
	width: 0.5em;
	height: 0.5em;
	border-radius: 50%;
	background: var( --vpress-border-strong );
}

.vpress-calendar__cell-shift[data-state="open"] .vpress-calendar__cell-dot,
.vpress-calendar__cell-shift[data-state="unlimited"] .vpress-calendar__cell-dot {
	background: var( --vpress-state-available );
}

.vpress-calendar__cell-shift[data-state="full"] .vpress-calendar__cell-dot {
	background: var( --vpress-state-limited );
}

/* The compact start time ("5pm") never truncates; it is what tells stacked
   same-day entries apart. */
.vpress-calendar__cell-time {
	flex: 0 0 auto;
	font-weight: 600;
	white-space: nowrap;
	opacity: 0.8;
}

/* Keep a long opportunity title from stretching the fixed-width cell: min-width:0
   lets the flex child shrink so the ellipsis engages, and the width cap holds it
   inside the cell. */
.vpress-calendar__cell-link {
	flex: 1 1 auto;
	color: inherit;
	font-weight: 600;
	text-decoration: none;
	min-width: 0;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * The entry's link affordances are the unhydrated state's only (M10.4): the
 * store removes the anchor's href on hydration (state.cellEntryHref — no link
 * role, no tab stop), and these rules are gated on the same root class so the
 * appearance tracks the semantics exactly — the M10.3 pattern. Hydrated, the
 * entry is plain content of the cell's single day-panel target: no underline
 * invitation, no private hit area (the cell's own cursor:pointer inherits).
 */
.vpress-calendar:not( .vpress-calendar--hydrated ) .vpress-calendar__cell-link:hover {
	text-decoration: underline;
}

/* Stretched link, unhydrated only: the anchor's hit area covers the whole
   entry line (dot and compact time included) via an overlay pinned to the
   entry's positioned box, so the no-JS entry is clickable edge to edge. The
   anchor node and its accessible name are untouched. */
.vpress-calendar:not( .vpress-calendar--hydrated ) .vpress-calendar__cell-link::after {
	content: "";
	position: absolute;
	inset: 0;
}

/*
 * A closed or past (M9.7e) entry is genuinely muted, matching the agenda. The
 * doubled class ties with the `[data-state]` dot-hue rules above on specificity,
 * so source order lets the muted grey win — the dot stops signalling
 * availability — regardless of the shift's underlying state.
 */
.vpress-calendar__cell-shift.vpress-calendar__cell-shift--closed,
.vpress-calendar__cell-shift.vpress-calendar__cell-shift--past {
	opacity: 0.55;
	background: transparent;
}

.vpress-calendar__cell-shift.vpress-calendar__cell-shift--closed .vpress-calendar__cell-dot,
.vpress-calendar__cell-shift.vpress-calendar__cell-shift--past .vpress-calendar__cell-dot {
	background: var( --vpress-state-closed );
}

/* The "+N more" affordance on an overflow day: a quiet text button whose click
   bubbles to the cell and opens the day panel (which lists every shift of the
   day). Hidden until hydration — the panel needs the store. */
.vpress-calendar__cell-more {
	cursor: pointer;
	display: inline-block;
	margin-top: var( --vpress-space-1, 0.25rem );
	padding: 0.15em 0.35em;
	border: 0;
	background: transparent;
	font: inherit;
	font-size: var( --vpress-text-xs, 0.6875rem );
	font-weight: 600;
	line-height: var( --vpress-line-tight, 1.25 );
	color: inherit;
	opacity: 0.7;
	border-radius: var( --vpress-radius-sm, 0.375rem );
}

.vpress-calendar__cell-more:hover {
	opacity: 1;
	background: var( --vpress-surface );
}

/* A shift-bearing cell's background is the day-panel trigger at any width. */
.vpress-calendar__cell--has-shifts {
	cursor: pointer;
}

/*
 * The selected day (its panel is open): an accent-tinted cell finished with a
 * solid accent bar along its bottom edge. Reads with — but distinctly from —
 * the today tint (a soft wash with no bar) and the keyboard focus ring (a full
 * outline).
 */
.vpress-calendar__cell--selected {
	background: var( --vpress-surface-raised );
	background: color-mix( in srgb, var( --vpress-accent-text ) 14%, transparent );
	box-shadow: inset 0 -3px 0 var( --vpress-accent );
}

/* The day panel: the selected day's shifts as agenda-style rows below the grid
   (the rows reuse the agenda item classes wholesale). A JS-only surface at any
   viewport width — it ships [hidden] and only the store reveals it. */
.vpress-calendar__day-panel {
	display: flex;
	flex-direction: column;
	gap: var( --vpress-space-3, 0.75rem );
	margin-top: var( --vpress-space-3, 0.75rem );
	padding: var( --vpress-space-3, 0.75rem );
	border: 1px solid var( --vpress-border );
	border-radius: var( --vpress-radius );
}

.vpress-calendar__day-panel-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var( --vpress-space-3, 0.75rem );
}

.vpress-calendar__day-panel-heading {
	margin: 0;
	font-size: var( --vpress-text-lg, 0.9375rem );
	font-weight: 600;
	line-height: var( --vpress-line-tight, 1.25 );
}

/* The close affordance matches the calendar's control chrome. */
.vpress-calendar__day-panel-close {
	cursor: pointer;
	font: inherit;
	font-size: var( --vpress-text-md, 0.8125rem );
	font-weight: 500;
	line-height: var( --vpress-line-tight, 1.25 );
	color: inherit;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0.4em 0.85em;
	border-radius: var( --vpress-radius-sm, 0.375rem );
	border: 1px solid var( --vpress-border );
	background: transparent;
}

.vpress-calendar__day-panel-close:hover {
	background: var( --vpress-surface );
	border-color: var( --vpress-border-strong );
}

/* -------------------------------------------------------------------------- */
/* Responsive — a tighter, list-favoured layout on narrow screens.            */
/*                                                                            */
/* One breakpoint, the existing 600px query. Below it: the toolbar wraps into */
/* full-width rows, every control becomes a 44px touch target, and the month  */
/* grid's in-cell entries collapse to availability dots. The day panel itself */
/* is width-independent (base rules above); only dot mode lives here.         */
/* -------------------------------------------------------------------------- */

@media ( max-width: 600px ) {
	/* Toolbar: let the nav and the view toggle each take a full row so the
	   controls wrap cleanly instead of colliding, with token-consistent gaps. */
	.vpress-calendar__toolbar {
		gap: var( --vpress-space-3, 0.75rem );
	}

	.vpress-calendar__nav {
		flex: 1 1 100%;
		justify-content: space-between;
	}

	.vpress-calendar__views {
		flex: 1 1 100%;
	}

	/*
	 * Touch targets: every interactive control clears the 44px minimum on narrow
	 * screens. Expressed in rem (2.75rem == 44px at the ~16px root) so the target
	 * grows with the user's font-size preference rather than staying pinned to a
	 * device pixel.
	 */
	.vpress-calendar__nav-button,
	.vpress-calendar__view-button,
	.vpress-calendar__retry {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-height: 2.75rem;
	}

	.vpress-calendar__nav-button {
		min-width: 2.75rem;
	}

	/* The segmented view toggle fills its row, each option an equal tappable half. */
	.vpress-calendar__view-button {
		flex: 1 1 0;
	}

	/* Agenda row actions carry a 44px tap height without disturbing the row rhythm. */
	.vpress-calendar__opportunity-link,
	.vpress-calendar__shift .vpress-calendar__signup-button,
	.vpress-calendar__shift .vpress-calendar__signup,
	.vpress-calendar__upcoming {
		display: inline-flex;
		align-items: center;
		min-height: 2.75rem;
	}

	/* A long status label ("Full — new signups join the waitlist.") can be
	   wider than a narrow row all by itself. The meta unit is already capped
	   at the row width (base rule), so here the label drops nowrap and wraps
	   its text instead of overflowing the card on screens down to 320px. */
	.vpress-calendar__availability {
		max-width: 100%;
		white-space: normal;
	}

	.vpress-calendar__time {
		min-width: 0;
		flex-basis: 100%;
	}

	.vpress-calendar__cell {
		height: 5rem;
	}

	/*
	 * Dot mode (M9.6b). Below the breakpoint each in-cell entry collapses to
	 * its availability dot (same hue system as the desktop entry). The day
	 * panel — not visually-hidden in-cell content — is the accessible path to
	 * a day's shifts at this width, so the entry's time and title link leave
	 * the display AND the accessibility tree here. The "+N more" affordance
	 * stays: the dots are capped, so without it an overflow day reads as
	 * exactly N dots and under-reports the day (restyled compact below).
	 * Pure CSS: the panel itself, its trigger wiring, and the selected-cell
	 * state are width-independent and live in the base rules above.
	 */
	.vpress-calendar__cell-shifts {
		flex-direction: row;
		flex-wrap: wrap;
		align-items: center;
		gap: var( --vpress-space-1, 0.25rem );
	}

	.vpress-calendar__cell-shift {
		flex: 0 0 auto;
		padding: 0;
		border-radius: 0;
		background: transparent;
	}

	.vpress-calendar__cell-dot {
		width: 0.5rem;
		height: 0.5rem;
	}

	.vpress-calendar__cell-time,
	.vpress-calendar__cell-link {
		display: none;
	}

	/*
	 * The overflow affordance in dot mode: a compact muted "+N" sitting inline
	 * with the dot row — no chrome, trimmed padding, the same quiet text-xs +
	 * reduced-opacity treatment secondary text uses elsewhere. It needs no
	 * 44px target of its own: its click bubbles to the cell, whose full
	 * tap area (the 5rem day cell) is the panel trigger at this width.
	 */
	.vpress-calendar__cell-more {
		margin-top: 0;
		padding: 0 0.15em;
		font-size: var( --vpress-text-xs, 0.6875rem );
		line-height: 1;
	}

	.vpress-calendar__cell-more:hover {
		background: transparent;
	}

	/* The panel close affordance grows to the 44px touch target. */
	.vpress-calendar__day-panel-close {
		min-height: 2.75rem;
		min-width: 2.75rem;
	}

	/* The dialog's Close button gets the same 44px touch target. */
	.vpress-dialog__close {
		min-height: 2.75rem;
		min-width: 2.75rem;
	}
}

/* -------------------------------------------------------------------------- */
/* Signup dialog shell (M10.1) — a native <dialog>, platform-first.           */
/*                                                                            */
/* Opened only with showModal(), so the top layer, focus trap, and Esc come   */
/* from the platform. The same "hidden means hidden" discipline as the        */
/* [hidden] reset above: NO `display` on the bare .vpress-dialog — the UA's   */
/* dialog:not([open]) { display: none } is what keeps the closed shell out of */
/* the page — and only the [open] state opts into flex layout.               */
/*                                                                            */
/* Colour: the UA's opaque Canvas/CanvasText base pair is kept — it flips     */
/* with the page's color-scheme, so no hard-coded white or black — and the    */
/* token surface is layered over it as a background-image tint, the same way  */
/* every other vpress surface tints whatever it sits on. The currentColor-    */
/* derived tokens (border, surface, focus) then re-derive against the         */
/* dialog's own text colour, so the chrome stays legible on either scheme.    */
/* -------------------------------------------------------------------------- */

.vpress-dialog {
	/* Sized by token: the full 40rem where there is room, the viewport minus
	   a token gutter where there is not; capped to the viewport with room to
	   breathe (the dvh variant so mobile browser chrome never clips it). */
	width: min( 40rem, calc( 100vw - 2 * var( --vpress-space-4, 1rem ) ) );
	max-height: calc( 100vh - 2 * var( --vpress-space-6, 2rem ) );
	max-height: calc( 100dvh - 2 * var( --vpress-space-6, 2rem ) );
	/* No padding of its own: the header and content regions tile the whole
	   inner box (and pad themselves), so a click landing on the dialog node
	   itself can only be a backdrop click — the JS close contract. */
	padding: 0;
	border: 1px solid var( --vpress-border );
	border-radius: var( --vpress-radius );
	/* The token surface tint over the UA's opaque Canvas base (see the
	   section note); background-image leaves background-color untouched. */
	background-image: linear-gradient( var( --vpress-surface ), var( --vpress-surface ) );
	font-family: var( --vpress-font, inherit );
	font-size: var( --vpress-font-size, 0.875rem );
	line-height: var( --vpress-line, 1.45 );
}

/* Layout only while open — the closed dialog stays on the UA's display:none.
   Column flex so the content region can own the internal scroll. */
.vpress-dialog[open] {
	display: flex;
	flex-direction: column;
}

/* The scrim: derived from the document's text colour (CanvasText flips with
   the colour scheme) — never hard-coded black, which would disappear into a
   dark theme. The plain fallback is the token layer's neutral mid-grey. */
.vpress-dialog::backdrop {
	background: rgba( 128, 128, 128, 0.5 );
}

@supports ( color: color-mix( in srgb, red, blue ) ) {
	.vpress-dialog::backdrop {
		background: color-mix( in srgb, CanvasText 42%, transparent );
	}
}

.vpress-dialog__header {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var( --vpress-space-3, 0.75rem );
	padding: var( --vpress-space-4, 1rem ) var( --vpress-space-5, 1.5rem );
	border-bottom: 1px solid var( --vpress-border );
}

/* The title is the programmatic focus target on open (tabindex="-1");
   M10.2 fills it per shift. It is a flex child beside the Close button:
   min-width:0 releases the min-content floor so a long opportunity title
   wraps inside its column instead of shoving the Close button (or, at
   320px, escaping the dialog), and overflow-wrap covers the unbroken-word
   worst case. */
.vpress-dialog__title {
	margin: 0;
	min-width: 0;
	overflow-wrap: anywhere;
	font-size: var( --vpress-text-xl, 1.0625rem );
	font-weight: 600;
	line-height: var( --vpress-line-tight, 1.25 );
}

/* The close affordance matches the calendar's control chrome. */
.vpress-dialog__close {
	cursor: pointer;
	font: inherit;
	font-size: var( --vpress-text-md, 0.8125rem );
	font-weight: 500;
	line-height: var( --vpress-line-tight, 1.25 );
	color: inherit;
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0.4em 0.85em;
	border-radius: var( --vpress-radius-sm, 0.375rem );
	border: 1px solid var( --vpress-border );
	background: transparent;
	transition: background-color 0.15s ease, border-color 0.15s ease;
}

.vpress-dialog__close:hover {
	background: var( --vpress-surface );
	border-color: var( --vpress-border-strong );
}

/* Focus ring per the shared tokens — the same treatment as every calendar
   control. */
.vpress-dialog__close:focus-visible {
	outline: 2px solid var( --vpress-focus, currentColor );
	outline-offset: 2px;
	border-radius: var( --vpress-radius-sm, 0.375rem );
}

/* The content region owns the internal scroll: the header stays pinned while
   long signup content scrolls under it. The top edge steps down to space-4,
   matching the header's own vertical rhythm, so the shift details sit with
   the header hairline instead of floating a full gutter below it (M10.4
   polish); the sides and bottom keep the space-5 gutter. */
.vpress-dialog__content {
	flex: 1 1 auto;
	min-height: 0;
	overflow: auto;
	padding: var( --vpress-space-4, 1rem ) var( --vpress-space-5, 1.5rem )
		var( --vpress-space-5, 1.5rem );
}

/* -------------------------------------------------------------------------- */
/* Dialog content (M10.2, rows since M16.5): the selected shift's details as  */
/* stacked rows — icon + value, one fact per line — so a phone reader can     */
/* tell what each value means. Icons are decorative currentColor strokes;    */
/* the availability/status value reuses the day-panel classes                */
/* (.vpress-calendar__availability / __status), so the signal reads exactly  */
/* as it does in the rows that launched this dialog. Detail text never drops */
/* below the 0.875rem body base in the modal (M16.5 legibility floor).       */
/* -------------------------------------------------------------------------- */

.vpress-dialog__details {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var( --vpress-space-2, 0.5rem );
	font-size: var( --vpress-font-size, 0.875rem );
}

.vpress-dialog__detail {
	display: flex;
	align-items: flex-start;
	gap: var( --vpress-space-2, 0.5rem );
	margin: 0;
}

/* The row icon: decorative (aria-hidden in the markup — the text carries the
   meaning), drawn in currentColor so it inherits whatever the theme gives the
   dialog text, and quieted so the values stay the loudest thing in the row.
   Sized in rem against the row's first text line. */
.vpress-dialog__detail-icon {
	flex: 0 0 auto;
	width: 1.125rem;
	height: 1.125rem;
	margin-top: 0.1em;
	opacity: 0.65;
}

.vpress-dialog__when {
	display: flex;
	flex-wrap: wrap;
	column-gap: var( --vpress-space-3, 0.75rem );
	font-weight: 600;
}

.vpress-dialog__time {
	font-variant-numeric: tabular-nums;
	font-weight: 500;
	opacity: 0.8;
}

.vpress-dialog__role,
.vpress-dialog__location {
	opacity: 0.75;
}

/* The day-panel status/availability classes bring their own smaller type
   size; inside the modal's detail rows they take the row's body-base size
   instead (the M16.5 legibility floor). Colour, dot, and weight are theirs. */
.vpress-dialog__detail .vpress-calendar__status--closed,
.vpress-dialog__detail .vpress-calendar__availability {
	font-size: var( --vpress-font-size, 0.875rem );
}

/* The form sits under the shift details, separated on the token scale. When
   it is hidden (no actionable shift selected), the shared [hidden] reset above
   collapses it, hairline and all. */
.vpress-dialog__form {
	margin-top: var( --vpress-space-4, 1rem );
	padding-top: var( --vpress-space-4, 1rem );
	border-top: 1px solid var( --vpress-border );
}

/* The signup confirmation is a bordered, surface-tinted card where it stands
   alone in the page (the inline sheet needs the card to read as a region).
   Inside the dialog it already sits on a bordered token surface, so the card
   chrome doubles up — a box in a box on the same tint, indented from the
   details above it by its own padding. In the dialog it flattens to plain
   content (M10.4 polish): the check icon and heading carry the success
   emphasis, and the panel's token gap keeps the rhythm. */
.vpress-dialog .vpress-signup__confirmation {
	padding: 0;
	border: 0;
	background: transparent;
}

/*
 * Body scroll lock while a vpress dialog is open (the store toggles this
 * class on <html>). overflow:hidden alone would drop the scrollbar and let
 * the page reflow into the freed space; scrollbar-gutter keeps that gutter
 * reserved, so the page behind the dialog freezes exactly in place. (Where
 * scrollbar-gutter is unsupported the lock still works, at the cost of a
 * one-scrollbar-width shift.)
 */
html.vpress-scroll-lock {
	overflow: hidden;
	scrollbar-gutter: stable;
}

/*
 * Visually-hidden but screen-reader-available live region. Mirrors core's
 * .screen-reader-text so it works even under themes that do not define it.
 */
.vpress-calendar__live {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect( 0 0 0 0 );
	border: 0;
	white-space: nowrap;
}
