/* ============================================================================
   CALENDAR - what is DUE, and the PLAN for actually doing it.

   The page is built around one distinction, and the styling exists to make it
   impossible to miss: the DUE column is the school's answer, so it is drawn
   flat and factual; the PLAN column is Aluma's and the student's, so it is
   drawn as something you are meant to pick up and rearrange.

   Every rule here is scoped to .view-calendar or a .cal-* class, so this sheet
   composes the design-system tokens without reaching into any other view.
   ========================================================================== */

body[data-view="calendar"] .view-calendar {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  animation: viewIn 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}

/* ── Class colour ─────────────────────────────────────────────────────────
   The global .subj-* rules (styles.css) set --subj from theme-aware --cc-*
   tokens, so a class reads the same colour here as on the Assignments page
   and stays legible in light theme. The calendar adds nothing to that on
   purpose: an earlier copy of the dark values here measured ~1.5:1 on a
   light background and made every class chip a pale smear. Only the
   "no class" grey is the calendar's own, kept dim. */
.view-calendar .subj-gray, .cal-goto-list .subj-gray, .cal-pick-list .subj-gray,
.cal-detail-body .subj-gray, .cal-dock .subj-gray { --subj: var(--text-dim); }

/* ── Topbar ─────────────────────────────────────────────────────────────── */
.cal-topbar { flex-wrap: wrap; gap: 10px; }
.cal-topbar .topbar-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.cal-modes {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--bg-elev-2); border: 1px solid var(--border);
  border-radius: var(--r-full);
}
/* 36px segments in a 3px track and its border make the control 44px, the
   height of the Go to / Plan / Ask buttons beside it (it was 39.4). */
.cal-modes button {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  min-height: var(--ctl-h-compact, 36px);
  padding: 6px 16px; border-radius: var(--r-full);
  font-size: 12.5px; font-weight: 650; letter-spacing: 0.01em;
  color: var(--text-muted); transition: background 0.16s, color 0.16s;
}
.cal-modes button:hover { color: var(--text); }
.cal-modes button[aria-selected="true"] {
  background: var(--accent); color: var(--accent-ink);
}

/* ── The selector bar ───────────────────────────────────────────────────── */
/* One bar, both views: the nav on the left swaps with the mode, the ruler
   runs through the middle of both. It wraps rather than squeezes - on a phone
   the ruler drops to its own line instead of shrinking to nothing. */
.cal-selbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 24px; border-bottom: 1px solid var(--border);
  background: var(--bg-subtle); min-width: 0;
  container-type: inline-size; container-name: calsel;
}
/* Measured against the bar, not the window: an iPad in portrait is 820px of
   viewport but the sidebar leaves this row about 600px. Below that the ruler
   takes its own line rather than being squeezed to a thumbnail - and above it,
   Day view stays the single row it has always been. */
@container calsel (max-width: 780px) {
  /* Week view has the most in this row. When it stops fitting, the ruler takes
     a line of its own rather than shrinking to a thumbnail, and the labels
     give up the width they were reserving for a long month name. */
  body[data-cal-mode="week"] .cal-ruler { flex-basis: 100%; order: 5; }
  body[data-cal-mode="week"] .cal-weeknav-mid { min-width: 0; }
}
body[data-cal-mode="week"] .cal-daynav,
body[data-cal-mode="day"]  .cal-weeknav,
body[data-cal-mode="day"]  .cal-shows { display: none; }

.cal-arrow {
  flex-shrink: 0; width: 34px; height: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-full); border: 1px solid var(--border);
  background: var(--bg-elev-2); color: var(--text-muted); cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.cal-arrow svg { width: 16px; height: 16px; }
.cal-arrow:hover { background: var(--bg-elev-3); color: var(--text); border-color: var(--border-strong); }
.cal-arrow:active { transform: scale(0.94); }

.cal-daynav, .cal-weeknav { display: flex; align-items: center; gap: 10px; }
.cal-daynav-mid, .cal-weeknav-mid {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-width: 160px; line-height: 1.2; text-align: center;
}
.cal-daynav-mid strong, .cal-weeknav-mid strong {
  font-size: 14px; font-weight: 700; letter-spacing: -0.01em; color: var(--text);
}
.cal-daynav-mid small, .cal-weeknav-mid small { font-size: 11.5px; color: var(--text-dim); }
.cal-now { flex-shrink: 0; }

/* Which half of the week. Same shape as the Day/Week switch above it, because
   it is the same kind of decision - one of these at a time, always one on. */
.cal-shows {
  display: inline-flex; gap: 2px; padding: 3px; flex-shrink: 0;
  border-radius: var(--r-full); background: var(--bg-elev-2);
  border: 1px solid var(--border);
}
.cal-shows button {
  border: 0; background: none; cursor: pointer; border-radius: var(--r-full);
  padding: 5px 12px; font-size: 12.5px; font-weight: 650; color: var(--text-muted);
  font-family: inherit; transition: background 0.15s, color 0.15s;
}
.cal-shows button:hover { color: var(--text); }
.cal-shows button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }

/* The ruler. A measuring-tape you drag: the needle stays put and the days run
   underneath it. Touch only - a mouse gets the arrows above, which are exact.
   `scroll-snap` does the physics so the strip keeps native momentum. */
/* Shown to every pointer. A finger drags it; a mouse clicks a tick, which is
   why every tick is a real button. */
.cal-ruler {
  display: block; position: relative; flex: 1 1 260px; min-width: 180px;
  height: 64px; touch-action: pan-x;
}
.cal-ruler-strip {
  display: flex; align-items: flex-end; gap: 0;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  height: 100%;
  padding-block: 22px 4px;   /* clears the caption sitting above the strip */
  /* Half the ruler, less half a tick (.cal-tick is 34px), at each end, so the
     first and last day can still sit under the needle. A percentage here is
     of the ruler's width as it is NOW. This used to be a pixel value that
     buildCalRuler() measured once, and the ruler's width changes after that:
     Week view has the week arrows, Due/Plan and "This week" beside it, Day
     view does not, and the window or sidebar can change. Padded for a wider
     ruler, the strip could not shrink below its own padding, so it spilled
     out under Both/Due/Plan and centred the day off the needle. Kept in CSS,
     the padding always adds up to less than the ruler, and the scroll that
     centres a day (idx * 34px) is the same at every width. */
  padding-inline: calc(50% - 17px);
}
.cal-ruler-strip::-webkit-scrollbar { display: none; }
.cal-tick {
  flex: 0 0 auto; width: 34px;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: 4px; scroll-snap-align: center;
  cursor: pointer; background: none; border: 0; padding: 0;
  color: var(--text-dim); font: inherit;
}
.cal-tick .tick-mark {
  width: 2px; height: 12px; border-radius: 14px;
  background: var(--border-strong); transition: height 0.14s, background 0.14s;
}
.cal-tick.is-week .tick-mark { height: 20px; }
.cal-tick .tick-num { font-size: 10px; font-weight: 600; opacity: 0.55; }
/* The 1st is a major unit, drawn and labelled like the inch mark on a tape
   measure. Without it the only thing that changes crossing a month is 31 → 1. */
/* Deliberately NOT wider than its neighbours: wireCalRuler() finds the day
   under the needle from a uniform tick width, so a fatter mark here would
   drift the selection by a day for every month you scroll past. It earns its
   emphasis with weight and colour instead. */
.cal-tick.is-month .tick-mark {
  height: 26px; width: 2.5px; background: var(--text-muted);
}
.cal-tick.is-month .tick-num {
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.06em;
  text-transform: uppercase; opacity: 1; color: var(--text);
}
/* The bubble over a day says how much is on it, not merely that something is.
   A student scanning three weeks for "where does it get bad" reads the size
   before they read any number. Ticks bottom-align, so a fatter bubble grows
   upward and the marks and dates underneath it never move. */
