.calendar-view {
  --calendar-accent: #2563eb;
  --calendar-bg: #f8fafc;
  --calendar-surface: #ffffff;
  --calendar-line: #e2e8f0;
  --calendar-text: #1f2328;
  --calendar-muted: color-mix(in srgb, var(--calendar-text) 58%, var(--calendar-surface));
  --calendar-event-bg: color-mix(in srgb, var(--calendar-accent) 12%, var(--calendar-surface));
  --calendar-event-border: color-mix(in srgb, var(--calendar-accent) 35%, var(--calendar-surface));
  --calendar-background-bg: color-mix(in srgb, var(--calendar-accent) 10%, transparent);
  --calendar-column-min: 10rem;
  /* Width of the time axis strip the column tracks repeat after, shared
     with the renderer's own grid template. */
  --calendar-axis-size: 3.5rem;
  /* Sticky header row heights: the resource row sits above the day headers,
     the group row above the resource row, and the sticky offsets stack the
     same way. The group row falls back to the resource height. */
  --calendar-resource-row-size: 3rem;
  --calendar-group-row-size: var(--calendar-resource-row-size);
  display: block;
  min-width: 0;
  color: var(--calendar-text);
  background: var(--calendar-bg);
  /* Typography belongs to the host application; the dense small print
     below keeps explicit sizes of its own. */
  font: inherit;
}

.calendar-view *,
.calendar-view *::before,
.calendar-view *::after {
  box-sizing: border-box;
}

.cv-scroller {
  max-height: 70vh;
  overflow: auto;
  overscroll-behavior: contain;
}

.cv-grid {
  display: grid;
  grid-template-columns: var(--calendar-axis-size) repeat(auto-fit, minmax(var(--calendar-column-min), 1fr));
  align-items: start;
  min-width: max-content;
}

.cv-axis {
  position: sticky;
  inset-inline-start: 0;
  z-index: 3;
  width: var(--calendar-axis-size);
  background: var(--calendar-bg);
}

.cv-axis-label {
  position: absolute;
  inset-inline-end: 0.5rem;
  color: var(--calendar-muted);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  /* End-aligned so a label that fills or wraps inside a narrow axis still
     reads against the column edge instead of floating to the far side. */
  text-align: end;
  transform: translateY(-0.5em);
}

/* The lift above centres a label on its own hour line, which hangs the
   first one outside the grid: over the all-day lane's border when there is
   one, under the day headers when there is not. That one label sits below
   its line instead, keeping the same optical gap. */
.cv-axis-label:first-child {
  transform: translateY(0.2em);
}

.cv-day {
  min-width: var(--calendar-column-min);
  border-inline-start: 1px solid var(--calendar-line);
  background: var(--calendar-surface);
}

.cv-day-header {
  position: sticky;
  top: 0;
  z-index: 4;
  min-height: 3rem;
  padding: 0.75rem;
  border-bottom: 1px solid var(--calendar-line);
  background: var(--calendar-surface);
  font-weight: 600;
}

.cv-resource-row {
  display: grid;
  align-items: stretch;
  min-width: max-content;
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--calendar-surface);
  border-bottom: 1px solid var(--calendar-line);
}

.cv-resource-corner {
  position: sticky;
  inset-inline-start: 0;
  z-index: 3;
  width: var(--calendar-axis-size);
  background: var(--calendar-bg);
}

.cv-resource-header {
  display: flex;
  align-items: center;
  min-height: var(--calendar-resource-row-size);
  padding: 0.75rem;
  overflow: hidden;
  border-inline-start: 1px solid var(--calendar-line);
  background: var(--calendar-surface);
  font-weight: 600;
  white-space: nowrap;
}

/* One-level resource groups: a row above the resource headers, one header
   per non-empty group spanning its member columns. Renders only when real
   members exist; ungrouped trailing columns get no header because the row
   is entirely skipped then. */
