/* ==========================================================================
   View-specific styles
   ========================================================================== */

/* --------------------------------------------------------- dashboard -- */

.hero {
  position: relative;
  overflow: hidden;
  /* All three stops ride the accent ramp — a hardcoded tail here used to keep
     the hero purple no matter which accent was picked, which made the whole
     setting look broken since this is the largest thing on the dashboard. */
  background: linear-gradient(125deg, var(--brand-700) 0%, var(--brand-500) 55%, var(--brand-400) 100%);
  color: #fff;
  border-radius: var(--radius-lg);
  padding: 22px 24px;
  box-shadow: var(--shadow);
}
.hero::after {
  content: "";
  position: absolute;
  right: -60px; top: -80px;
  width: 260px; height: 260px;
  background: radial-gradient(circle, rgba(255,255,255,.16), transparent 68%);
  border-radius: 50%;
  pointer-events: none;
}
.hero .hero-eyebrow {
  font-size: .69rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; opacity: .82;
}
.hero .hero-title { font-size: 1.6rem; font-weight: 720; letter-spacing: -.03em; margin-top: 5px; }
.hero .hero-sub { opacity: .9; font-size: .875rem; margin-top: 3px; }

.hero-progress {
  margin-top: 14px;
  height: 6px;
  background: rgba(255,255,255,.22);
  border-radius: var(--radius-full);
  overflow: hidden;
  position: relative;
  z-index: 1;
}
.hero-progress > i {
  display: block; height: 100%;
  background: #fff;
  border-radius: var(--radius-full);
  box-shadow: 0 0 10px rgba(255,255,255,.5);
  transition: width 1s linear;
}

.live-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: #4ade80;
  box-shadow: 0 0 0 0 rgba(74, 222, 128, .7);
  animation: ping 2.2s infinite;
  flex-shrink: 0;
}
@keyframes ping {
  0%   { box-shadow: 0 0 0 0 rgba(74, 222, 128, .6); }
  70%  { box-shadow: 0 0 0 9px rgba(74, 222, 128, 0); }
  100% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0); }
}

.countdown {
  font-variant-numeric: tabular-nums;
  font-weight: 720;
  letter-spacing: -.02em;
}

/* --------------------------------------------------------- timetable -- */

.timetable {
  display: grid;
  gap: 5px;
  min-width: 620px;
}
.tt-head {
  font-size: .7rem; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-3);
  text-align: center; padding: 6px 2px;
}
.tt-time {
  font-size: .68rem; color: var(--text-3);
  text-align: right; padding-right: 8px;
  font-variant-numeric: tabular-nums;
  display: flex; flex-direction: column; justify-content: center;
  line-height: 1.3;
}
.tt-cell {
  min-height: 54px;
  border-radius: var(--radius-xs);
  padding: 6px 8px;
  color: #fff;
  font-size: .755rem;
  font-weight: 650;
  line-height: 1.25;
  display: flex; flex-direction: column; justify-content: center;
  overflow: hidden;
  cursor: pointer;
  transition: filter .12s var(--ease), transform .12s var(--ease);
}
.tt-cell:hover { filter: brightness(1.08); transform: scale(1.015); }
.tt-cell small { font-weight: 500; opacity: .88; font-size: .68rem; display: block; margin-top: 1px; }
.tt-cell.empty {
  background: var(--surface-2);
  border: 1px dashed var(--border);
  cursor: default;
}
.tt-cell.empty:hover { filter: none; transform: none; }
.tt-cell.now { box-shadow: 0 0 0 2px var(--text), 0 0 0 4px var(--surface); }

/* ---------------------------------------------------------- calendar -- */

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.cal-dow {
  background: var(--surface-2);
  padding: 8px 6px;
  text-align: center;
  font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-3);
}
.cal-day {
  background: var(--surface);
  min-height: 104px;
  padding: 6px;
  display: flex; flex-direction: column; gap: 3px;
  transition: background-color .1s var(--ease);
  cursor: pointer;
}
.cal-day:hover { background: var(--surface-2); }
.cal-day.dim { background: var(--surface-2); }
.cal-day.dim .cal-num { color: var(--text-3); }
.cal-day.today .cal-num {
  background: var(--brand-600); color: #fff;
  border-radius: 50%;
  width: 21px; height: 21px;
  display: grid; place-items: center;
}
.cal-num { font-size: .755rem; font-weight: 650; align-self: flex-start; }

/* Toolbar overflow, shared by any page whose header has more actions than a
   phone can show at once (Calendar, Notes, Flashcards).
   `.act-secondary` marks a button that folds away on narrow screens;
   `.act-overflow` is the ⋯ trigger that reveals the same actions in a menu.
   On desktop the secondaries show normally and the trigger is hidden, so
   nothing here applies until the width actually runs out. */
.act-overflow { display: none; }

@media (max-width: 640px) {
  .act-secondary { display: none; }
  .act-overflow { display: grid; }
}

/* U40 — workload heat: shade each day by how much is due, so a crunch
   week is visible before opening a single item. Levels 3-4 also carry
   an explicit "Xh due" chip, so the heaviest days never rest on color
   alone (U47). */
