/* SCF Sale Calendar: month grid, sale bars, channel bubbles, detail notes, quarter timeline, drag states,
   phone agenda. Tokens from tokens.css only, plus the fixed --ch-* channel data colours declared below.
   Only transform and opacity animate. Curves are --ease and --spring. */

/* 0. Channel colours. A categorical data set, not a second brand accent, so they are fixed here and every
      surface that draws a channel reads them from these names. Dark mode lifts each to a legible tint of the
      same hue under both dark guards. */
:root {
  --ch-email: #2F6099;
  --ch-sms: #5F7D6A;
  --ch-site: #8A6A1F;
  --ch-meta: #B34A38;
  --ch-instagram: #7A4A7E;
  --ch-google: #4A7C8C;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ch-email: #7FAAD8;
    --ch-sms: #93B39F;
    --ch-site: #D9B45A;
    --ch-meta: #E08871;
    --ch-instagram: #C296C6;
    --ch-google: #86BACB;
  }
}
:root[data-theme="dark"] {
  --ch-email: #7FAAD8;
  --ch-sms: #93B39F;
  --ch-site: #D9B45A;
  --ch-meta: #E08871;
  --ch-instagram: #C296C6;
  --ch-google: #86BACB;
}

/* 1. Month grid. The grid carries its own scroller so a tall week never pushes the page, and the weekday
      header is sticky at the top of that scroller (contract 13.5). */
.cal-host { display: flex; flex-direction: column; }
/* The height comes from the box above rather than from an arithmetic on the viewport. #main is a definite
   100dvh and #view is a flex child of it, so .cal filling #view hands .cal-scroll a real height to scroll
   inside whatever the topbar actually measures. */
.cal {
  flex: 1;
  display: flex; flex-direction: column;
  min-height: 0;
  position: relative;
  -webkit-user-select: none; user-select: none;
}
/* The thumb is --ghost rather than --rule-2: on a thin scrollbar the paler token was the only sign the grid
   could move at all, and a reader who cannot see the bar reports that the grid does not scroll. */
.cal-scroll {
  flex: 1; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  overscroll-behavior-y: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--ghost) transparent;
}
.cal-scroll:focus { outline: none; }
.cal-head {
  position: sticky; top: 0; z-index: 7;
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  height: 32px; flex: none;
  border-bottom: 1px solid var(--rule);
  background: var(--linen);
}
.cal-dow { display: flex; align-items: center; padding: 0 10px; font: 700 11px/1.2 var(--f-ui); letter-spacing: .18em; text-transform: uppercase; color: var(--muted); border-right: 1px solid var(--rule); }
.cal-dow:last-child { border-right: 0; }
.cal-dow.is-weekend { color: var(--ghost); }

.cal-body { display: flex; flex-direction: column; position: relative; overflow-anchor: none; }
.cal-body.is-creating { cursor: crosshair; }
/* The week row takes its height from its cells, so a cell full of notes grows the row rather than clipping it.
   The lane layer floats over the cells and keeps the bars where the day columns put them. */
.cal-week { position: relative; border-bottom: 1px solid var(--rule); }
.cal-week:last-child { border-bottom: 0; }
.cal-cells { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); min-height: inherit; }
.cal-day {
  position: relative; min-width: 0;
  /* A column, so the blank day note can be pushed to the foot of the square where an empty day invites a
     double click, without it taking any room on a day that has nothing written on it. */
  display: flex; flex-direction: column;
  padding: var(--lane-h, 30px) 0 8px;
  border-right: 1px solid var(--rule); background: var(--linen); cursor: cell;
}
.cal-day:last-child { border-right: 0; }
.cal-day.is-weekend { background: var(--linen-2); }
.cal-num {
  position: absolute; top: 5px; left: 7px;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 22px; padding: 0 4px; border-radius: 999px;
  font: 600 14px/1 var(--f-display); color: var(--ink);
  pointer-events: none;
}
.cal-day.is-today .cal-num { background: var(--accent-strong); color: var(--accent-soft); }
/* The run of weeks never breaks, so two things and only two say where one month ends and the next begins:
   a heavier rule across the week a month opens on, and the month's own name on the numeral of its first,
   which reads "1 Sep" rather than "1" and picks up the year again every January. Everything else is
   identical from row to row, which is what makes it read as one planner rather than as a stack of pages. */