.cal-tick .tick-dot {
  width: 4px; height: 4px; border-radius: 50%; background: var(--accent);
  opacity: 0; transition: opacity 0.14s, width 0.14s, height 0.14s;
}
.cal-tick.has-due .tick-dot { opacity: 0.5; }
.cal-tick[data-level="2"] .tick-dot { width: 5.5px; height: 5.5px; opacity: 0.68; }
.cal-tick[data-level="3"] .tick-dot { width: 7px;   height: 7px;   opacity: 0.84; }
.cal-tick[data-level="4"] .tick-dot { width: 8.5px; height: 8.5px; opacity: 1; }
.cal-tick.is-today .tick-mark { background: var(--accent); }
.cal-tick.is-sel .tick-mark { background: var(--text); height: 24px; }
.cal-tick.is-sel .tick-num { opacity: 1; color: var(--text); }
.cal-ruler-caption {
  position: absolute; top: 0; left: 0; right: 0; z-index: 2;
  display: flex; align-items: baseline; justify-content: center; gap: 6px;
  pointer-events: none; line-height: 1.2; padding-bottom: 2px;
}
.cal-ruler-caption strong {
  font-size: 13px; font-weight: 750; color: var(--text); letter-spacing: -0.01em;
}
.cal-ruler-caption .rc-stamp { font-size: 11.5px; font-weight: 600; color: var(--text-dim); }
.cal-ruler-caption strong + .rc-stamp::before { content: "· "; }
/* Stops above the day labels: a needle drawn through the number it is
   selecting makes that number the one hardest to read. */
.cal-ruler-needle {
  position: absolute; left: 50%; top: 20px; bottom: 18px; width: 2px;
  transform: translateX(-50%); border-radius: 14px;
  background: var(--accent); pointer-events: none;
  box-shadow: 0 0 0 4px rgb(var(--accent-rgb) / 0.14);
}
.cal-ruler-fade {
  position: absolute; top: 0; bottom: 0; width: 44px; pointer-events: none;
}
.cal-ruler-fade.left  { left: 0;  background: linear-gradient(90deg, var(--bg-subtle), rgb(from var(--bg-subtle) r g b / 0)); }
.cal-ruler-fade.right { right: 0; background: linear-gradient(270deg, var(--bg-subtle), rgb(from var(--bg-subtle) r g b / 0)); }

/* ── Body ───────────────────────────────────────────────────────────────── */
/* The top padding is the gap under the selector bar: the same --filter-gap
   every filter row in the app leaves above its list. */
.cal-body { overflow: auto; min-height: 0; padding: var(--filter-gap) 24px 28px; container-type: inline-size; container-name: calbody; }

/* ── OVERDUE STRIP: the loudest thing on the page ───────────────────────────
   Late work is drawn on the day it WAS due, which is a day nobody scrolls
   back to. This strip lifts it onto the day the student is standing on, in
   the one colour this app reserves for "this is already a problem". */
.cal-overdue {
  border: 1px solid rgb(var(--danger-rgb) / 0.45);
  border-left: 3px solid var(--danger);
  border-radius: var(--r-lg);
  background: rgb(var(--danger-rgb) / 0.07);
  margin-bottom: 18px; overflow: hidden;
}
.cal-overdue-head {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 12px 16px; cursor: pointer; text-align: left;
  appearance: none; border: 0; background: rgb(var(--danger-rgb) / 0.10);
  color: inherit; font: inherit;
}
.cal-overdue-head:hover { background: rgb(var(--danger-rgb) / 0.16); }
.cal-overdue-head:focus-visible { outline: 2px solid var(--border-focus); outline-offset: -3px; }
.cal-overdue-head h3 {
  margin: 0; font-size: 13px; font-weight: 750; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--danger); flex-shrink: 0;
}
.cal-overdue-dot {
  width: 8px; height: 8px; border-radius: 999px; flex-shrink: 0;
  background: var(--danger); box-shadow: 0 0 0 4px rgb(var(--danger-rgb) / 0.18);
}
.cal-overdue-sub { font-size: 11.5px; color: var(--text-dim); font-weight: 500; min-width: 0; }
.cal-overdue-head .cal-chev { margin-left: auto; }
.cal-overdue-head[aria-expanded="true"] .cal-chev { transform: rotate(-135deg); }
.cal-overdue-body { padding: 12px; display: flex; flex-direction: column; gap: 10px; }
.cal-overdue-body[hidden] { display: none; }
.cal-overdue-when {
  font-size: 10.5px; font-weight: 600; color: var(--text-dim);
}

/* Late anywhere it is drawn, not only inside the strip: the red edge is what
   makes one card in a full Tuesday column the one you look at. */
.cal-card.is-late:not(.is-done) {
  --subj: var(--danger) !important;
  border-color: rgb(var(--danger-rgb) / 0.42);
  background: rgb(var(--danger-rgb) / 0.06);
}
.cal-card.is-late:not(.is-done):hover { background: rgb(var(--danger-rgb) / 0.11); }
/* A kind chip keeps its own ground on a late card: its tint over the card's
   red put the essay ink at 3.98:1. Over the plain surface it clears 4.5:1. */
.cal-card.is-late:not(.is-done) .cal-kind {
  background: linear-gradient(var(--kind-bg, transparent), var(--kind-bg, transparent)), var(--surface-strong, var(--bg-elev-1));
}
.cal-chip.is-late:not(.is-done) {
  --subj: var(--danger) !important;
  border-color: rgb(var(--danger-rgb) / 0.42);
  background: rgb(var(--danger-rgb) / 0.08);
}
.cal-wday.has-late { border-color: rgb(var(--danger-rgb) / 0.45); }
.cal-wday.has-late .cal-wday-head .dcount { color: var(--danger); font-weight: 700; }

/* ── DAY VIEW: due on the left, plan on the right ───────────────────────── */
.cal-day {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 18px; align-items: start;
}
/* A day that has been has no plan half (see renderCalDay), so what is left
   takes the whole width rather than sitting in half a page. */
.cal-day.is-gone { grid-template-columns: minmax(0, 1fr); }
.cal-col {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--bg-elev-1); min-width: 0; overflow: hidden;
}
.cal-col-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
  background: var(--bg-elev-2);
}
.cal-col-head h3 {
  margin: 0; font-size: 13px; font-weight: 700; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--text);
}
.cal-col-head .cal-col-sub {
  font-size: 11.5px; color: var(--text-dim); font-weight: 500;
}
.cal-col-head .cal-col-actions { margin-left: auto; display: flex; gap: 6px; }
.cal-col-body { padding: 12px; display: flex; flex-direction: column; gap: 10px; }
/* The plan column is the editable half - say so with a dashed, lighter frame. */
.cal-col-plan { border-style: dashed; border-color: var(--border-strong); background: var(--bg-subtle); }
.cal-col-plan .cal-col-head { background: transparent; border-bottom-style: dashed; }

/* ── Assignment card (the DUE side) ─────────────────────────────────────── */
.cal-card {
  position: relative; display: block; width: 100%; text-align: left;
  border: 1px solid var(--border); border-left: 3px solid var(--subj, var(--text-dim));
  border-radius: var(--r-md); background: var(--bg-elev-2);
  padding: 11px 13px; cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.12s;
}
.cal-card:hover { background: var(--bg-elev-3); }
.cal-card.is-open { background: var(--bg-elev-3); border-color: var(--border-strong); }
.cal-card.is-focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgb(var(--accent-rgb) / 0.35); }
.cal-card-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 5px; }
.cal-card-title { font-size: 14px; font-weight: 650; line-height: 1.32; color: var(--text); overflow-wrap: anywhere; }
/* The whole head is one button: tap anywhere on the title to open it. */
.cal-card-hit {
  display: block; width: 100%; text-align: left; cursor: pointer;
  appearance: none; border: 0; background: none; padding: 0; margin: 0;
  color: inherit; font: inherit; border-radius: var(--r-xs);
}
.cal-card-hit:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 3px; }
.cal-card-hit .cal-card-top { margin-bottom: 5px; }
.cal-card-hit .cal-card-title { display: block; }
.cal-chev {
  margin-left: auto; width: 8px; height: 8px; flex-shrink: 0;
  border-right: 2px solid var(--text-dim); border-bottom: 2px solid var(--text-dim);
  transform: rotate(45deg); transition: transform 0.16s; opacity: 0.8;
}
.cal-card.is-open .cal-chev { transform: rotate(-135deg); }
.cal-card-detail { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border-strong); }
.cal-card-detail[hidden] { display: none; }
.cal-note { font-size: 11px; color: var(--text-dim); font-style: italic; }