.cv-group-row {
  display: grid;
  align-items: stretch;
  min-width: max-content;
  position: sticky;
  top: 0;
  z-index: 6;
  background: var(--calendar-surface);
  border-bottom: 1px solid var(--calendar-line);
}

.cv-group-corner {
  position: sticky;
  inset-inline-start: 0;
  z-index: 3;
  width: var(--calendar-axis-size);
  background: var(--calendar-bg);
}

.cv-group-header {
  display: flex;
  align-items: center;
  min-height: var(--calendar-group-row-size);
  padding: 0.75rem;
  overflow: hidden;
  border-inline-start: 1px solid var(--calendar-line);
  background: var(--calendar-surface);
  font-weight: 600;
  white-space: nowrap;
  color: var(--calendar-muted);
}

/* All-day lane: mirrors the column tracks, sits between the sticky headers
   and the time bodies, and scrolls away with the body like FullCalendar's
   own day slot. A bar spans civil days inside one resource block only. */
.cv-allday {
  display: grid;
  align-items: stretch;
  gap: 2px;
  padding-block: 4px;
  border-bottom: 1px solid var(--calendar-line);
  background: var(--calendar-surface);
}

.cv-allday-corner {
  grid-column: 1;
  align-self: center;
  padding-inline: 0.5rem;
  color: var(--calendar-muted);
  font-size: 0.7rem;
  font-weight: 600;
  white-space: nowrap;
}

.cv-allday-event {
  z-index: 2;
  min-inline-size: 0;
  margin: 1px 2px;
  padding: 0.15rem 0.4rem;
  overflow: hidden;
  border: 1px solid var(--calendar-event-border);
  border-radius: 0.375rem;
  background: var(--calendar-event-bg);
  color: var(--calendar-text);
  font: inherit;
  font-size: 0.75rem;
  line-height: 1.3;
  text-align: start;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: grab;
  touch-action: pan-x pan-y;
}

.cv-allday-event:active {
  cursor: grabbing;
}

.cv-allday-background {
  z-index: 1;
  margin: 1px 2px;
  border-inline-start: 2px solid color-mix(in srgb, var(--calendar-accent) 40%, transparent);
  background: var(--calendar-background-bg);
  pointer-events: none;
}

/* Grouped resource headers stick above the day headers. */
.calendar-view[data-view^="resource"] .cv-day-header {
  top: var(--calendar-resource-row-size);
}

/* A real group row above the resource row pushes both sticky offsets down
   one row. The sibling selector keys off the presence of an actual
   `.cv-group-row`, so `resourceGroups` configured without matching
   resources reserves no space. */
.cv-group-row + .cv-resource-row {
  top: var(--calendar-group-row-size);
}

.calendar-view .cv-group-row ~ .cv-grid .cv-day-header {
  top: calc(var(--calendar-group-row-size) + var(--calendar-resource-row-size));
}

.cv-empty {
  grid-column: 1 / -1;
  margin: 0;
  padding: 2rem;
  color: var(--calendar-muted);
}

.cv-month {
  background: var(--calendar-surface);
}

/* Column count is set by the renderer: hidden days make the grid narrower
   than a full week. */
.cv-month-weekdays,
.cv-month-week {
  display: grid;
  grid-template-columns: repeat(var(--calendar-month-columns, 7), minmax(0, 1fr));
}

.cv-month-weekdays {
  position: sticky;
  top: 0;
  z-index: 4;
  background: var(--calendar-surface);
  border-bottom: 1px solid var(--calendar-line);
}

.cv-month-weekday {
  padding: 0.5rem 0.75rem;
  color: var(--calendar-muted);
  font-size: 0.75rem;
  font-weight: 500;
}

.cv-month-day {
  min-height: 6rem;
  padding: 0.35rem 0.45rem;
  border-bottom: 1px solid var(--calendar-line);
  border-inline-start: 1px solid var(--calendar-line);
}

.cv-month-day:first-child {
  border-inline-start: none;
}

.cv-month-day[data-outside="true"] {
  color: var(--calendar-muted);
  background: var(--calendar-bg);
}