.cal-week.is-month-open { border-top: 2px solid var(--ink); }
.cal-week.is-month-open:first-child { border-top: 0; }
.cal-num.is-month {
  padding: 0 7px;
  font-weight: 800;
  letter-spacing: .01em;
  color: var(--ink);
}
.cal-lanes { position: absolute; inset: 0; pointer-events: none; z-index: 2; }
.cal-lanes > * { pointer-events: auto; }
.cal-lanes > .cal-ghost, .cal-lanes > .bar-skel { pointer-events: none; }

.cal-empty {
  position: absolute; left: 0; right: 0; top: 50%;
  margin: 0; padding: 0 24px; text-align: center;
  font: 500 14px/1.4 var(--f-ui); color: var(--muted);
  pointer-events: none; z-index: 3; transform: translateY(-50%);
}

/* 2. Bars. A solid fill of the kind colour with the title in the kind ink (contract 13.2). Readiness is a 4px
      rule along the bottom inside edge, filled left to right, so it never sits behind a word. The rule ink is
      the kind ink on a dark kind and --accent-ink on a light one.
      The kind fill lives here rather than in an inline style so one rule can carry a correction: the clearance
      grey is mixed one step toward --ink because linen type on the raw token measured 4.32 to 1, under the 4.5
      a 12.5px title needs. Every value is still built from tokens.css. */
.bar {
  --rk: var(--ki);
  position: absolute; height: 26px;
  z-index: 1;
  cursor: grab;
  border-radius: 0;
  outline-offset: 2px;
}
.bar.k-half { --k: var(--k-half); --ki: var(--k-half-ink); }
.bar.k-tail { --k: var(--k-tail); --ki: var(--k-tail-ink); }
.bar.k-new { --k: var(--k-new); --ki: var(--k-new-ink); }
.bar.k-clear { --k: color-mix(in srgb, var(--k-clear) 85%, var(--ink)); --ki: var(--k-clear-ink); }
.bar.k-flash { --k: var(--k-flash); --ki: var(--k-flash-ink); }
.bar.k-other { --k: var(--k-other); --ki: var(--k-other-ink); }
.bar.k-tail, .bar.k-other { --rk: var(--accent-ink); }
.bar:active { cursor: grabbing; }
.bar-in {
  position: absolute; inset: 0;
  overflow: hidden;
  border-radius: inherit;
  background: var(--k);
  color: var(--ki);
  transition: transform 120ms var(--ease);
}
.bar.is-start, .bar.is-start .bar-in { border-top-left-radius: var(--r-sm); border-bottom-left-radius: var(--r-sm); }
.bar.is-end, .bar.is-end .bar-in { border-top-right-radius: var(--r-sm); border-bottom-right-radius: var(--r-sm); }
/* The text layer clips at the bar's right edge and fades out over the last 18px instead of ending in a glyph.
   The right padding matches the fade, so a title that fits is never touched by it. */
.bar-text {
  position: absolute; inset: 0;
  display: flex; align-items: center; gap: 6px;
  padding: 0 18px 0 8px;
  white-space: nowrap; overflow: hidden; text-overflow: clip;
  font: 500 12.5px/1 var(--f-ui);
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
}
.bar-title { flex: none; }
/* The kind label is the eyebrow of the bar. It is separated from the title by size, weight and tracking rather
   than by fading it, because opacity compounded with the past state and dropped this line to 2.2 to 1. */
.bar-kind { font: 700 10.5px/1 var(--f-ui); letter-spacing: .07em; text-transform: uppercase; flex: none; }
/* The offer figure sits right aligned inside the bar when the bar is wide enough to hold it. Display face at
   700 against the interface face at 500 keeps it distinct with no loss of ink. */