/* ── The expanded assignment (inline in a day card, or as a modal) ────── */
.cal-facts {
  margin: 0 0 10px; display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: 4px 12px; font-size: 12.5px;
}
.cal-facts > div { display: contents; }
.cal-facts dt { color: var(--text-dim); font-weight: 600; white-space: nowrap; }
.cal-facts dd { margin: 0; color: var(--text); overflow-wrap: anywhere; }
.cal-detail-sec { margin-top: 10px; }
.cal-detail-label {
  font-size: 10px; font-weight: 750; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-dim); margin-bottom: 4px;
}
.cal-detail-brief { margin: 0; font-size: 13px; line-height: 1.55; color: var(--text); white-space: pre-line; overflow-wrap: anywhere; }
.cal-detail-brief.is-empty { color: var(--text-dim); font-style: italic; }
.cal-detail-plan { display: flex; gap: 6px; flex-wrap: wrap; }
.cal-detail-acts { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; }
.cal-detail-acts .ghost-btn { font-size: 12.5px; }
.cal-detail-modal { max-width: 620px; width: 100%; max-height: min(88dvh, 760px); display: flex; flex-direction: column; }
.cal-detail-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.cal-detail-head { border-left: 3px solid var(--subj, var(--text-dim)); padding-left: 12px; margin-bottom: 12px; }
.cal-detail-title { margin: 6px 0 8px; font-size: 18px; font-weight: 700; line-height: 1.25; letter-spacing: -0.01em; overflow-wrap: anywhere; }
.cal-detail-title.is-done { text-decoration: line-through; color: var(--text-dim); }
.cal-detail-body .task-materials { display: flex; gap: 6px; flex-wrap: wrap; }

/* How hard Aluma thinks it is. */
.cal-diff {
  font-size: 10px; font-weight: 700; letter-spacing: 0.03em;
  padding: 3px 8px; border-radius: var(--r-xs);
  border: 1px solid var(--border-strong); color: var(--text-muted);
}
.cal-diff[data-level="heavy"]   { color: var(--warning); border-color: rgb(var(--warning-rgb) / 0.42); }
.cal-diff[data-level="hardest"] { color: var(--danger);  border-color: rgb(var(--danger-rgb) / 0.42); }
.cal-diff[data-level="light"]   { color: var(--text-dim); }

/* What this sitting is: "Study 2 of 3", "Do", "Write".
   Sentence case, like every other chip. The rule across this sheet is that a
   HEADING shouts - "DUE", "PLANNER", "1 OVERDUE" label a region - and a CHIP
   does not, because a chip carries a value and sits in a row with three
   others. These were split between the two treatments, which is what made one
   card's chips read as four unrelated things. */
.cal-sess {
  display: inline-flex; align-items: center;
  font-size: 10px; font-weight: 750; letter-spacing: 0.05em; text-transform: none;
  padding: 3px 8px; border-radius: var(--r-xs);
  color: var(--accent); border: 1px solid rgb(var(--accent-rgb) / 0.38);
  background: rgb(var(--accent-rgb) / 0.10);
}
.cal-sess[data-kind="test"], .cal-sess[data-kind="study"] { color: var(--danger); border-color: rgb(var(--danger-rgb) / 0.4); background: rgb(var(--danger-rgb) / 0.10); }
.cal-sess[data-kind="quiz"] { color: var(--warning); border-color: rgb(var(--warning-rgb) / 0.4); background: rgb(var(--warning-rgb) / 0.10); }
.cal-plan-hit {
  appearance: none; border: 0; background: none; padding: 0; margin-top: 5px;
  display: block; width: 100%; text-align: left; cursor: pointer;
  color: inherit; font: inherit; border-radius: var(--r-xs);
}
.cal-plan-hit:hover { text-decoration: underline; text-decoration-color: var(--text-dim); text-underline-offset: 3px; }
.cal-plan-hit:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 3px; }
.cal-card-meta {
  margin-top: 6px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 11.5px; color: var(--text-dim);
}

/* The class name, in the class's colour. */
.cal-class {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10.5px; font-weight: 650; letter-spacing: 0.02em;
  padding: 3px 9px; border-radius: var(--r-full);
  color: var(--subj-ink, var(--subj, var(--text-dim)));
  background: color-mix(in srgb, var(--subj, var(--text-dim)) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--subj, var(--text-dim)) 26%, transparent);
  max-width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* What KIND of work it is. A test does not look like a reading, because a
   student's week does not treat them the same. */
.cal-kind {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10px; font-weight: 750; letter-spacing: 0.05em; text-transform: none;
  padding: 3px 8px; border-radius: var(--r-xs);
  border: 1px solid var(--kind-line, var(--border-strong));
  color: var(--kind-ink, var(--text-muted));
  background: var(--kind-bg, transparent);
}
.cal-kind[data-kind="test"]      { --kind-ink: var(--danger);  --kind-line: rgb(var(--danger-rgb) / 0.45);  --kind-bg: rgb(var(--danger-rgb) / 0.12); }
.cal-kind[data-kind="quiz"]      { --kind-ink: var(--warning); --kind-line: rgb(var(--warning-rgb) / 0.42); --kind-bg: rgb(var(--warning-rgb) / 0.12); }
.cal-kind[data-kind="in_class"]  { --kind-ink: var(--text-dim); --kind-line: var(--border-strong); --kind-bg: var(--bg-elev-3); }
.cal-kind[data-kind="lab"]       { --kind-ink: var(--kind-lab); --kind-line: rgb(var(--kind-lab-rgb) / 0.38); --kind-bg: rgb(var(--kind-lab-rgb) / 0.10); }
.cal-kind[data-kind="project"]   { --kind-ink: var(--kind-project); --kind-line: rgb(var(--kind-project-rgb) / 0.38); --kind-bg: rgb(var(--kind-project-rgb) / 0.10); }
.cal-kind[data-kind="essay"]     { --kind-ink: var(--kind-essay); --kind-line: rgb(var(--kind-essay-rgb) / 0.38); --kind-bg: rgb(var(--kind-essay-rgb) / 0.10); }
.cal-kind[data-kind="study"]     { --kind-ink: var(--accent); --kind-line: rgb(var(--accent-rgb) / 0.38); --kind-bg: rgb(var(--accent-rgb) / 0.10); }
.cal-kind[data-kind="reading"]   { --kind-ink: var(--text-muted); --kind-line: var(--border-strong); }
.cal-kind[data-kind="homework"]  { --kind-ink: var(--success); --kind-line: rgb(var(--success-rgb) / 0.36); --kind-bg: rgb(var(--success-rgb) / 0.10); }

.cal-late {
  font-size: 10px; font-weight: 750; letter-spacing: 0.04em; text-transform: none;
  padding: 3px 8px; border-radius: var(--r-xs);
  color: var(--danger); border: 1px solid rgb(var(--danger-rgb) / 0.4);
  background: rgb(var(--danger-rgb) / 0.12);
}

/* Done = completely greyed out. Colour, weight and contrast all drop together
   so a finished row reads as finished at a glance, in either theme. The
   fade is the whole card's, so every line in it starts from the darkest ink
   and the fade leaves it at 4.5:1 or better (AA): faded from the dim grey,
   a done card's words measured 2.1:1. */
.cal-card.is-done,
.cal-chip.is-done,
.cal-plan-item.is-done {
  --subj: var(--text-dim) !important;
  filter: grayscale(1);
  opacity: 0.66;
}
:is(.cal-card, .cal-chip, .cal-plan-item).is-done * { color: var(--text) !important; }
.cal-card.is-done .cal-card-title,
.cal-chip.is-done .cal-chip-title,
.cal-plan-item.is-done .cal-plan-title {
  text-decoration: line-through;
  text-decoration-color: var(--text-dim);
}
.cal-card.is-done:hover, .cal-chip.is-done:hover { opacity: 0.8; }