.cal-day[data-load="1"] { background: var(--load-1-bg); }
.cal-day[data-load="2"] { background: var(--load-2-bg); }
.cal-day[data-load="3"] { background: var(--warn-bg); }
.cal-day[data-load="4"] { background: var(--danger-bg); }
.cal-load-chip {
  align-self: flex-start;
  font-size: .62rem;
  font-weight: 700;
  color: var(--danger);
  background: var(--surface);
  border-radius: 4px;
  padding: 1px 5px;
}

.cal-pill {
  font-size: .66rem;
  font-weight: 600;
  padding: 2px 5px;
  border-radius: 4px;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1.4;
}
.cal-pill.more { background: transparent; color: var(--text-3); font-weight: 600; padding-left: 2px; }

/* On a phone the month grid used to set `font-size: 0` on every event pill
   and hide the "+N more" affordance — so a day's events became 5px coloured
   bars. That makes colour the only carrier of meaning, puts the text out of
   reach of a screen reader, leaves a 5px touch target, and hides the fact
   that more events exist. Keep the text, just make it small and clipped. */
@media (max-width: 720px) {
  .cal-day { min-height: 76px; }
  .cal-pill {
    font-size: .58rem;
    line-height: 1.5;
    padding: 0 3px;
    min-height: 14px;
    border-radius: 3px;
    margin-bottom: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .cal-pill.more { display: block; font-weight: 700; }
}

/* Narrower still: there genuinely isn't room for labels, so fall back to
   bars — but keep them tall enough to tap and to see, and keep "+N more"
   visible so the day is known to hold something. */
@media (max-width: 400px) {
  .cal-pill { font-size: 0; padding: 0; height: 8px; min-height: 8px; }
  .cal-pill.more { font-size: .58rem; height: auto; padding: 0 3px; }
}

/* agenda */
.agenda-day + .agenda-day { margin-top: 18px; }
.agenda-date {
  display: flex; align-items: baseline; gap: 8px;
  font-size: .78rem; font-weight: 700;
  color: var(--text-2);
  padding-bottom: 7px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 9px;
}
.agenda-date .dnum { font-size: 1.1rem; font-weight: 720; color: var(--text); }
.agenda-date.is-today .dnum { color: var(--brand-600); }

.agenda-row {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  margin-bottom: 7px;
}
.agenda-row:hover { border-color: var(--border-strong); }
.agenda-time {
  font-size: .74rem; color: var(--text-3);
  font-variant-numeric: tabular-nums;
  width: 74px; flex-shrink: 0;
}
.agenda-stripe { width: 3px; align-self: stretch; border-radius: 3px; flex-shrink: 0; }

/* ------------------------------------------------------------- board -- */

.board {
  display: grid;
  grid-template-columns: repeat(4, minmax(230px, 1fr));
  gap: 14px;
  overflow-x: auto;
  padding-bottom: 6px;
  align-items: start;
}
.board-col {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  min-height: 130px;
}
.board-col.drop { border-color: var(--brand-500); background: var(--brand-50); }
.board-col-head {
  display: flex; align-items: center; gap: 7px;
  padding: 2px 4px 10px;
  font-size: .78rem; font-weight: 700;
}
.board-col-head .n {
  font-size: .68rem; font-weight: 700;
  background: var(--surface-3); color: var(--text-2);
  padding: 1px 7px; border-radius: var(--radius-full);
  margin-left: auto;
}

.task-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand-500);
  border-radius: var(--radius-sm);
  padding: 10px 11px;
  margin-bottom: 8px;
  box-shadow: var(--shadow-xs);
  cursor: grab;
  transition: box-shadow .12s var(--ease), transform .12s var(--ease);
}
.task-card:hover { box-shadow: var(--shadow); transform: translateY(-1px); }
.task-card:active { cursor: grabbing; }
.task-card.dragging { opacity: .45; }
.task-card .tc-title { font-weight: 620; font-size: .835rem; line-height: 1.35; }
.task-card .tc-meta { display: flex; align-items: center; gap: 6px; margin-top: 7px; flex-wrap: wrap; }
.tc-high { display: inline-flex; align-items: center; gap: 4px; font-size: .68rem; font-weight: 700; color: var(--danger); }

/* assignment list row */
.hw-row {
  display: flex; align-items: flex-start; gap: 11px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  transition: background-color .1s var(--ease);
}
.hw-row:last-child { border-bottom: none; }
.hw-row:hover { background: var(--surface-2); }
.hw-row.done .hw-title { text-decoration: line-through; color: var(--text-3); }
.hw-title { font-weight: 600; font-size: .865rem; line-height: 1.35; transition: color .25s var(--ease); }
.hw-sub { display: flex; align-items: center; gap: 8px; margin-top: 4px; flex-wrap: wrap; }
.hw-stripe { width: 3px; align-self: stretch; border-radius: 3px; flex-shrink: 0; min-height: 30px; }