.bar.has-offer .bar-text { inset: 0 58px 0 0; }
.bar-offer {
  position: absolute; top: 0; bottom: 0; right: 8px;
  display: flex; align-items: center;
  font: 700 11px/1 var(--f-display); font-variant-numeric: tabular-nums; letter-spacing: .01em;
  white-space: nowrap; pointer-events: none;
}
/* The readiness rule, the organising idea. The track carries a faint counter tone so a sale at zero reads as an
   empty rule rather than as no rule at all, and the fill is strong enough to measure over 3 to 1 against the bar
   it sits on. Width is carried by scaleX so only transform animates. */
.bar-rule { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; overflow: hidden; background: color-mix(in srgb, var(--rk) 16%, transparent); }
.bar-rule-fill {
  position: absolute; inset: 0;
  transform-origin: left center;
  transform: scaleX(var(--pf, 0));
  background: color-mix(in srgb, var(--rk) 72%, transparent);
}
.bar.is-turning .bar-rule-fill { transition: transform 500ms var(--ease); }

/* A finished sale is told by a desaturated fill, not by fading the whole bar, so its title and its figure keep
   the ratio they had while it was live. A past sale is also done with readiness, so the rule goes with it and a
   full rule on a past bar never competes with a live sale that has just reached 100. */
.bar.is-past { opacity: .9; }
.bar.is-past .bar-in { filter: grayscale(.6); }
.bar.is-past .bar-rule { display: none; }
.bar.is-cancelled { opacity: .45; }
.bar.is-cancelled .bar-title { text-decoration: line-through; }
.bar.is-dim { opacity: .15; }
/* A bar too narrow for even a word (under 44px on the phone grid) is a coloured block with no fragment inside. */
.bar.is-tiny .bar-text, .bar.is-tiny .bar-offer { display: none; }

/* Resize handles: 12px hit areas, visible on hover and focus */
.bar-h {
  position: absolute; top: 0; bottom: 0; width: 12px;
  cursor: ew-resize; opacity: 0;
  transition: opacity 120ms var(--ease);
}
.bar-h-start { left: 0; }
.bar-h-end { right: 0; }
.bar-h::after {
  content: ''; position: absolute; top: 7px; bottom: 7px; width: 2px; border-radius: 1px;
  background: currentColor; opacity: .55;
}
.bar-h-start::after { left: 4px; }
.bar-h-end::after { right: 4px; }
.bar:focus-within .bar-h, .bar.is-resizing .bar-h { opacity: 1; }
@media (hover: hover) and (pointer: fine) {
  .bar:hover .bar-h { opacity: 1; }
}

/* Drag states */
.bar.is-lifted { z-index: 6; cursor: grabbing; box-shadow: var(--shadow-drag); border-radius: var(--r-sm); }
.bar.is-lifted .bar-in { transform: scale(1.02); }
.bar.is-drop .bar-in { outline: 2px solid var(--accent-strong); outline-offset: -3px; }
.cal-ghost, .qtr-ghost {
  position: absolute; height: 26px;
  border: 1.5px dashed var(--ink-2);
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  pointer-events: none; z-index: 5;
}
.cal-ghost.is-start, .qtr-ghost.is-start { border-top-left-radius: var(--r-sm); border-bottom-left-radius: var(--r-sm); }
.cal-ghost.is-end, .qtr-ghost.is-end { border-top-right-radius: var(--r-sm); border-bottom-right-radius: var(--r-sm); }
.cal-ghost.is-new { background: var(--k-other); border-color: var(--ink-2); }

/* Skeleton bars while the range loads: linen 2 blocks, nothing moves */
.bar-skel { position: absolute; height: 26px; border-radius: var(--r-sm); background: var(--linen-2); border: 1px solid var(--rule); }

/* 2b. The creative strip (contract 16.1). The month grid shows the artwork itself, not a count of it.
      One cell per slot, filled or empty, scrolling sideways inside the day. This is the layer that makes a
      week of work visible from across the room, and it replaced the creatives count pill that used to sit
      in the marks row saying how many files existed without ever showing one. */