/* ── Plan card (the editable side) ──────────────────────────────────────── */
.cal-plan-item {
  position: relative;
  border: 1px solid var(--border); border-left: 3px solid var(--subj, var(--text-dim));
  border-radius: var(--r-md); background: var(--bg-elev-2);
  padding: 11px 13px; cursor: grab;
}
.cal-plan-item.dragging { opacity: 0.45; cursor: grabbing; }
.cal-plan-item .cal-plan-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.cal-plan-title { font-size: 13.5px; font-weight: 650; line-height: 1.3; margin-top: 5px; color: var(--text); overflow-wrap: anywhere; }
.cal-plan-why {
  margin-top: 5px; font-size: 11.5px; line-height: 1.45; color: var(--text-dim);
  display: flex; align-items: flex-start; gap: 6px;
}
.cal-plan-why .why-ico { flex-shrink: 0; opacity: 0.7; margin-top: 1px; }
.cal-plan-acts { margin-top: 9px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cal-mini {
  appearance: none; cursor: pointer;
  font-size: 11px; font-weight: 600; padding: 4px 9px;
  border-radius: var(--r-full); border: 1px solid var(--border);
  background: var(--bg-elev-3); color: var(--text-muted);
  transition: background 0.14s, color 0.14s, border-color 0.14s;
}
.cal-mini:hover { background: var(--bg-elev-4); color: var(--text); border-color: var(--border-strong); }
.cal-mini.danger:hover { color: var(--danger); border-color: rgb(var(--danger-rgb) / 0.4); }
.cal-mini:disabled { opacity: 0.45; cursor: not-allowed; }
.cal-mini:disabled:hover { background: var(--bg-elev-3); color: var(--text-muted); border-color: var(--border); }
/* A study session you have logged. It stays a button - the way back from a
   mis-tap is the same tap - but it reads as the thing you did, not the thing
   you still owe. */
.cal-mini.is-on {
  background: rgb(var(--success-rgb) / 0.16);
  border-color: rgb(var(--success-rgb) / 0.4);
  color: var(--success);
}
.cal-mini.is-on:hover {
  background: rgb(var(--success-rgb) / 0.26);
  border-color: rgb(var(--success-rgb) / 0.55);
  color: var(--success);
}
.cal-mins {
  margin-left: auto; font-size: 11px; font-weight: 600; color: var(--text-dim);
  white-space: nowrap;
}
.cal-mins.est { opacity: 0.72; }
/* An estimate you can fix. Underlined rather than boxed: a fifth bordered
   control on a card that already has four buttons would read as clutter, and
   the point here is that the NUMBER is the thing you touch. */
.cal-mins-edit {
  font: inherit; font-size: 11px; font-weight: 600; color: var(--text-dim);
  background: none; border: 0; padding: 2px 4px; border-radius: var(--r-xs);
  cursor: pointer; white-space: nowrap;
  text-decoration: underline dotted currentColor; text-underline-offset: 3px;
}
.cal-mins-edit:hover { color: var(--text); background: var(--bg-elev-3); }
.cal-mins-edit:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Already shaded by this student's own history - worth a different colour,
   because "we guessed" and "we guessed, for you" are different claims. */
.cal-mins-edit.tuned { color: var(--accent); opacity: 1; }
.cal-mins-input {
  font: inherit; font-size: 11px; font-weight: 600; width: 68px;
  padding: 2px 6px; border-radius: var(--r-xs);
  border: 1px solid var(--border-focus, var(--border-strong));
  background: var(--bg-elev-1); color: var(--text);
}
.cal-mins-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.cal-src {
  font-size: 10px; font-weight: 750; letter-spacing: 0.05em; text-transform: none;
  padding: 2px 7px; border-radius: var(--r-xs);
  color: var(--accent); border: 1px solid rgb(var(--accent-rgb) / 0.34);
}
.cal-src[data-src="you"] { color: var(--text-dim); border-color: var(--border-strong); }

/* ── How long have you got? ───────────────────────────────────────────────
   The question is the heading and the answer is the big number next to it,
   because this is the one control on the page that changes the whole plan.
   The bar underneath is the consequence, not the control - it is read, never
   dragged, which is why it sits below the slider rather than beside it. */
.cal-budget {
  display: grid; gap: 6px; padding: 10px 12px 12px; margin-bottom: 10px;
  border: 1px solid var(--border); border-radius: var(--r-card, 26px);
  background: var(--bg-elev-2);
}
.cal-budget-head { display: flex; align-items: baseline; gap: 8px; }
.cal-budget-q { font-size: 12.5px; font-weight: 600; color: var(--text-muted); }
.cal-budget-val {
  margin-left: auto; font-size: 17px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.cal-budget.over .cal-budget-val { color: var(--warning); }
.cal-budget-foot { display: flex; align-items: center; gap: 10px; }
.cal-budget-note {
  font-size: 11.5px; color: var(--text-dim); white-space: nowrap;
}
.cal-budget.over .cal-budget-note { color: var(--warning); }
.cal-budget .bar {
  flex: 1; min-width: 40px; height: 4px; border-radius: 999px;
  background: var(--bg-elev-3); overflow: hidden;
}
.cal-budget .bar span {
  display: block; height: 100%; background: var(--accent); border-radius: 999px;
  transition: width 260ms var(--ease-settle, ease);
}
.cal-budget.over .bar span { background: var(--warning); }

/* The slider itself. Track and thumb are drawn by hand in both engines - a
   native range control is a different height, colour and shape on every
   platform, and this one has to read as part of the card. */
.cal-cap {
  -webkit-appearance: none; appearance: none;
  width: 100%; margin: 2px 0; padding: 0;
  background: transparent; cursor: pointer;
  /* A 44px-tall hit area around a 6px track: the thumb is small to look at
     and large to hit, which is the whole trick on a phone. */
  height: var(--tap-min, 44px);
  touch-action: pan-y;
}
.cal-cap::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px; background: var(--bg-elev-3);
  border: 1px solid var(--border);
}
.cal-cap::-moz-range-track {
  height: 6px; border-radius: 999px; background: var(--bg-elev-3);
  border: 1px solid var(--border);
}
.cal-cap::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 26px; height: 26px; border-radius: 999px; margin-top: -11px;
  background: var(--accent); border: 3px solid var(--bg-elev-1);
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.22);
  transition: transform 140ms var(--ease-press, ease);
}
.cal-cap::-moz-range-thumb {
  width: 26px; height: 26px; border-radius: 999px;
  background: var(--accent); border: 3px solid var(--bg-elev-1);
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.22);
  transition: transform 140ms var(--ease-press, ease);
}
.cal-cap:active::-webkit-slider-thumb { transform: scale(1.18); }
.cal-cap:active::-moz-range-thumb { transform: scale(1.18); }
.cal-cap:focus { outline: none; }
.cal-cap:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px rgb(var(--accent-rgb) / 0.3); }
.cal-cap:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 4px rgb(var(--accent-rgb) / 0.3); }
@media (prefers-reduced-motion: reduce) {
  .cal-budget .bar span,
  .cal-cap::-webkit-slider-thumb,
  .cal-cap::-moz-range-thumb { transition: none; }
}
/* Where the time you said you have runs out. Dimming what is past the line
   rather than hiding it: the work is still yours, it just is not tonight. */
.cal-cutline {
  display: flex; align-items: center; gap: 10px;
  margin: 6px 2px 4px; font-size: 11px; font-weight: 600; color: var(--text-dim);
}
.cal-cutline::before, .cal-cutline::after {
  content: ""; flex: 1; height: 1px; background: var(--border-strong);
}
.cal-cutline span { white-space: nowrap; }
.cal-plan-item.is-spill { opacity: 0.55; }
.cal-plan-item.is-spill:hover, .cal-plan-item.is-spill:focus-within { opacity: 1; }

.cal-foot { padding: 2px 2px 0; font-size: 11px; color: var(--text-dim); line-height: 1.5; }