.cv-month-day-number {
  display: block;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.cv-month-event {
  display: block;
  width: 100%;
  margin-top: 0.2rem;
  padding: 0.15rem 0.35rem;
  overflow: hidden;
  border: 1px solid var(--calendar-event-border);
  border-radius: 0.375rem;
  background: var(--calendar-event-bg);
  color: var(--calendar-text);
  font: inherit;
  font-size: 0.8rem;
  text-align: start;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

.cv-month-more {
  display: block;
  width: 100%;
  margin-top: 0.2rem;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--calendar-muted);
  font: inherit;
  font-size: 0.8rem;
  text-align: start;
  cursor: pointer;
}

.cv-month-more:focus-visible {
  outline: 2px solid var(--calendar-accent);
  outline-offset: 1px;
}

.cv-list {
  background: var(--calendar-surface);
}

.cv-list-day {
  border-bottom: 1px solid var(--calendar-line);
}

.cv-list-day-header {
  position: sticky;
  top: 0;
  z-index: 4;
  padding: 0.6rem 0.75rem;
  background: var(--calendar-surface);
  font-weight: 600;
}

.cv-list-event {
  display: block;
  width: 100%;
  padding: 0.5rem 0.75rem;
  border: none;
  border-top: 1px solid var(--calendar-line);
  background: transparent;
  color: var(--calendar-text);
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.cv-list-empty {
  margin: 0;
  padding: 0.5rem 0.75rem;
  color: var(--calendar-muted);
}

/* The focus ring stays outside the card rather than inset: consumers may
   fill an event with the accent colour itself, where an inner ring in the
   same hue would disappear. One pixel of bleed onto a neighbour is the
   smaller cost. */
.cv-event:focus-visible,
.cv-month-event:focus-visible,
.cv-list-event:focus-visible,
.cv-allday-event:focus-visible {
  outline: 2px solid var(--calendar-accent);
  outline-offset: 1px;
}

/* Reveal highlight: a temporary outline applied by `revealEvent()`/`reveal()`
   alongside `data-revealed="true"`. The core removes both after ~2s; any
   re-render removes them sooner by replacing the node. */
.cv-event.cv-reveal,
.cv-month-event.cv-reveal,
.cv-list-event.cv-reveal,
.cv-allday-event.cv-reveal {
  outline: 2px solid var(--calendar-accent);
  outline-offset: 2px;
  transition: outline-color 0.2s ease;
}

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

/* Narrow viewports change density, never semantics: smaller column minimum,
   still author-overridable, horizontal scroll preserved. */
@media (max-width: 640px) {
  .calendar-view {
    --calendar-column-min: 7.5rem;
  }
}

/* Motion inside the component is decorative; autoscroll stays functional
   because it is viewport movement, not decoration. */
@media (prefers-reduced-motion: reduce) {
  .calendar-view *,
  .calendar-view *::before,
  .calendar-view *::after {
    /* Author styles must not reanimate motion inside the component:
       `!important` is what makes the accessibility preference win. */
    /* biome-ignore lint/complexity/noImportantStyles: reduced-motion must override author animations */
    animation: none !important;
    /* biome-ignore lint/complexity/noImportantStyles: reduced-motion must override author transitions */
    transition: none !important;
    /* biome-ignore lint/complexity/noImportantStyles: reduced-motion must force instant scroll */
    scroll-behavior: auto !important;
  }
}

/* Forced colors: the UA already keeps text, borders and focus rings
   legible, so only the information that would disappear outright is
   restated in system colors — tinted fills, painted overlays, the now
   indicator and refusal markings. No parallel theme. */
@media (forced-colors: active) {
  /* Background tints vanish; a dashed contour keeps the zone legible
     without faking the fill. */
  .cv-background,
  .cv-allday-background {
    border: 1px dashed CanvasText;
    background: transparent;
  }

  /* Painted overlays lose both fill and tint: their outlines keep the
     target readable as Highlight. */
  .cv-hover,
  .cv-select-ghost,
  .cv-preview,
  .cv-external-ghost {
    border-color: Highlight;
    background: transparent;
  }

  .cv-hover-chip,
  .cv-select-chip {
    color: Highlight;
  }

  .cv-now {
    color: Highlight;
  }

  /* A refusal must not collapse into a plain overlay: Mark stays distinct
     from Highlight where the deterministic red cannot render. */
  .cv-drag-mirror.cv-invalid,
  .cv-event.cv-invalid {
    outline-color: Mark;
  }

  .cv-select-ghost.cv-invalid,
  .cv-external-ghost.cv-invalid,
  .cv-preview.cv-invalid {
    border-color: Mark;
  }

  .cv-drag-mirror[data-reason]::after,
  .cv-event.cv-invalid[data-reason]::after,
  .cv-select-ghost[data-reason]::after,
  .cv-external-ghost[data-reason]::after,
  .cv-preview[data-reason]::after {
    background: Mark;
    color: Canvas;
  }
}

.cv-day-body {
  position: relative;
}

.cv-hour-line {
  position: absolute;
  inset-inline: 0;
  border-top: 1px solid var(--calendar-line);
  pointer-events: none;
}

.cv-background {
  position: absolute;
  inset-inline: 0;
  border-inline-start: 2px solid color-mix(in srgb, var(--calendar-accent) 40%, transparent);
  background: var(--calendar-background-bg);
  pointer-events: none;
}

.cv-event {
  position: absolute;
  z-index: 2;
  overflow: hidden;
  padding: 0.35rem 0.45rem;
  border: 1px solid var(--calendar-event-border);
  border-radius: 0.375rem;
  background: var(--calendar-event-bg);
  color: var(--calendar-text);
  /* A <button> never inherits the host font on its own, and the UA default
     is denser than body text: keep the card at small-print size. */
  font: inherit;
  font-size: 0.8125rem;
  line-height: 1.3;
  text-align: start;
  cursor: grab;
  /* Pan keeps touch scrolling working when the gesture starts on an event.
     A scroll takeover fires pointercancel, which the drag/resize paths
     already treat as an abort without commit. Resize handles stay precise
     with touch-action: none. */
  touch-action: pan-x pan-y;
}

.cv-event:active {
  cursor: grabbing;
}

.cv-drag-source {
  opacity: 0.35;
}

.cv-drag-mirror {
  z-index: 6;
  margin: 0;
  opacity: 0.92;
  pointer-events: none;
  box-shadow: 0 0.5rem 1.5rem rgb(15 23 42 / 0.25);
}

.cv-drag-mirror.cv-invalid,
.cv-event.cv-invalid {
  outline: 2px dashed #dc2626;
  outline-offset: -2px;
}

/* A refusal explains itself: `data-reason` rides the painted ghost and
   renders as a chip inside it, so a refused destination says why instead of
   only turning red. The chip stays inside the (clipped) card, anchored to
   its bottom edge. All-day bars and lane ghosts are static boxes, so the
   mirror/lane variants opt into positioning to anchor the chip. */
.cv-allday-event.cv-drag-mirror,
.cv-external-ghost-lane {
  position: relative;
}

.cv-drag-mirror[data-reason]::after,
.cv-event.cv-invalid[data-reason]::after,
.cv-select-ghost[data-reason]::after,
.cv-external-ghost[data-reason]::after,
.cv-preview[data-reason]::after {
  content: attr(data-reason);
  position: absolute;
  inset-block-end: 0.15rem;
  inset-inline: 0.2rem;
  z-index: 1;
  overflow: hidden;
  padding: 0.1rem 0.35rem;
  border-radius: 0.25rem;
  background: #dc2626;
  color: #fff;
  font-size: 0.7rem;
  line-height: 1.25;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
}

/* A refused selection drag: the ghost keeps its geometry so the user still
   reads the range being drawn, and turns red like every other refusal. */
.cv-select-ghost.cv-invalid {
  border-color: #dc2626;
  background: color-mix(in srgb, #dc2626 12%, transparent);
}

/* Preview of an external placement (app-owned drag source): shows the real
   duration in the day column, or a civil bar in the all-day lane. Never
   interactive, and structural/policy invalidity turns it red. */
.cv-external-ghost {
  position: absolute;
  inset-inline: 2px;
  z-index: 5;
  border: 2px dashed color-mix(in srgb, var(--calendar-accent) 75%, transparent);
  border-radius: 0.375rem;
  background: color-mix(in srgb, var(--calendar-accent) 16%, transparent);
  pointer-events: none;
}

.cv-external-ghost-label {
  position: absolute;
  inset-block-start: 0.1rem;
  inset-inline-start: 0.35rem;
  max-inline-size: calc(100% - 0.7rem);
  overflow: hidden;
  color: var(--calendar-muted);
  font-size: 0.7rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cv-external-ghost.cv-invalid {
  border-color: #dc2626;
  background: color-mix(in srgb, #dc2626 12%, transparent);
}

/* A refused application proposal reads exactly like a refused drop ghost. */
.cv-preview.cv-invalid {
  border-color: #dc2626;
  background: color-mix(in srgb, #dc2626 12%, transparent);
}

.cv-external-ghost-lane {
  position: static;
  inset-inline: auto;
  margin: 1px 2px;
}

/* Application-proposed range overlay (`previewRange`): the real event
   geometry for a placement target while no interaction is running. Read-only
   by construction — `aria-hidden` in the DOM, never interactive, never a
   dispatch source. Interaction ghosts paint above it. */
.cv-preview {
  position: absolute;
  inset-inline: 2px;
  z-index: 4;
  border: 2px solid color-mix(in srgb, var(--calendar-accent) 75%, transparent);
  border-radius: 0.375rem;
  background: color-mix(in srgb, var(--calendar-accent) 16%, transparent);
  pointer-events: none;
}

.cv-resize-handle {
  position: absolute;
  inset-inline: 0;
  z-index: 3;
  height: 0.65rem;
  cursor: ns-resize;
  touch-action: none;
}

/* Grip affordance: a small centred pill inside each handle, revealed on
   hover/keyboard focus. The handles themselves stay invisible at rest so
   quiet cards keep clean edges. */
.cv-resize-handle::before {
  content: "";
  position: absolute;
  inset-inline-start: 50%;
  top: 50%;
  inline-size: 1.1rem;
  block-size: 0.25rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--calendar-text) 35%, transparent);
  opacity: 0;
  transform: translate(-50%, -50%);
  transition: opacity 0.12s ease;
}

.cv-event:hover .cv-resize-handle::before,
.cv-event:focus-visible .cv-resize-handle::before {
  opacity: 1;
}

.cv-resize-n {
  top: 0;
}

.cv-resize-s {
  bottom: 0;
}

/* The now indicator is one colour end to end: `currentColor` ties the line
   and its leading dot to a single `color` declaration, so a skin retints
   both by overriding `color` alone. */
.cv-now {
  position: absolute;
  inset-inline: 0;
  z-index: 2;
  border-top: 2px solid currentColor;
  color: var(--calendar-accent);
  pointer-events: none;
}

.cv-now::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  background: currentColor;
  transform: translate(-50%, -50%);
}

.cv-hover,
.cv-select-ghost {
  position: absolute;
  inset-inline: 0;
  z-index: 3;
  border: 1px dashed var(--calendar-accent);
  background: color-mix(in srgb, var(--calendar-accent) 12%, transparent);
  pointer-events: none;
}

.cv-select-ghost {
  border-style: solid;
}

.cv-hover-chip,
.cv-select-chip {
  position: absolute;
  top: 0.15rem;
  inset-inline-start: 0.35rem;
  color: var(--calendar-accent);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Selection/hover/drop overlays are implemented above; density policies
   per `data-view` (an all-day lane outside time grids, a slot-row policy)
   remain open design space rather than shipped behavior. */