.cal-strip {
  position: relative; z-index: 1;
  display: flex; align-items: center; gap: 3px;
  margin: 4px 6px 0; padding: 1px 0 2px;
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.cal-strip::-webkit-scrollbar { display: none; }
.cal-strip.is-past { opacity: .8; }
.cal-strip.is-cancelled { opacity: .45; }
.cal-strip.is-dim { opacity: .15; }

.cstrip-i {
  position: relative;
  flex: 0 0 auto;
  width: 32px; height: 32px; padding: 0;
  border-radius: 5px;
  border: 1px solid var(--rule);
  background: var(--linen-2);
  color: var(--ghost);
  cursor: pointer;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  transition: transform 140ms var(--spring), border-color 140ms var(--ease), box-shadow 140ms var(--ease);
}
.cstrip-i img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cstrip-i .ic { width: 13px; height: 13px; }
.cstrip-i.is-void { border-style: dashed; background: transparent; }
.cstrip-i:hover { border-color: var(--accent-strong); box-shadow: 0 0 0 2px var(--accent-soft); transform: translateY(-1px); }
.cstrip-i:active { transform: scale(.94); }
.cstrip-i:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 1px; }
/* The readiness mark, the same language the studio rail speaks: amber for made but not approved,
   green for approved or live. A slot with nothing in it carries no mark at all. */
.cstrip-i::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: transparent;
}
.cstrip-i.is-draft::after { background: var(--warn); }
.cstrip-i.is-ready::after { background: var(--good); }
.cstrip-n {
  flex: 0 0 auto; margin-right: 3px; padding-right: 5px;
  border-right: 1px solid var(--rule);
  font: 700 10px/1 var(--f-ui); letter-spacing: .04em; color: var(--muted);
  align-self: stretch; display: flex; align-items: center;
}

/* 2c. The posting lane (contract 16.2). A queued post sits on the day it goes out, which is often not the
      day its sale starts, so these are placed by their own time rather than under a bar. */
.cal-posts {
  position: relative; z-index: 1;
  display: flex; flex-wrap: wrap; gap: 3px;
  margin: 5px 6px 0;
}
.cal-post {
  display: inline-flex; align-items: center; gap: 4px;
  height: 19px; padding: 0 6px 0 5px;
  border-radius: 999px;
  border: 1px dashed var(--rule-2);
  background: transparent;
  color: var(--muted);
  font: 600 10px/1 var(--f-ui);
  cursor: pointer;
  transition: border-color 140ms var(--ease), color 140ms var(--ease), background 140ms var(--ease);
}
.cal-post .ic { width: 11px; height: 11px; }
.cal-post:hover { border-color: var(--accent-strong); color: var(--accent-strong); background: var(--accent-soft); }
.cal-post:active { transform: scale(.96); }
.cal-post:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 1px; }
/* Armed means a person said go and the clock will take it. That is the state worth seeing from a distance. */
.cal-post.is-armed { border-style: solid; border-color: var(--accent-strong); color: var(--accent-strong); background: var(--accent-soft); }
.cal-post.is-sending { border-style: solid; border-color: var(--warn); color: var(--warn); background: var(--warn-soft); }
.cal-post.is-posted { border-style: solid; border-color: var(--good); color: var(--good); background: var(--good-soft); }
.cal-post.is-failed { border-style: solid; border-color: var(--bad); color: var(--bad); background: var(--bad-soft); }

/* 2d. The inline note editor (contract 16.3). Double click the small text and write over it. */
.cal-detail.is-editing { cursor: default; background: var(--paper); box-shadow: 0 0 0 2px var(--accent); }
.cal-note-in {
  width: 100%; min-height: 54px;
  border: 0; padding: 2px; margin: 0;
  border-radius: 3px;
  background: transparent;
  color: var(--ink);
  font: 400 11.5px/1.45 var(--f-ui);
  resize: vertical;
}
.cal-note-in:focus { outline: none; }
.cal-note-tip { font: 500 9.5px/1.2 var(--f-ui); letter-spacing: .08em; text-transform: uppercase; color: var(--ghost); }