/* ── WEEK VIEW: seven days side by side, due above, plan below ──────────── */
.cal-week {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 10px; align-items: stretch;
}
.cal-wday {
  display: flex; flex-direction: column; min-width: 0; min-height: 320px;
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--bg-elev-1); overflow: hidden;
}
.cal-wday.is-today { border-color: rgb(var(--accent-rgb) / 0.5); }
.cal-wday.is-weekend { background: var(--bg-subtle); }
.cal-wday.is-focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgb(var(--accent-rgb) / 0.22);
}
.cal-wday-head {
  display: flex; align-items: baseline; gap: 6px;
  padding: 9px 10px; border-bottom: 1px solid var(--border);
  background: var(--bg-elev-2); cursor: pointer;
}
.cal-wday-head .dow {
  font-size: 10.5px; font-weight: 750; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-dim);
}
.cal-wday-head .dnum { font-size: 16px; font-weight: 750; color: var(--text); letter-spacing: -0.02em; }
.cal-wday.is-today .cal-wday-head .dow,
.cal-wday.is-today .cal-wday-head .dnum { color: var(--accent); }
.cal-wday-head .dmon {
  font-size: 10px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--accent);
}
.cal-wday-head .dcount { margin-left: auto; font-size: 10.5px; color: var(--text-dim); }

.cal-wsec { padding: 8px; display: flex; flex-direction: column; gap: 6px; }
/* A heavy day scrolls its own due list rather than pushing the plan section
   off the bottom of the column - the plan is the half you came to edit. */
.cal-wsec-due { flex: 1 1 auto; overflow-y: auto; min-height: 54px; }
.cal-wsec-label {
  font-size: 10px; font-weight: 750; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-dim); padding: 2px 2px 0;
}
.cal-wsec-plan { border-top: 1px dashed var(--border-strong); margin-top: auto; }
/* Hiding half the column is only worth doing if the other half takes the room
   back, so the survivor becomes the column. */
body[data-cal-shows="due"]  .cal-wsec-plan,
body[data-cal-shows="plan"] .cal-wsec-due { display: none; }
body[data-cal-shows="plan"] .cal-wsec-plan {
  flex: 1 1 auto; overflow-y: auto; margin-top: 0; border-top: 0;
}
/* The "Plan" heading separates the two halves. With only one half on screen
   there is nothing to separate it from, and seven of them is just noise. */
body[data-cal-shows="plan"] .cal-wsec-label { display: none; }
/* The day the ruler's needle is on, marked in the grid so the strip above and
   the week below are visibly the same selection. */
.cal-wday.is-sel .cal-wday-head { background: rgb(var(--accent-rgb) / 0.12); }

/* The week chip: the name and the class, with the class colour on the edge. */
.cal-chip {
  display: block; width: 100%; text-align: left; cursor: pointer;
  border: 1px solid var(--border); border-left: 3px solid var(--subj, var(--text-dim));
  border-radius: var(--r-sm); background: var(--bg-elev-2);
  padding: 7px 8px; transition: background 0.15s, box-shadow 0.15s;
}
.cal-chip:hover { background: var(--bg-elev-3); }
.cal-chip.is-focus {
  box-shadow: 0 0 0 2px rgb(var(--accent-rgb) / 0.45);
  background: var(--bg-elev-3);
}
/* Both lines clip - the title to two lines, the class to one - so both state
   their shape. rounded.css pills anything whose class says "chip", and on a box
   that clips its overflow a pill is a mask: it shaved the first letter off
   every line of every week card, so "Unit 4 Test" read "Jnit 4 Test". */
.cal-chip-title {
  font-size: 12px; font-weight: 650; line-height: 1.3; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  border-radius: 0;
}
/* A plan chip's title sits in its button (app.js calWeekPlanChip): the same
   two lines it was (the title keeps its clamp, which a button box cannot
   hold), and the keyboard's way into the details. */
.cal-chip-open {
  -webkit-appearance: none; appearance: none;
  display: block; width: 100%; margin: 0; padding: 0; border: 0; background: none;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.cal-chip-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cal-chip-class {
  display: block; margin-top: 3px; font-size: 10.5px; font-weight: 600;
  color: var(--subj-ink, var(--subj, var(--text-dim)));
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  border-radius: 0;
}
.cal-chip-kind {
  display: inline-block; margin-top: 4px;
  font-size: 10px; font-weight: 750; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--kind-ink, var(--text-dim));
}
.cal-chip-kind[data-kind="test"] { --kind-ink: var(--danger); }
.cal-chip-kind[data-kind="quiz"] { --kind-ink: var(--warning); }
.cal-chip-kind[data-kind="in_class"] { --kind-ink: var(--text-dim); }

.cal-wempty { font-size: 11px; color: var(--text-dim); padding: 4px 2px 6px; }
/* A column behind the student: what was due is a record worth reading, so it
   stays, just quieter than the days still to come. */
.cal-wday.is-gone { opacity: 0.66; }
.cal-wday.is-gone:hover { opacity: 1; }
/* Week plan chips can be dragged with a mouse; a finger gets these instead. */
.cal-chip-move { display: flex; gap: 4px; margin-top: 6px; }
.cal-chip-move .cal-mini { flex: 1; min-width: 0; padding-inline: 4px; }

/* A plan write in flight: the plan half dims and stops taking taps, so a
   slow network reads as "working" rather than "ignored me". */
body.cal-busy .cal-col-plan, body.cal-busy .cal-wsec-plan { opacity: 0.6; pointer-events: none; }
body.cal-busy .cal-mini { cursor: progress; }

/* ── Empty / error states ───────────────────────────────────────────────── */
.cal-empty {
  border: 1px dashed var(--border-strong); border-radius: var(--r-lg);
  padding: 28px 22px; text-align: center; color: var(--text-muted);
  background: var(--bg-subtle);
}
/* The heading only. Unscoped, this also caught the `<strong>` INSIDE the
   paragraph - the one that names the button to press - and turned a
   three-word emphasis into its own line, so "Hit / Plan with Aluma / at the
   top" read as a broken sentence rather than an instruction. */
.cal-empty > strong { display: block; font-size: 14.5px; color: var(--text); margin-bottom: 6px; }
.cal-empty p { margin: 0 0 14px; font-size: 12.5px; line-height: 1.55; }
.cal-empty .cal-empty-acts { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }

/* ── Go to: every dated assignment, most urgent first ───────────────────── */
.cal-goto-modal {
  max-width: 560px; width: 100%; max-height: min(86dvh, 680px);
  display: flex; flex-direction: column;
}
.cal-goto-modal .modal-actions, .cal-pick-modal .modal-actions { flex-shrink: 0; }
.cal-goto-modal .modal-sub { font-size: 12px; color: var(--text-dim); margin: 0 0 12px; line-height: 1.5; }
/* The search filters the list under it, so the two sit --filter-gap apart,
   like every filter row over a list in the app. It was 24px: the modal
   field's 10px margin plus the modal body's 14px over each block, which
   is (0,17,1) and so needs the bang to answer. */