.prio { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.prio.high { background: var(--danger); }
.prio.med  { background: var(--warn); }
.prio.low  { background: var(--text-3); }

.subtask {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0;
  font-size: .8rem;
}
.subtask span { transition: color .25s var(--ease); }
.subtask.done span { text-decoration: line-through; color: var(--text-3); }

/* U23 — drag handle on a checklist step */
.drag-handle { cursor: grab; color: var(--text-3); flex-shrink: 0; touch-action: none; }
.drag-handle:active { cursor: grabbing; }
[data-st].dragging { opacity: .4; }

/* U19 — swipe-to-complete / swipe-to-snooze on mobile */
.hw-row { touch-action: pan-y; position: relative; }
.hw-row.swipe-done { background: var(--ok-bg); }
.hw-row.swipe-snooze { background: var(--warn-bg); }

/* U24 — keyboard list cursor (j/k) */
.hw-row.kb-cursor { box-shadow: inset 3px 0 0 var(--brand-600); background: var(--surface-2); }

/* U22 — inline-editable due badge in the list */
.due-inline { cursor: pointer; display: inline-flex; border-radius: var(--radius-xs); }
.due-inline:hover .badge, .due-inline:focus-visible .badge { box-shadow: 0 0 0 2px var(--brand-200); }
.due-inline:focus-visible { outline: none; }

/* U33 — the incremental-render sentinel is invisible, just a scroll trigger */
.list-sentinel { height: 1px; }

/* ------------------------------------------------------------ grades -- */

.grade-pill {
  display: inline-grid;
  place-items: center;
  min-width: 46px;
  height: 30px;
  padding: 0 9px;
  border-radius: var(--radius-sm);
  font-weight: 720;
  font-size: .9rem;
  font-variant-numeric: tabular-nums;
  background: var(--surface-3);
  color: var(--text);
}
.grade-pill.a { background: var(--ok-bg);     color: var(--ok); }
.grade-pill.b { background: var(--info-bg);   color: var(--info); }
.grade-pill.c { background: var(--warn-bg);   color: var(--warn); }
.grade-pill.d,
.grade-pill.f { background: var(--danger-bg); color: var(--danger); }

.grade-pill.lg { min-width: 62px; height: 40px; font-size: 1.2rem; }

.cat-row { display: grid; grid-template-columns: 1fr auto; gap: 8px 12px; align-items: center; padding: 7px 0; }
.cat-row + .cat-row { border-top: 1px solid var(--border); }

/* ------------------------------------------------------------ charts -- */

.chart { width: 100%; display: block; overflow: visible; }
.chart .grid-line { stroke: var(--border); stroke-width: 1; }
.chart .axis-text { fill: var(--text-3); font-size: 10px; font-family: var(--font); }
.chart .line { fill: none; stroke: var(--brand-500); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.chart .area { fill: url(#areaGrad); }
.chart .pt { fill: var(--surface); stroke: var(--brand-500); stroke-width: 2.5; }
.chart .bar-r { fill: var(--brand-500); transition: fill .12s var(--ease); }
.chart .bar-r:hover { fill: var(--brand-700); }
.chart .bar-r.alt { fill: var(--brand-300); }

/* U34/U35 — hover crosshair + drag-to-zoom brush on line() charts */
.chart-capture { cursor: crosshair; touch-action: none; }
.chart-crosshair-g .crosshair-line { stroke: var(--text-3); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-crosshair-g .crosshair-dot { fill: var(--brand-600); stroke: var(--surface); stroke-width: 2; }
.chart-brush-sel { fill: var(--brand-500); opacity: .15; }
.chart-tip {
  position: fixed;
  z-index: 80;
  pointer-events: none;
  background: var(--text);
  color: var(--bg);
  font-size: .72rem;
  font-weight: 600;
  padding: 4px 8px;
  border-radius: 6px;
  white-space: nowrap;
}

.legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 10px; }
.legend-item { display: flex; align-items: center; gap: 6px; font-size: .75rem; color: var(--text-2); }
.legend-swatch { width: 9px; height: 9px; border-radius: 3px; flex-shrink: 0; }

/* heatmap */
/* The week columns are implicit tracks, and an implicit `auto` track absorbs
   free space — so in a card wider than the four weeks it holds, the columns
   spread across it and the grid read as scattered dots rather than the
   GitHub-style block it is. Pinning the track to the cell width keeps the
   block compact, and gives .scroll-x a real width to scroll once a longer
   range is passed in. */
.heatmap {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(7, 1fr);
  grid-auto-columns: 13px;
  justify-content: start;
  gap: 3px;
}
.heat-cell {
  width: 13px; height: 13px;
  border-radius: 3px;
  background: var(--surface-3);
}
/* U47 — never color alone: intensity also reads as size, not just hue/shade,
   so the ramp still communicates to someone who can't distinguish the blues. */
.heat-cell[data-lv="0"] { transform: scale(.55); }
.heat-cell[data-lv="1"] { background: var(--brand-200); transform: scale(.68); }
.heat-cell[data-lv="2"] { background: var(--brand-400); transform: scale(.8); }
.heat-cell[data-lv="3"] { background: var(--brand-500); transform: scale(.9); }
.heat-cell[data-lv="4"] { background: var(--brand-700); transform: scale(1); }
[data-theme="dark"] .heat-cell[data-lv="1"] { background: #263166; }
[data-theme="dark"] .heat-cell[data-lv="2"] { background: #3a49a8; }

/* ----------------------------------------------------------- focus -- */

.timer-face {
  font-size: 4.2rem;
  font-weight: 730;
  letter-spacing: -.045em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
@media (max-width: 560px) { .timer-face { font-size: 3rem; } }

.timer-ring { transform: rotate(-90deg); }
.timer-ring .track { stroke: var(--surface-3); fill: none; }
.timer-ring .prog {
  stroke: var(--brand-500);
  fill: none;
  stroke-linecap: round;
  transition: stroke-dashoffset .5s linear;
}
.timer-ring.break .prog { stroke: var(--ok); }

/* ------------------------------------------------------- flashcards -- */

.flashcard {
  perspective: 1400px;
  height: 250px;
  cursor: pointer;
  user-select: none;
}
.flashcard-inner {
  position: relative;
  width: 100%; height: 100%;
  transition: transform .5s var(--ease);
  transform-style: preserve-3d;
}
.flashcard.flipped .flashcard-inner { transform: rotateY(180deg); }
.flashcard-face {
  position: absolute; inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  display: grid; place-items: center;
  padding: 26px;
  text-align: center;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow);
  font-size: 1.15rem;
  font-weight: 600;
  line-height: 1.45;
  overflow: hidden;
}
.flashcard-face.back {
  transform: rotateY(180deg);
  background: var(--brand-600);
  border-color: var(--brand-600);
  color: #fff;
}
.flashcard-face .face-tag {
  position: absolute; top: 12px; left: 14px;
  font-size: .65rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; opacity: .5;
}

.box-pips { display: flex; gap: 3px; }
.box-pip { width: 15px; height: 4px; border-radius: 2px; background: var(--surface-3); }
.box-pip.on { background: var(--brand-500); }

/* F027 cloze + F028 occlusion */
.cloze-blank { display: inline-block; min-width: 3em; border-bottom: 2px solid currentColor; opacity: .6; }
.cloze-answer { font-weight: 800; text-decoration: underline; text-underline-offset: 3px; }
.occlusion-wrap { position: relative; width: 100%; max-width: 100%; max-height: 100%; line-height: 0; }
.occlusion-img { width: 100%; max-height: 190px; object-fit: contain; border-radius: var(--radius-sm); display: block; }
.occlusion-region {
  position: absolute;
  display: grid; place-items: center;
  background: var(--ink, #1e293b);
  color: #fff;
  font-size: .7rem; font-weight: 700;
  border-radius: 3px;
  opacity: .88;
}
.occlusion-region.revealed { background: var(--ok); padding: 0 4px; }

/* ------------------------------------------------------------- notes -- */

.note-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 15px;
  cursor: pointer;
  transition: all .13s var(--ease);
  display: flex; flex-direction: column;
  min-height: 128px;
}
.note-card:hover { box-shadow: var(--shadow); transform: translateY(-2px); border-color: var(--border-strong); }
.note-card .n-title { font-weight: 650; font-size: .9rem; }
.note-card .n-body {
  font-size: .8rem; color: var(--text-2); margin-top: 6px;
  display: -webkit-box; -webkit-line-clamp: 4; line-clamp: 4;
  -webkit-box-orient: vertical; overflow: hidden;
  white-space: pre-wrap;
}
.note-card .n-foot { margin-top: auto; padding-top: 10px; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.tag {
  font-size: .68rem; font-weight: 600;
  background: var(--surface-3); color: var(--text-2);
  padding: 1px 7px; border-radius: var(--radius-full);
}

/* ------------------------------------------------------------ habits -- */

.habit-week { display: flex; gap: 4px; }
.habit-day {
  width: 27px; height: 27px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  display: grid; place-items: center;
  font-size: .64rem; font-weight: 700;
  color: var(--text-3);
  transition: all .12s var(--ease);
}
.habit-day:hover { border-color: var(--brand-400); }
.habit-day.on { background: var(--brand-600); border-color: var(--brand-600); color: #fff; }
.habit-day.future { opacity: .4; pointer-events: none; }

/* ------------------------------------------------------------ misc -- */

.kbd {
  font-family: var(--font);
  font-size: .68rem; font-weight: 650;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: 5px;
  padding: 1px 6px;
  color: var(--text-2);
}

.pin { color: var(--warn); }

.scroll-x { overflow-x: auto; }

@media print {
  .sidebar, .topbar, .page-actions, .toast-host,
  .mobile-tabbar, .fab-add, .ptr-indicator { display: none !important; }
  .page { overflow: visible; padding: 0; }
  .card { box-shadow: none; break-inside: avoid; }

  /* U36 — a schedule to pin on a wall: the scroll clip and card frame
     are for screen only, and the class colors need to survive printing. */
  .print-sheet { border: none; box-shadow: none; }
  .print-sheet .scroll-x { overflow: visible; }
  /* The print rules reset .page, but html, body and .shell are all
     height:100% with .main overflow:hidden, so the printed output was
     clipped to one viewport-height page — a 40-assignment list printed as
     one page and the rest was simply unreachable. */
  html, body, .shell, .main, .page {
    height: auto !important;
    min-height: 0 !important;
    max-height: none !important;
    overflow: visible !important;
  }
  .scroll-x, .week-wrap, .board, .tl { overflow: visible !important; }
  .timetable { min-width: 0 !important; }
  .tt-cell, .cal-day[data-load], .cal-pill {
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }

  /* U36 — printing while a modal is open (transcript, formula sheets)
     should print that modal's full content, not a clipped fixed-position
     box over a dark backdrop. */
  body:has(.modal-root) > *:not(.modal-root) { display: none !important; }
  .modal-root {
    position: static !important;
    inset: auto !important;
    background: none !important;
    backdrop-filter: none !important;
    padding: 0 !important;
    display: block !important;
  }
  .modal {
    max-width: none !important;
    max-height: none !important;
    width: auto !important;
    box-shadow: none !important;
    border: none !important;
  }
  .modal-body { overflow: visible !important; }
  .modal-head [data-close], .modal-foot { display: none !important; }
}

/* ==========================================================================
   Week view (calendar time grid)
   ========================================================================== */

.week-wrap { overflow-x: auto; }

.week-grid {
  display: grid;
  grid-template-columns: 62px repeat(7, minmax(94px, 1fr));
  grid-template-rows: auto var(--wk-h, 600px);
  min-width: 760px;
  border: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
  overflow: hidden;
  background: var(--surface);
}

.week-corner { background: var(--surface-2); border-bottom: 1px solid var(--border); }

.week-head {
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  border-left: 1px solid var(--border);
  padding: 7px 4px;
  text-align: center;
  cursor: pointer;
}
.week-head:hover { background: var(--surface-3); }
.week-head .week-num { font-size: 1.05rem; font-weight: 700; line-height: 1.15; }
.week-head.is-today .week-num { color: var(--brand-600); }
[data-theme="dark"] .week-head.is-today .week-num { color: var(--brand-300); }

.week-gutter { position: relative; background: var(--surface); }
.week-hour {
  position: absolute; right: 6px;
  transform: translateY(-50%);
  font-size: .64rem;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.week-col {
  position: relative;
  border-left: 1px solid var(--border);
  transition: background-color .12s var(--ease);
}
.week-col.is-today { background: var(--brand-50); }
[data-theme="dark"] .week-col.is-today { background: rgba(79, 70, 229, .07); }
.week-col.drop { background: var(--brand-100); }
/* U30 — month cells are drop targets too, so a drag can start anywhere. */
.cal-day.drop { background: var(--brand-100); outline: 2px solid var(--brand-500); outline-offset: -2px; }

.week-line {
  position: absolute; left: 0; right: 0;
  border-top: 1px solid var(--border);
  pointer-events: none;
  opacity: .65;
}

.week-now {
  position: absolute; left: 0; right: 0;
  height: 2px;
  background: var(--danger);
  z-index: 5;
  pointer-events: none;
}
.week-now::before {
  content: "";
  position: absolute; left: -3px; top: -3px;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--danger);
}

.week-ev {
  position: absolute;
  left: 3px; right: 3px;
  border-radius: 5px;
  padding: 3px 5px;
  font-size: .66rem;
  line-height: 1.2;
  overflow: hidden;
  cursor: pointer;
  z-index: 2;
  box-shadow: 0 1px 2px rgba(0,0,0,.12);
}
.week-ev:hover { filter: brightness(1.07); z-index: 6; }

.week-plan {
  position: absolute;
  left: 3px; right: 3px;
  border: 1.5px dashed currentColor;
  border-radius: 5px;
  padding: 2px 5px;
  font-size: .64rem;
  font-weight: 600;
  background: var(--surface);
  overflow: hidden;
  cursor: grab;
  z-index: 3;
}
.week-plan:active { cursor: grabbing; }

.week-allday {
  display: grid;
  grid-template-columns: 62px repeat(7, minmax(94px, 1fr));
  min-width: 760px;
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--surface-2);
}
.week-allday-col {
  border-left: 1px solid var(--border);
  padding: 5px 4px;
  min-height: 34px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.week-allday-col .cal-pill { cursor: pointer; }

/* ==========================================================================
   Markdown rendering
   ========================================================================== */

.md { line-height: 1.62; font-size: .885rem; }
.md > *:first-child { margin-top: 0; }
.md p { margin: 0 0 .7em; }
.md .md-h { margin: 1.1em 0 .45em; line-height: 1.3; }
.md h3.md-h { font-size: 1.03rem; }
.md h4.md-h { font-size: .93rem; }
.md h5.md-h, .md h6.md-h { font-size: .86rem; color: var(--text-2); }

.md .md-list { margin: 0 0 .7em; padding-left: 1.3em; }
.md .md-list li { margin-bottom: .22em; }
.md .md-tasks { list-style: none; padding-left: .2em; }
.md .md-task { display: flex; gap: 7px; align-items: baseline; }
.md .md-task.done { color: var(--text-3); text-decoration: line-through; }
.md .md-box { color: var(--brand-text); flex-shrink: 0; }

.md .md-code {
  background: var(--surface-3);
  padding: 1px 5px;
  border-radius: 4px;
  font-family: var(--mono);
  font-size: .86em;
}
.md .md-pre {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 11px 13px;
  overflow-x: auto;
  margin: 0 0 .8em;
}
.md .md-pre code { font-family: var(--mono); font-size: .8rem; line-height: 1.5; }

.md .md-quote {
  border-left: 3px solid var(--brand-300);
  background: var(--surface-2);
  margin: 0 0 .8em;
  padding: 8px 13px;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  color: var(--text-2);
}
.md .md-quote p:last-child { margin-bottom: 0; }

.md .md-hr { border: none; border-top: 1px solid var(--border); margin: 1.2em 0; }
.md .md-mark { background: var(--warn-bg); color: var(--warn); padding: 0 3px; border-radius: 3px; }
.md .md-table { margin-bottom: .8em; }
.md a { text-decoration: underline; text-underline-offset: 2px; }

/* ==========================================================================
   Misc additions
   ========================================================================== */

.spin { animation: spin 1.1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.toast .t-action { flex-shrink: 0; align-self: center; }

@media (max-width: 640px) {
  .week-grid, .week-allday { min-width: 640px; }
}

/* F059 — a teacher's post in a group feed. The left rail is a second,
   non-colour channel alongside the "From your teacher" badge, so the
   distinction survives greyscale and colour-vision differences. */
.feed-item-official {
  border-left: 3px solid var(--brand-600);
  background: var(--brand-50);
  border-radius: var(--radius-xs);
}
.feed-item-official .title { font-weight: 700; }

/* ==========================================================================
   Guidance — recommendation cards that show their working

   The visual job here is different from every other view: these are claims
   about someone's future, so the evidence behind each one has to read as part
   of the card rather than as small print under it. Hence the reason list gets
   its own indented rail with a glyph per evidence type, and cautions are the
   same shape as endorsements so a warning can't be mistaken for decoration.
   ========================================================================== */

.guide-sec { margin-bottom: 24px; }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.section-head h2 { font-size: 1.05rem; margin: 0; }

/* ---- callout: a statement the page is making, not a control ------------- */
.callout {
  border: 1px solid var(--border);
  border-left: 3px solid var(--text-3);
  border-radius: var(--radius-sm);
  background: var(--surface);
  padding: 12px 14px;
}
.callout.ok     { border-left-color: var(--ok);     background: var(--ok-bg, var(--surface)); }
.callout.warn   { border-left-color: var(--warn);   background: var(--warn-bg, var(--surface)); }
.callout.danger { border-left-color: var(--danger); background: var(--danger-bg, var(--surface)); }
.callout.info   { border-left-color: var(--info);   background: var(--info-bg, var(--surface)); }
.callout strong { color: var(--text); }

/* ---- the cards ---------------------------------------------------------- */
.guide-card .card-body { padding-bottom: 14px; }

.rank {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: .8rem;
  color: var(--text-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  min-width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

/* ---- evidence rail ------------------------------------------------------ */
.reasons { list-style: none; margin: 12px 0 0; padding: 0; }
.reasons li {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  gap: 8px;
  align-items: start;
  font-size: .88rem;
  color: var(--text-2);
  padding: 4px 0;
}
.reasons .r-ico {
  color: var(--brand-600);
  font-size: .8rem;
  line-height: 1.5;
  text-align: center;
}
/* A caution is the same shape as an endorsement so it can't read as garnish;
   only the glyph and its colour separate them, and the text carries the rest. */
.reasons li.caution .r-ico { color: var(--warn); font-weight: 700; }
.reasons li.caution { color: var(--text-2); }

.reality {
  margin-top: 12px;
  padding: 10px 12px;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  font-size: .86rem;
  color: var(--text-2);
}
.reality strong { color: var(--text); }

.opens { margin-top: 12px; }
.opens-k {
  font-size: .7rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-3);
  font-weight: 600;
}

/* A chip that is a label, not a control — no hover, no pointer. */
.chip.static { cursor: default; }
.chip.static:hover { border-color: var(--border); color: var(--text-2); }

.chip-x {
  border: 0;
  background: none;
  color: var(--text-3);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 0 0 0 6px;
  margin: 0;
}
.chip-x:hover { color: var(--danger); }

/* Full-width tab strip, as opposed to the compact one in a page header. */
.segmented.wide { width: 100%; }
.segmented.wide button { flex: 1; }

@media (max-width: 560px) {
  .reasons li { grid-template-columns: 16px minmax(0, 1fr); gap: 6px; }
  .rank { min-width: 20px; height: 20px; }
}

/* The extracted-text sample shown when a link can't be read. Monospaced and
   scroll-capped: it is evidence to glance at, not content to read, and an
   unbounded dump of a page's navigation would bury the fix underneath it. */
.read-sample {
  margin: 8px 0 0;
  padding: 10px 12px;
  max-height: 200px;
  overflow: auto;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .78rem;
  line-height: 1.5;
  color: var(--text-2);
  white-space: pre-wrap;
  word-break: break-word;
}

.link-candidate {
  justify-content: flex-start;
  text-align: left;
  width: 100%;
  white-space: normal;
}
.link-candidate .tag {
  background: var(--surface-2);
  color: var(--text-3);
  margin-right: 6px;
}

/* ==========================================================================
   Study shop — proof gallery and the cosmetics for sale

   Two jobs, one screen: show that the photos are real records (a gallery of
   what you actually did, at a size where the page is still legible), and
   show what a cosmetic looks like *before* the tokens are spent, so nothing
   in here is bought blind.
   ========================================================================== */

.token-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px 4px 8px;
  border-radius: var(--radius-full);
  background: var(--brand-50);
  color: var(--brand-600);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
[data-theme="dark"] .token-pill { color: var(--brand-300); }
.token-pill .tk { font-size: 1rem; line-height: 1; }
.token-pill.lg { font-size: 1.15rem; padding: 7px 16px 7px 12px; }

/* The drop target for a photo. It is a <label> wrapping a file input, so a
   click, a tap, a keyboard Enter and a drag all reach the same control. */
.proof-drop {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 26px 18px;
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-2);
  text-align: center;
  cursor: pointer;
  transition: border-color .14s var(--ease), background .14s var(--ease);
}
.proof-drop:hover, .proof-drop.drag { border-color: var(--brand-500); background: var(--brand-50); }
.proof-drop:focus-within { box-shadow: var(--ring); border-color: var(--brand-500); }
.proof-drop .ico { font-size: 1.7rem; line-height: 1; }
.proof-drop input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

.proof-preview {
  width: 100%; max-height: 260px; object-fit: contain;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
}

/* The quiz (F158). A question is a <fieldset> so its options are announced as
   one group, and each option is a <label> wrapping its radio so the whole row
   is a target rather than the 13px dot. */
.quiz-list { display: flex; flex-direction: column; gap: 14px; }

.quiz-q {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px 10px;
  background: var(--surface-2);
  margin: 0;
}
.quiz-q legend { padding: 0 6px; margin-left: -6px; }

.quiz-choice {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 10px;
  margin-top: 6px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background .12s var(--ease), border-color .12s var(--ease);
}
.quiz-choice:hover { background: var(--surface-3); }
.quiz-choice:focus-within { box-shadow: var(--ring); border-color: var(--brand-500); }
/* Never colour alone (U47): a chosen option gets a border and a weight change,
   not just a tint. */
.quiz-choice:has(input:checked) {
  background: var(--brand-50);
  border-color: var(--brand-500);
  font-weight: 600;
}
.quiz-choice input { margin-top: 3px; flex: none; }
.quiz-choice[hidden] { display: none; }

[data-theme="dark"] .quiz-choice:has(input:checked) { background: var(--surface-3); }

.proof-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 12px;
}
.proof-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
}
.proof-thumb {
  display: block; width: 100%; aspect-ratio: 4 / 3;
  background: var(--surface-3);
  border: none; padding: 0; cursor: zoom-in;
}
.proof-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.proof-thumb:focus-visible { box-shadow: var(--ring); }
/* The blob is gone (cleared site data, or a backup opened on another
   device). The record is still true, so the card keeps its numbers and says
   what happened instead of showing a broken image. */
.proof-thumb-missing {
  display: flex; align-items: center; justify-content: center;
  width: 100%; aspect-ratio: 4 / 3;
  background: var(--surface-2);
  color: var(--text-3);
  font-size: .72rem;
  text-align: center;
  padding: 10px;
}
.proof-meta { padding: 9px 11px; display: flex; flex-direction: column; gap: 3px; }

.shop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(212px, 1fr));
  gap: 12px;
}
.shop-item {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.shop-item.owned { border-color: var(--brand-200); }
.shop-item.worn { border-color: var(--brand-500); box-shadow: 0 0 0 1px var(--brand-500); }
.shop-item .shop-foot { margin-top: auto; display: flex; align-items: center; gap: 8px; }

/* Two stacked bars: the accent as a fill, and the same accent as it reads on
   a dark surface — the pair a student is actually choosing between. */
.shop-swatch {
  display: flex; height: 54px; border-radius: var(--radius-sm); overflow: hidden;
  border: 1px solid var(--border);
}
.shop-swatch span { flex: 1; }

.ring-preview {
  display: flex; align-items: center; justify-content: center;
  height: 54px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

.plate-preview {
  display: flex; align-items: center; justify-content: center;
  height: 54px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

/* The nameplate as it appears in the sidebar, under the student's name. */
.nameplate {
  display: inline-block;
  max-width: 100%;
  padding: 1px 7px;
  border-radius: var(--radius-full);
  background: var(--brand-50);
  color: var(--brand-600);
  font-size: .655rem;
  font-weight: 700;
  letter-spacing: .01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
[data-theme="dark"] .nameplate { color: var(--brand-300); }

/* Reads exactly like .field-error, but is deliberately NOT that class: the
   modal's own submit handler removes every .field-error in the form before
   handing over, and this message has to survive its own submit to explain
   why a photo was refused. */
.proof-error {
  margin-top: 4px;
  color: var(--danger);
  display: flex;
  align-items: flex-start;
  gap: 4px;
}
.proof-error::before { content: "⚠"; flex: none; line-height: 1.35; }
.proof-error[hidden] { display: none; }

.earn-rules { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 5px; }
.earn-rules li { color: var(--text-2); font-size: .8rem; }

/* ------------------------------------------------- daily quote (dashboard) */

/* Styled through tokens only, so it follows the accent picker and both
   themes without a second definition. The left rule and the oversized
   opening mark are the whole decoration — this sits directly under the hero
   banner and would fight it if it were another filled card. */
.quote-card {
  position: relative;
  margin: 0;
  padding: 16px 18px 16px 22px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand-500);
  border-radius: var(--radius);
  box-shadow: var(--shadow-xs);
}
.quote-card blockquote {
  margin: 0;
  font-size: 1.02rem;
  line-height: 1.55;
  color: var(--text);
  text-wrap: pretty;
}
.quote-card blockquote::before { content: "\201C"; }
.quote-card blockquote::after  { content: "\201D"; }

.quote-card figcaption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin-top: 10px;
  font-size: .82rem;
}
.quote-card figcaption a {
  font-weight: 650;
  color: var(--brand-text);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.quote-card figcaption a:hover,
.quote-card figcaption a:focus-visible { border-bottom-color: currentColor; }
.quote-card figcaption a::before { content: "\2014\00a0"; color: var(--text-3); }

.quote-card .quote-source { color: var(--text-3); font-size: .76rem; }

/* ==========================================================================
   Mobile pass — touch targets and page toolbars

   This block lives at the end of the last-loaded stylesheet on purpose.
   The original `@media (pointer: coarse)` rules sit in layout.css, which
   loads second of four; every component size in components.css and
   views.css therefore overrode them at equal specificity and won on order
   alone. `.habit-day` is the clearest case — layout.css asks for 36px, the
   base rule here sets 27px, and 27px is what a phone actually rendered.
   Re-stating the touch sizes here is what makes them take effect.

   Measured at 375x667 with a real touch pointer: every view had at least
   one control under the 44px WCAG 2.5.5 minimum, the worst being the
   28px-tall segmented buttons used for view switching on six pages.
   ========================================================================== */

@media (pointer: coarse) {
  .btn { min-height: 44px; }
  .btn-sm { min-height: 40px; }
  .segmented { padding: 4px; }
  .segmented button { min-height: 40px; padding-left: 14px; padding-right: 14px; }
  .input, .select, .textarea { min-height: 44px; }
  .input-sm, .select.input-sm, .input.input-sm { min-height: 40px; }
  .tab { min-height: 44px; }
  .check { width: 24px; height: 24px; }
  .habit-day { width: 36px; height: 36px; }
  /* An inline due-date chip is a real tap target for editing the date, but
     it sits inside a text row, so it gets its padding from the row rather
     than a fixed box. */
  .due-inline { min-height: 32px; align-items: center; }

  /* The help dot stays a 16px dot — it sits inline in a heading, and a 40px
     circle there would read as a button competing with the heading. Padding
     plus content-box clipping grows the TAP TARGET to 40px while the painted
     circle stays 16px, and the negative margin keeps the surrounding text
     laid out exactly as before. */
  .help-hint {
    box-sizing: content-box;
    padding: 12px;
    margin: -12px;
    background-clip: content-box;
  }
}

/* --------------------------------------------------- page toolbars ----- */

/* Every page header puts its title and its actions in one flex row, and on
   a phone the actions wrapped raggedly: buttons of different label lengths
   broke into two or three uneven lines with no hierarchy, so nothing read
   as the main action. Notes reached six buttons across three rows and
   flashcards five, before a single note or deck was visible.

   The rule below gives the whole app one predictable shape on narrow
   screens: mode switchers take a full-width row of equal segments, the
   primary action takes a full-width row of its own at the end, and
   whatever is left shares the row between them. */
@media (max-width: 640px) {
  .page-head { gap: 12px; flex-direction: column; align-items: center; text-align: center; }
  .page-actions { width: 100%; gap: 8px; justify-content: center; flex-wrap: wrap; }

  .page-actions > .segmented { flex: 1 1 100%; }
  .page-actions > .segmented button { flex: 1; text-align: center; }

  .page-actions > .btn:not(.btn-primary) { flex: 1 1 auto; }

  /* Last and full width: the one action the page is for, sized like a
     phone's primary button rather than competing with the rest. */
  .page-actions > .btn-primary { flex: 1 1 100%; order: 99; }

  /* An overflow trigger belongs beside the primary action, not stretched. */
  .page-actions > .icon-btn { flex: 0 0 auto; order: 98; }
}

/* Checkboxes stay at 24px on touch, which is a decision rather than an
   oversight: 24x24 is exactly the WCAG 2.2 AA minimum (SC 2.5.8). The 44px
   used above is the stricter iOS guideline, which a dense list of
   assignments cannot meet without a painted square that dominates the row.

   Worth recording for whoever tries to improve this: growing the hit area
   with a transparent `::before` on the input does not work. <input> is a
   replaced element and renders no pseudo-elements, so such a rule is
   silently inert — elementFromPoint returns nothing at the offset it is
   supposed to cover. Enlarging the target properly means making the
   surrounding label or row the tap target, which is a markup change per
   call site rather than a stylesheet rule. */

/* ==========================================================================
   Tier identity on a shop card (views/shop.js)

   Carried by a label AND a colour, never colour alone — the same rule the
   class palette follows, for the same reason. The stripe sits on top of
   main's .shop-item box rather than replacing it.
   ========================================================================== */

.shop-item { border-top: 3px solid var(--border); }
.shop-item.tier-common { border-top-color: #64748b; }
.shop-item.tier-rare   { border-top-color: #2a78d6; }
.shop-item.tier-ultra  { border-top-color: #7c3aed; }
.shop-item.tier-elite  { border-top-color: #d4a017; }

.shop-item.tier-elite {
  background: linear-gradient(180deg, rgba(212, 160, 23, .08), transparent 120px), var(--surface);
}

.tier-head { margin: 0; font-size: 1.02rem; }
.tier-head.tier-common { color: var(--text-2); }
.tier-head.tier-rare   { color: #2a78d6; }
.tier-head.tier-ultra  { color: #7c3aed; }
.tier-head.tier-elite  { color: #a97a06; }
[data-theme="dark"] .tier-head.tier-rare  { color: #6ea8f0; }
[data-theme="dark"] .tier-head.tier-ultra { color: #b49bfa; }
[data-theme="dark"] .tier-head.tier-elite { color: #e8bf52; }

/* An alarm or a boost has no colour pair to show, so its swatch carries a
   glyph at the same size instead of two empty halves. */
.shop-swatch.shop-glyph {
  display: grid;
  place-items: center;
  font-size: 1.3rem;
}