/* 2e. The day note (contract 19). Every square of the month can be written on, whether or not anything is
      planned on it, because most of what a team needs to remember lives on a day with no sale: a supplier
      is closed, a container lands, the photographer is booked, do not launch anything this week.
      It sits at the bottom of the cell in the quietest type on the grid, because it is background to
      whatever is planned rather than a thing to act on. */
.cal-daynote {
  position: relative; z-index: 1;
  margin: 6px 6px 0; padding: 3px 4px;
  border-radius: var(--r-sm);
  border-left: 2px solid var(--rule-2);
  font: 400 11px/1.4 var(--f-ui);
  color: var(--muted);
  cursor: text;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.cal-daynote:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 1px; }
/* A day with nothing written on it keeps a target to click but takes no room and draws no line until the
   pointer is over it, or an empty month would read as a page of empty boxes. */
.cal-daynote.is-blank {
  min-height: 14px; margin-top: auto; padding: 0 4px;
  border-left-color: transparent;
}
@media (hover: hover) and (pointer: fine) {
  .cal-daynote.is-blank:hover::before {
    content: "Click to write a note";
    font: 400 10px/1.4 var(--f-ui);
    color: var(--ghost);
  }
  .cal-daynote:not(.is-blank):hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
}
.cal-daynote.is-editing {
  border-left-color: var(--accent);
  background: var(--paper);
  box-shadow: 0 0 0 2px var(--accent);
  min-height: 0;
  cursor: default;
}
/* 3. The detail block: channel bubbles, the offer and creatives pills, then the dot point notes, in the cell
      where the sale starts. It answers what is happening that day without opening anything (contract 13.3, 13.4). */
.cal-detail {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; gap: 5px;
  margin: 4px 6px 0; padding: 3px 4px 4px;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: transform 120ms var(--ease);
}
.cal-detail + .cal-detail { margin-top: 8px; }
/* Past holds its notes at a ratio that still reads: at .6 the 11.5px dot points measured 3.0 to 1. */
.cal-detail.is-past { opacity: .8; }
.cal-detail.is-cancelled { opacity: .45; }
.cal-detail.is-dim { opacity: .15; }
.cal-detail:active { transform: scale(.985); }
/* The block advertises itself as a control, so it carries the focus state of one: it takes focus from its own
   bar rather than from the tab order, which keeps one tab stop per sale (contract 13.4). */
.cal-detail:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .cal-detail:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
}
.cal-marks { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
.cal-marks:empty { display: none; }
/* Four lines is the most a note gets in a cell, which holds the 90 character note ui.js hands over and still
   stops one long mechanic from owning the whole week row. */
.cal-detail .dp { max-height: calc(1.35em * 4); overflow: hidden; }

/* Channel bubbles: 20px round, a 12px icon centred, a 1.5px ring, three states. */
.bub {
  --chc: var(--rule-2);
  box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 20px; height: 20px;
  border: 1.5px solid var(--chc); border-radius: 999px;
  color: var(--chc);
}
.bub .ic { width: 12px; height: 12px; }
.bub-email { --chc: var(--ch-email); }
.bub-sms { --chc: var(--ch-sms); }
.bub-site { --chc: var(--ch-site); }
.bub-meta { --chc: var(--ch-meta); }
.bub-instagram { --chc: var(--ch-instagram); }
.bub-google { --chc: var(--ch-google); }
.bub.is-ready { background: var(--chc); color: var(--linen); }
.bub.is-started { background: transparent; color: var(--chc); }
/* Waiting keeps the hue and drops the strength, so a waiting Meta bubble still reads as Meta. Grey is left for
   the fallback --chc, which is all a bubble with no channel of its own can carry. */
.bub.is-waiting {
  background: transparent;
  border-color: color-mix(in srgb, var(--chc) 45%, transparent);
  color: color-mix(in srgb, var(--chc) 70%, transparent);
}

/* 4. Quarter timeline */
.qtr-host { display: flex; flex-direction: column; }
.qtr { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; -webkit-user-select: none; user-select: none; }
.qtr-scroll {
  flex: 1; min-height: 0;
  overflow: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--ghost) transparent;
}
.qtr-scroll:focus { outline: none; }
.qtr-canvas {
  position: relative;
  width: calc(var(--qd) * var(--days));
  min-height: 100%;
}
/* The day columns are real elements, not a repeating gradient: the weekend ground and the Monday hairline are
   structure, and a gradient is banned on this surface now that the hatch is gone. The layer sits under the
   groups, which come after it in the DOM, and takes no pointer events. */