.cal-goto-modal > #cal-goto-search,
.cal-pick-modal > #cal-pick-search { margin-bottom: 0; }
.cal-goto-modal > .cal-goto-list,
.cal-pick-modal > .cal-pick-list { margin-top: var(--filter-gap) !important; }
.cal-goto-list {
  margin-top: 10px; flex: 1 1 auto; min-height: 0; overflow-y: auto;
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  display: flex; flex-direction: column; gap: 6px;
}
.cal-goto-item {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 11px; cursor: pointer;
  border: 1px solid var(--border); border-left: 3px solid var(--subj, var(--text-dim));
  border-radius: var(--r-sm); background: var(--bg-elev-2);
}
.cal-goto-item:hover, .cal-goto-item:focus-visible { background: var(--bg-elev-3); }
.cal-goto-item .gi-body { min-width: 0; flex: 1; }
.cal-goto-item .gi-title {
  /* These two are <span>s, and .gi-body is a flex ITEM but not a flex
     CONTAINER, so they stayed `display: inline`. `overflow`, `text-overflow`
     and `margin-top` are all ignored on an inline box: the ellipsis below
     never rendered, the title and the meta ran together on one line, and a
     long assignment name was cut mid-word and slid under the due-date column
     instead. Measured on the seeded 120-character title, which reports
     scrollWidth 0 / clientWidth 0 precisely because the box is inline.
     `display: block` is what makes the truncation this rule already asks for
     actually happen. */
  display: block;
  font-size: 13px; font-weight: 650; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-goto-item .gi-meta {
  display: block;
  margin-top: 3px; font-size: 11px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-goto-item .gi-when {
  flex-shrink: 0; font-size: 11px; font-weight: 700; color: var(--text-muted);
  text-align: right; line-height: 1.3;
}
.cal-goto-item .gi-when.urgent { color: var(--danger); }
.cal-goto-item .gi-when.soon { color: var(--warning); }
.cal-goto-item.is-done { filter: grayscale(1); opacity: 0.45; }
.cal-goto-empty { padding: 18px 4px; font-size: 12.5px; color: var(--text-dim); text-align: center; }

/* ── Plan it myself ─────────────────────────────────────────────────────── */
.cal-pick-modal {
  max-width: 580px; width: 100%; max-height: min(86dvh, 680px);
  display: flex; flex-direction: column;
}
.cal-pick-modal .modal-sub { font-size: 12px; color: var(--text-dim); margin: 0 0 12px; line-height: 1.5; }
.cal-pick-list {
  margin-top: 10px; flex: 1 1 auto; min-height: 0; overflow-y: auto;
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  display: flex; flex-direction: column; gap: 6px;
}
.cal-pick-item { cursor: pointer; }
.cal-pick-item[disabled] { opacity: 0.5; cursor: not-allowed; }
.cal-pick-box {
  flex-shrink: 0; width: 18px; height: 18px; border-radius: 14px;
  border: 1.5px solid var(--border-strong); background: var(--bg);
  position: relative; transition: background 0.12s, border-color 0.12s;
}
.cal-pick-item.is-picked { border-color: rgb(var(--accent-rgb) / 0.5); background: var(--accent-soft); }
.cal-pick-item.is-picked .cal-pick-box { background: var(--accent); border-color: var(--accent); }
.cal-pick-item.is-picked .cal-pick-box::after {
  content: ""; position: absolute; left: 5px; top: 1.5px; width: 5px; height: 9px;
  border-right: 2px solid var(--accent-ink); border-bottom: 2px solid var(--accent-ink);
  transform: rotate(45deg);
}
.cal-pick-actions { display: flex; align-items: center; gap: 8px; }
.cal-pick-count { margin-right: auto; font-size: 12px; color: var(--text-dim); font-weight: 600; }

/* ── Plan with Aluma ────────────────────────────────────────────────────── */
.cal-dock {
  position: fixed; right: 22px; bottom: calc(22px + env(keyboard-inset-height, 0px)); z-index: 90;
  width: min(380px, calc(100vw - 32px));
  display: flex; flex-direction: column;
  border: 1px solid var(--border-strong); border-radius: var(--r-lg);
  background: var(--bg-elev-1);
  box-shadow: 0 22px 48px rgba(0, 0, 0, 0.42);
  overflow: hidden;
}
.cal-dock.hidden { display: none; }
.cal-dock-head {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 12px; border-bottom: 1px solid var(--border); background: var(--bg-elev-2);
}
.cal-dock-orb img { border-radius: 50%; display: block; }
.cal-dock-title { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.cal-dock-title strong { font-size: 13px; font-weight: 700; color: var(--text); }
.cal-dock-title small { font-size: 10.5px; color: var(--text-dim); }
.cal-dock-head .icon-btn { margin-left: auto; }
.cal-dock-log {
  padding: 10px 12px; display: flex; flex-direction: column; gap: 8px;
  max-height: 230px; overflow-y: auto; font-size: 12.5px; line-height: 1.5;
}
.cal-dock-msg { border-radius: var(--r-sm); padding: 7px 10px; }
.cal-dock-msg.you { align-self: flex-end; background: var(--accent-soft); color: var(--text); max-width: 88%; }
.cal-dock-msg.aluma { align-self: flex-start; background: var(--bg-elev-2); color: var(--text-muted); max-width: 92%; }
.cal-dock-msg.aluma .done-line { display: block; color: var(--text); font-weight: 600; }
.cal-dock-chips { display: flex; gap: 6px; flex-wrap: wrap; padding: 0 12px 8px; }
.cal-dock-chips button {
  font-size: 11px; font-weight: 600; padding: 5px 10px; cursor: pointer;
  border-radius: var(--r-full); border: 1px solid var(--border);
  background: var(--bg-elev-2); color: var(--text-muted);
}
.cal-dock-chips button:hover { background: var(--bg-elev-3); color: var(--text); }
.cal-dock-chips button.on { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.cal-dock-chips button.primary { color: var(--text); border-color: rgb(var(--accent-rgb) / 0.5); background: var(--accent-soft); font-weight: 700; }
.cal-dock-chips[hidden] { display: none; }
.cal-dock-head #cal-dock-restart { margin-left: auto; }
.cal-dock-head .icon-btn { margin-left: 0; }
.cal-dock-form {
  display: flex; align-items: center; gap: 7px;
  padding: 10px 12px; border-top: 1px solid var(--border); background: var(--bg-elev-2);
}
.cal-dock-form input {
  flex: 1; min-width: 0; font: inherit; font-size: 12.5px;
  padding: 8px 11px; border-radius: var(--r-full);
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
}
.cal-dock-form input:focus { border-color: var(--border-focus); }
.cal-dock-form input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cal-dock-mic {
  flex-shrink: 0; width: 34px; height: 34px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; border: 1px solid var(--border);
  background: var(--bg); color: var(--text-muted);
}
.cal-dock-mic svg { width: 16px; height: 16px; }
.cal-dock-mic:hover { color: var(--text); border-color: var(--border-strong); }
.cal-dock-mic.listening {
  color: var(--accent-ink); background: var(--accent); border-color: var(--accent);
  animation: calMicPulse 1.5s ease-in-out infinite;
}
@keyframes calMicPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgb(var(--accent-rgb) / 0.45); }
  50%      { box-shadow: 0 0 0 7px rgb(var(--accent-rgb) / 0); }
}
.cal-dock-form .primary-btn { flex-shrink: 0; padding: 8px 14px; font-size: 12.5px; }

/* ── Touch / narrow ─────────────────────────────────────────────────────── */
/* A pointer that cannot hover is a finger: give it the ruler and hide the
   arrows, which are a mouse affordance. Anything with a real cursor keeps the
   arrows, because a drag is a worse way to pick an exact day. */
@media (hover: none) and (pointer: coarse) {
  /* The day arrows go: the ruler is the better way to move by a day and a
     34px arrow is the worse way for a thumb. The WEEK arrows stay - stepping a
     week on the ruler means dragging seven ticks, which is not a gesture, it
     is a chore. */
  .cal-daynav { display: none; }
  /* A drag needs more room to be a drag. */
  .cal-ruler { height: 72px; }
  /* Nothing a thumb has to hit is under 44px. The tick keeps its 34px box
     (wireCalRuler divides by it) and grows a hit area with ::after instead. */
  /* .cal-cap is deliberately NOT here: it is a range input whose hit area is
     already 44px tall, and side padding on a range shrinks the track out from
     under the thumb. */
  .cal-mini { min-height: var(--tap-min, 44px); padding: 0 14px; font-size: 12.5px; }
  .cal-plan-acts, .cal-card-meta { gap: 8px; }
  .cal-arrow, .cal-dock-mic { width: var(--tap-min, 44px); height: var(--tap-min, 44px); }
  .cal-modes button { min-height: var(--tap-min, 44px); padding: 0 18px; font-size: 13.5px; }
  .cal-dock-chips button { min-height: var(--tap-min, 44px); padding: 0 14px; font-size: 12.5px; }
  .cal-shows button { min-height: var(--tap-min, 44px); padding: 0 14px; }
  .cal-topbar .ghost-btn, .cal-topbar .primary-btn, .cal-selbar .ghost-btn,
  .cal-empty-acts .ghost-btn, .cal-empty-acts .primary-btn, .cal-detail-acts .ghost-btn { min-height: var(--tap-min, 44px); }
  .cal-tick { position: relative; }
  .cal-tick::after { content: ""; position: absolute; inset: -8px -3px; }
  .cal-chip-kind, .cal-src, .cal-wsec-label, .cal-wday-head .dmon { font-size: 11px; }
  .cal-foot, .cal-wempty { font-size: 12px; }
}

/* Column count follows the calendar's own width. At iPad landscape the
   window is 1180px but the sidebar leaves the grid ~880px, which is seven
   118px columns - unreadable - so the viewport breakpoints below are only a
   fallback for browsers without container queries. */
@container calbody (max-width: 900px) { .cal-week { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@container calbody (max-width: 640px) { .cal-week { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container calbody (max-width: 400px) { .cal-week { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 1100px) {
  .cal-week { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .cal-body { padding: var(--filter-gap) 14px 96px; }
  .cal-selbar { padding: 8px 14px; }
  .cal-topbar { padding: 12px 14px; }
  .cal-topbar .topbar-right { width: 100%; }
  .cal-day { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .cal-week { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cal-dock {
    right: 8px; left: 8px;
    bottom: calc(72px + env(safe-area-inset-bottom, 0px) + env(keyboard-inset-height, 0px));
    width: auto;
  }
  .cal-dock-log { max-height: min(180px, 26dvh); }
}

@media (max-width: 560px) {
  .cal-week { grid-template-columns: minmax(0, 1fr); }
  .cal-modes button { padding: 6px 13px; }
}

/* Light theme: the greys need to go the other way to still read as "finished". */
[data-theme="light"] .cal-dock { box-shadow: 0 18px 40px rgba(20, 32, 26, 0.16); }
[data-theme="light"] .cal-card.is-done,
[data-theme="light"] .cal-chip.is-done,
[data-theme="light"] .cal-plan-item.is-done { opacity: 0.66; }

@media (prefers-reduced-motion: reduce) {
  body[data-view="calendar"] .view-calendar { animation: none; }
  .cal-dock-mic.listening { animation: none; }
  .cal-card, .cal-chip, .cal-arrow, .cal-tick .tick-mark { transition: none; }
}

/* ── Editable traits: the student's word on an assignment ────────────────────
   What a piece of work IS and how hard it will be are two of Aluma's
   readings, not facts about the assignment, so wherever one is shown open the
   badge is a control. Deliberately NOT scoped to .view-calendar: the same
   badge, the same vocabulary and the same menu appear in the Assignments
   drawer, and two spellings of one control is how they drift apart. */
.trait-badge {
  appearance: none; font: inherit; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
  position: relative;
  transition: border-color 120ms ease, background-color 120ms ease, transform 120ms ease;
}
.trait-badge:hover { border-color: var(--border-focus, var(--border-strong)); }
.trait-badge:active { transform: scale(0.97); }
.trait-badge:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
/* The chevron says "this opens something" without a second word of label. */
.trait-caret {
  width: 0; height: 0; margin-left: 1px;
  border-left: 3.5px solid transparent; border-right: 3.5px solid transparent;
  border-top: 4px solid currentColor; opacity: 0.6;
}
.trait-badge:hover .trait-caret { opacity: 1; }
/* A trait the student set reads as settled, not as a guess. */
.trait-badge.is-yours { border-style: solid; box-shadow: inset 0 0 0 1px currentColor; }
.cal-diff.is-yours { box-shadow: inset 0 0 0 1px currentColor; }
/* The Assignments card badge says the same thing the drawer badge does.
   `taskKindBadgeHTML` has always emitted this class; nothing defined it, so a
   kind the student corrected looked identical to one Aluma guessed. */
.task-kind.is-yours { box-shadow: inset 0 0 0 1px currentColor; }

/* The fact rows the badges sit in, in both drawers. The badge and the note
   beside it are two things on one line only while two fit: below that the
   note takes the next line. `min-width: 0` is what lets the row be narrower
   than its own content in the first place - without it the note holds the
   column open and the drawer runs off the side of a phone. */
.cal-fact-edit, .td-fact-edit {
  display: flex; align-items: center; gap: 4px 8px; flex-wrap: wrap;
  min-width: 0;
}
.cal-fact-note, .td-fact-note {
  font-size: 11px; color: var(--text-dim);
  min-width: 0; overflow-wrap: anywhere;
}

/* The dropdown. Fixed to the viewport and parented to <body>, so a scrolling
   card, a drawer or a modal can never clip it. */
.trait-menu {
  position: fixed; z-index: 4000;
  min-width: 212px; max-width: min(288px, calc(100vw - 16px));
  overflow-y: auto; overscroll-behavior: contain;
  padding: 6px;
  border-radius: var(--r-md, 12px);
  border: 1px solid var(--border-strong);
  background: var(--bg-elev-2, var(--bg-elev-1, #14161b));
  box-shadow: 0 18px 44px rgb(0 0 0 / 0.42);
  animation: traitMenuIn 120ms ease-out;
}
@keyframes traitMenuIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.trait-menu-head {
  padding: 6px 8px 7px; font-size: 10.5px; font-weight: 750;
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-dim);
}
.trait-menu-opts { display: grid; gap: 1px; }
.trait-opt {
  appearance: none; border: 0; background: none; font: inherit; cursor: pointer;
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 8px 9px; border-radius: var(--r-sm, 8px);
  font-size: 13px; color: var(--text);
}
.trait-opt:hover { background: var(--bg-elev-3, rgb(255 255 255 / 0.06)); }
.trait-opt:focus-visible { outline: 2px solid var(--border-focus); outline-offset: -2px; }
.trait-opt.is-on { font-weight: 650; }
.trait-opt.is-on::after { content: '✓'; margin-left: auto; color: var(--accent); font-size: 12px; }
/* The same colour the badge will take, so the menu shows the answer rather
   than describing it. */
.trait-opt-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--kind-bg, transparent);
  border: 1.5px solid var(--kind-ink, var(--text-dim));
}
.trait-opt-dot[data-kind="test"]     { --kind-ink: var(--danger);  --kind-bg: rgb(var(--danger-rgb) / 0.2); }
.trait-opt-dot[data-kind="quiz"]     { --kind-ink: var(--warning); --kind-bg: rgb(var(--warning-rgb) / 0.2); }
.trait-opt-dot[data-kind="in_class"] { --kind-ink: var(--text-dim); }
/* Themed tokens, like `.cal-kind` and `.task-kind`. These three carried the
   raw dark-mode hexes they shipped with, and light is the default theme: the
   dots came out at 1.52:1, 1.97:1 and 1.71:1 against the light surface, and
   each disagreed by 3+ points with the badge it is supposed to be previewing. */
.trait-opt-dot[data-kind="lab"]      { --kind-ink: var(--kind-lab); --kind-bg: rgb(var(--kind-lab-rgb) / 0.2); }
.trait-opt-dot[data-kind="project"]  { --kind-ink: var(--kind-project); --kind-bg: rgb(var(--kind-project-rgb) / 0.2); }
.trait-opt-dot[data-kind="essay"]    { --kind-ink: var(--kind-essay); --kind-bg: rgb(var(--kind-essay-rgb) / 0.2); }
.trait-opt-dot[data-kind="study"]    { --kind-ink: var(--accent); --kind-bg: rgb(var(--accent-rgb) / 0.2); }
.trait-opt-dot[data-kind="reading"]  { --kind-ink: var(--text-muted); }
.trait-opt-dot[data-kind="homework"] { --kind-ink: var(--success); --kind-bg: rgb(var(--success-rgb) / 0.2); }
.trait-opt-dot[data-level="light"]    { --kind-ink: var(--text-dim); }
.trait-opt-dot[data-level="moderate"] { --kind-ink: var(--text-muted); }
.trait-opt-dot[data-level="heavy"]    { --kind-ink: var(--warning); --kind-bg: rgb(var(--warning-rgb) / 0.2); }
.trait-opt-dot[data-level="hardest"]  { --kind-ink: var(--danger);  --kind-bg: rgb(var(--danger-rgb) / 0.2); }
.trait-opt-auto {
  margin-top: 3px; padding-top: 9px; border-radius: 0 0 var(--r-sm, 8px) var(--r-sm, 8px);
  border-top: 1px solid var(--border);
  font-size: 12.5px; color: var(--text-muted);
}
/* "Aluma is deciding this" is the state, not an action - it must not read as
   a button the student is being invited to press again. */
.trait-opt-auto.is-on { color: var(--text-dim); cursor: default; }
.trait-opt-auto.is-on:hover { background: none; }
.trait-menu-foot {
  margin: 6px 2px 2px; padding: 0 7px;
  font-size: 11px; line-height: 1.4; color: var(--text-dim);
  overflow-wrap: anywhere;
}
/* An option is a dot, a label and a tick. The label is the only part that can
   give, so it is the only part told it may wrap. */
.trait-opt { align-items: flex-start; min-width: 0; overflow-wrap: anywhere; }
.trait-opt-dot { margin-top: 0.3em; }
.trait-opt.is-on::after { flex: none; padding-left: 8px; }
.trait-badge { max-width: 100%; }

/* The kind badge on an Assignments card. Display only - the card's whole
   surface opens the drawer, and a menu inside it would fight that. */
.task-kind {
  display: inline-flex; align-items: center;
  font-size: 9.5px; font-weight: 750; letter-spacing: 0.05em; text-transform: none;
  padding: 2.5px 7px; border-radius: var(--r-xs, 6px);
  border: 1px solid var(--kind-line, var(--border-strong));
  color: var(--kind-ink, var(--text-muted));
  background: var(--kind-bg, transparent);
}
.task-kind[data-kind="test"]     { --kind-ink: var(--danger);  --kind-line: rgb(var(--danger-rgb) / 0.45);  --kind-bg: rgb(var(--danger-rgb) / 0.12); }
.task-kind[data-kind="quiz"]     { --kind-ink: var(--warning); --kind-line: rgb(var(--warning-rgb) / 0.42); --kind-bg: rgb(var(--warning-rgb) / 0.12); }
.task-kind[data-kind="in_class"] { --kind-ink: var(--text-dim); --kind-line: var(--border-strong); }
.task-kind[data-kind="lab"]      { --kind-ink: var(--kind-lab); --kind-line: rgb(var(--kind-lab-rgb) / 0.38); --kind-bg: rgb(var(--kind-lab-rgb) / 0.10); }
.task-kind[data-kind="project"]  { --kind-ink: var(--kind-project); --kind-line: rgb(var(--kind-project-rgb) / 0.38); --kind-bg: rgb(var(--kind-project-rgb) / 0.10); }
.task-kind[data-kind="essay"]    { --kind-ink: var(--kind-essay); --kind-line: rgb(var(--kind-essay-rgb) / 0.38); --kind-bg: rgb(var(--kind-essay-rgb) / 0.10); }
.task-kind[data-kind="study"]    { --kind-ink: var(--accent); --kind-line: rgb(var(--accent-rgb) / 0.38); --kind-bg: rgb(var(--accent-rgb) / 0.10); }
.task-kind[data-kind="reading"]  { --kind-ink: var(--text-muted); --kind-line: var(--border-strong); }
.task-kind[data-kind="homework"] { --kind-ink: var(--success); --kind-line: rgb(var(--success-rgb) / 0.36); --kind-bg: rgb(var(--success-rgb) / 0.10); }

/* Test Prep: one "From your classes" section holding both lists - the study
   programs school set going and the tests Aluma can still build a set for. */
.tp-from-classes { display: grid; gap: 8px; margin: 10px 0 4px; }
.tp-from-classes.hidden { display: none; }
.tp-from-classes > .tp-school-tests, .tp-from-classes > .tp-school-sec { margin: 0; }
.tp-school-tests { display: grid; gap: 8px; margin: 10px 0 4px; }
.tp-school-tests.hidden { display: none; }   /* `display: grid` otherwise ignores the class */
/* A calm section title, sentence case like every other one in the app; the
   uppercase tracked eyebrow is kept for the sidebar's tiny labels only. */
.tp-school-head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: 13px; font-weight: 700; letter-spacing: 0;
  text-transform: none; color: var(--text-dim);
}
.tp-school-head span { font-size: 12px; font-weight: 500; letter-spacing: 0; text-transform: none; }
.tp-school-item {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  appearance: none; font: inherit; cursor: pointer;
  padding: 11px 13px; border-radius: var(--r-md, 12px);
  border: 1px solid var(--border); background: var(--bg-elev-1);
  color: var(--text);
  transition: border-color 120ms ease, background-color 120ms ease;
}
.tp-school-item:hover { border-color: var(--border-strong); background: var(--bg-elev-2); }
.tp-school-item:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
.tp-school-item-text { display: grid; gap: 2px; min-width: 0; flex: 1; }
.tp-school-item-text strong { font-size: 13.5px; font-weight: 650; overflow-wrap: anywhere; }
.tp-school-item-text span { font-size: 11.5px; color: var(--text-dim); }
.tp-school-item-cta { font-size: 12px; font-weight: 650; color: var(--accent); flex: none; }
@media (prefers-reduced-motion: reduce) {
  .trait-menu { animation: none; }
  .trait-badge, .tp-school-item { transition: none; }
  .trait-badge:active { transform: none; }
}

/* ── an educator's sitting (specs: family-student 4.7) ─────────────────────
   A sitting an educator who helps the student added carries their name on
   its source chip, and one they moved says "Moved by Ms Ortiz" under it:
   the student always sees who changed their plan. Tokens only. */
.cal-src[data-src="educator"] { color: var(--accent); border-color: rgb(var(--accent-rgb) / 0.45); }
.cal-plan-why.is-by { color: var(--accent); }
/* Aluma's chip is accent ink on an accent outline too, so the educator's is
   filled: their name has to stand apart from "Aluma" at a glance. And the
   card itself takes an accent hairline (the class colour keeps its edge). */
.cal-src[data-src="educator"] { background: var(--accent-soft); }
/* And a mortarboard before the name: a person who helps them, not a
   session word ("Draft", "Review") in the same green. */
.cal-src[data-src="educator"]::before {
  content: "";
  display: inline-block;
  width: 11px;
  height: 11px;
  margin: 0 4px -1px 0;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21.4 10.9a1 1 0 0 0 0-1.8l-8.6-3.9a2 2 0 0 0-1.6 0L2.6 9.1a1 1 0 0 0 0 1.8l8.6 3.9a2 2 0 0 0 1.6 0z'/%3E%3Cpath d='M6 12.5V16a6 3 0 0 0 12 0v-3.5'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21.4 10.9a1 1 0 0 0 0-1.8l-8.6-3.9a2 2 0 0 0-1.6 0L2.6 9.1a1 1 0 0 0 0 1.8l8.6 3.9a2 2 0 0 0 1.6 0z'/%3E%3Cpath d='M6 12.5V16a6 3 0 0 0 12 0v-3.5'/%3E%3C/svg%3E") center / contain no-repeat;
}
.cal-plan-item.is-educator,
.cal-chip.is-educator {
  border-top-color: rgb(var(--accent-rgb) / 0.4);
  border-right-color: rgb(var(--accent-rgb) / 0.4);
  border-bottom-color: rgb(var(--accent-rgb) / 0.4);
}
.cal-plan-why.is-by > span:last-child { font-weight: 600; }
/* Week view: the same source chip under the class line, small, and "Moved
   by Ms Ortiz" under it when an educator moved the sitting (app.js
   calWeekWhoHTML). "You" and "Aluma" stay quiet there; seven columns of
   accent would hide the one that matters. */
.cal-chip-who { display: block; margin-top: 5px; line-height: 1; border-radius: 0; }
.cal-chip .cal-src {
  display: inline-block;
  max-width: 100%;
  padding: 2px 6px;
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: top;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cal-chip .cal-src:not([data-src="educator"]) { color: var(--text-dim); border-color: var(--border); }
.cal-chip-moved { margin-top: 5px; font-size: 12px; line-height: 1.35; border-radius: 0; }
/* The detail dialog: who added it and who moved it, under the title. */
.cal-detail-by { display: flex; flex-wrap: wrap; gap: 2px 16px; margin: -2px 0 8px; }
.cal-detail-by .cal-plan-why { margin-top: 0; font-size: 12.5px; }
/* "On your plan": a sitting an educator added or moved says so. */
.cal-mini.is-by { border-color: rgb(var(--accent-rgb) / 0.4); }
.cal-mini-by { color: var(--accent); }