.qtr-cols { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.qtr-col { position: absolute; top: 0; bottom: 0; width: var(--qd); }
.qtr-col.is-weekend { background: var(--linen-2); }
.qtr-col.is-monday { border-left: 1px solid var(--rule); }
.qtr-head {
  position: sticky; top: 0; z-index: 4;
  height: 56px;
  background: var(--linen);
  border-bottom: 1px solid var(--rule);
}
.qtr-months { position: relative; height: 28px; border-bottom: 1px solid var(--rule); }
.qtr-month {
  position: absolute; top: 0; height: 28px;
  display: flex; align-items: center;
  border-left: 1px solid var(--rule-2);
  white-space: nowrap;
}
.qtr-month-name { position: sticky; left: 0; padding: 0 8px 0 10px; font: 700 14px/1 var(--f-display); letter-spacing: -.01em; color: var(--ink); }
.qtr-month-year { font: 500 12px/1 var(--f-ui); color: var(--muted); }
.qtr-days { position: relative; height: 28px; }
.qtr-day {
  position: absolute; top: 0; width: var(--qd); height: 28px;
  display: flex; align-items: center; justify-content: center; gap: 3px;
  font: 700 10.5px/1 var(--f-ui); letter-spacing: .06em; color: var(--muted);
}
.qtr-day.is-weekend { color: var(--ghost); }
.qtr-day-num { font: 700 10.5px/1 var(--f-display); color: var(--ink-2); }
.qtr-day.is-weekend .qtr-day-num { color: var(--ghost); }

.qtr-groups { position: relative; }
.qtr-group { --kc: var(--k-other); position: relative; border-bottom: 1px solid var(--rule); }
.qtr-group.k-half { --kc: var(--k-half); }
.qtr-group.k-tail { --kc: var(--k-tail); }
.qtr-group.k-new { --kc: var(--k-new); }
.qtr-group.k-clear { --kc: var(--k-clear); }
.qtr-group.k-flash { --kc: var(--k-flash); }
/* The group label carries its kind colour as a 3px left rule, the same device as the drawer's kind menu. */
.qtr-group-label {
  position: sticky; left: 0; z-index: 3;
  display: inline-flex; align-items: center; gap: 8px;
  height: 26px; padding: 0 12px 0 10px;
  background: var(--linen);
  border-left: 3px solid var(--kc);
  border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  border-bottom-right-radius: var(--r-sm);
}
.qtr-group-count { font: 700 11px/1 var(--f-display); color: var(--ink-2); }
.qtr-group-count:empty { display: none; }
.qtr-group > .bar { z-index: 2; }
/* A bar narrower than 64px keeps its colour and readiness and hands its words to a label sitting to its right. */
.qtr-label {
  position: absolute; height: 26px;
  display: flex; align-items: center; gap: 5px;
  max-width: 220px; padding-right: 18px;
  white-space: nowrap; overflow: hidden; text-overflow: clip;
  font: 500 11px/1 var(--f-ui); color: var(--ink);
  pointer-events: none; z-index: 2;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
}
.qtr-label .bar-kind { font-size: 10px; letter-spacing: .06em; }
/* The label sits on the linen ground rather than on a fill, so past holds a ratio that reads at 11px. */
.qtr-label.is-past { opacity: .75; }
.qtr-label.is-cancelled { opacity: .45; }
.qtr-label.is-cancelled .bar-title { text-decoration: line-through; }
.qtr-label.is-dim { opacity: .15; }
.qtr-label.is-off { opacity: 0; }
.qtr-gap {
  position: absolute; height: 26px;
  display: flex; align-items: center; justify-content: center;
  font: 700 11px/1 var(--f-display); letter-spacing: .02em; color: var(--muted);
  pointer-events: none; z-index: 1;
}
.qtr-gap > span { padding: 2px 6px; background: var(--linen); border-radius: 999px; white-space: nowrap; }
.qtr-today { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--accent-strong); z-index: 3; pointer-events: none; }
.qtr-empty {
  position: absolute; left: 0; right: 0; top: 50%;
  margin: 0; padding: 0 24px; text-align: center;
  font: 500 14px/1.4 var(--f-ui); color: var(--muted);
  pointer-events: none; transform: translateY(-50%);
}
.qtr-canvas.is-skeleton .qtr-group::after {
  content: ''; position: absolute; left: calc(var(--qd) * 9 + 3px); top: 26px; width: calc(var(--qd) * 3 - 6px); height: 26px;
  border-radius: var(--r-sm); background: var(--linen-2); border: 1px solid var(--rule);
}

/* 5. Phone agenda: the month's sales as a list under the grid, hidden from 1024px up where the bars and the
      detail blocks carry the same facts. */
.cal-agenda { display: none; list-style: none; margin: 0; padding: 8px 12px 24px; border-top: 1px solid var(--rule); }
.cal-agenda-row {
  --kc: var(--k-other);
  display: flex; align-items: center; gap: 12px;
  width: 100%; min-height: 56px; padding: 8px 12px 8px 14px;
  border-left: 4px solid var(--kc); border-bottom: 1px solid var(--rule);
  text-align: left; color: var(--ink);
  transition: transform 120ms var(--ease);
}
.cal-agenda-row:active { transform: scale(.985); }
.cal-agenda-row.is-past { opacity: .6; }
.cal-agenda-row.is-cancelled { opacity: .45; }
.cal-agenda-row.is-cancelled .cal-agenda-title { text-decoration: line-through; }
.cal-agenda-row.is-dim { opacity: .15; }
.cal-agenda-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cal-agenda-title { font: 500 14px/1.25 var(--f-ui); color: var(--ink); overflow-wrap: anywhere; }
.cal-agenda-date { font: 400 12.5px/1.2 var(--f-ui); color: var(--muted); }
.cal-agenda-fig { flex: none; font: 700 13px/1 var(--f-display); color: var(--ink-2); white-space: nowrap; }
.cal-agenda-empty { padding: 16px 12px; font: 400 13.5px/1.4 var(--f-ui); color: var(--muted); }

/* 6. Narrow screens: the agenda carries the facts, so the detail block goes and the cells stay compact. */
@media (max-width: 1023.98px) {
  /* --stick-h is the measured height of the fixed rail strip plus the wrapped topbar, published by app.js.
     The fallback is the height that stack takes at 390px wide.
     The grid takes its own height and no more: holding it to a full screen put the agenda, which is the thing
     that carries the facts on a phone, below the fold behind rows of empty day numerals. */
  .cal { height: auto; min-height: 0; }
  .cal-scroll { overflow: visible; }
  .cal-head { top: var(--stick-h, 155px); }
  .cal-num { top: 4px; left: 5px; min-width: 20px; height: 20px; font-size: 13px; }
  .bar-kind { display: none; }
  .bar-text { padding-right: 14px; }
  .bar.has-offer .bar-text { inset: 0 14px 0 0; }
  .bar-offer { display: none; }
  .cal-detail { display: none; }
  .cal-agenda { display: block; }
  /* The page scrolls here, so the timeline takes an explicit height instead of a share of a flex box. */
  .qtr-scroll { flex: none; height: calc(100dvh - var(--stick-h, 155px)); }
}

/* 7. Reduced motion: remove travel, keep opacity. The readiness rule lands on its final width with no run. */
@media (prefers-reduced-motion: reduce) {
  .bar-in { transition: none; }
  .bar.is-lifted .bar-in { transform: none; }
  .bar-h { transition: none; }
  .bar.is-turning .bar-rule-fill { transition: none; }
  .cal-detail { transition: none; }
  .cal-detail:active { transform: none; }
  .cal-agenda-row:active { transform: none; }
}
