/* Planality — "Technical Luxury" restyle layer (Google/Stitch, 2026-07-30).

   Loaded LAST, on purpose. The first attempt lived in components.css, which
   the cascade loads BEFORE views.css — so every rule lost to an
   equal-specificity rule there, and the tinted card header and the small
   greeting survived untouched. A trailing layer wins by ORDER rather than by
   specificity hacks, and keeps the whole overhaul in one file that can be
   deleted to revert cleanly.

   STYLE ONLY: this redesign modified no JS module.
*/


body {
  letter-spacing: var(--track-normal);
  font-weight: var(--fw-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Numbers must not dance as they change. Applied at the ROOT rather than by
   a hand-listed set of selectors — the first pass enumerated a dozen classes
   and still missed the Home stat tiles and the calendar gutter. Proportional
   digits are then restored for the display greeting, where tabular spacing
   looks mechanical rather than typeset. */
:root { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }
.tabular { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }
.home-head h2, .greeting, h1 {
  font-variant-numeric: normal; font-feature-settings: normal;
}

/* ── Type roles ─────────────────────────────────────────────────────────
   The greeting is the one display-scale element on a screen. */
.home-head h2 {
  font-size: var(--fs-display);
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-tight);
  line-height: 1.05;
}

/* Uppercase micro-label. Applied to the metadata the reference sets in
   caps: stat captions, column headers, section eyebrows. */
.eyebrow, .stat .k, .rail-head > span:first-child, .tm-stat + .hmeta,
.review .stat-chips .k, .set-sec > header p.caps {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--text-3);
}

/* Card and panel titles: bold, tight, high-contrast. */
.hcard-title, .rail-head, .review h3, .set-sec > header h2, .modal h2 {
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-normal);
}

/* ── The high-precision card ────────────────────────────────────────────
   A module: hairline perimeter, no shadow, border brightens on hover.
   The header is NOT tinted — the card already sits a step above a true
   black page, so the hairline is enough. */
.hcard-head {
  background: transparent;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-lg);
}
.hcard:hover { box-shadow: none; border-color: var(--border-strong); }
.hcard-title svg { color: var(--accent); }

/* ── Category ribbons ───────────────────────────────────────────────────
   2px on the extreme left edge. The app already sets --chip-c / --pc per
   course, so the ribbon reads the colour that is already there. */
.tg-block, .ur-row, .hrow.dl-row, .review-row, .tmod {
  border-left: 2px solid transparent;
}
.tg-block { border-left-color: var(--chip-c, var(--accent)); }
.tmod { border-left-color: var(--pc, transparent); }
.ur-row { border-left-color: var(--cat-gray); }
.hrow.dl-row:has(.hdot) { border-left-color: transparent; }

/* Calendar event: uppercase course eyebrow, bold title, ribbon edge. */
.tg-block .t, .tg-block .title { font-weight: var(--fw-bold); }
.tg-block .when, .tg-block .sub {
  font-size: var(--fs-eyebrow); letter-spacing: var(--track-caps);
  text-transform: uppercase; color: var(--text-2);
}

/* ── Buttons: rectangles, not pills ─────────────────────────────────────
   DESIGN.md: "Avoid pill shapes for buttons; keep them rectangular with
   the standard 8px radius to maintain the engineered aesthetic." This
   overrides the pill treatment added earlier the same week. */
.btn { border-radius: var(--r-md); font-weight: var(--fw-med); }
#toolbar .btn.pill, #toolbar .pill-group, #toolbar .segmented.pill,
#toolbar .segmented.pill button { border-radius: var(--r-md); }
.btn.primary { background: var(--accent); }
.btn.primary:hover { background: var(--accent-hover); }
/* Ghost = hairline, no fill. */
.btn.ghost { background: transparent; border: 1px solid var(--border-strong); }
.btn.ghost:hover { background: var(--raised); border-color: var(--border-strong); }

/* The Assistant is the one soft-accent surface, as in every reference screen. */
#ai-btn.btn.primary, .btn.primary#ai-btn {
  background: color-mix(in srgb, var(--accent) 30%, white);
  color: color-mix(in srgb, var(--accent) 85%, black);
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-normal);
}
#ai-btn.btn.primary:hover { background: color-mix(in srgb, var(--accent) 22%, white); }

/* ── Chips and tags: small rectangles, tinted, high-contrast text ───────*/
.pill, .hchip, .ur-course, .chip {
  border-radius: var(--r-sm);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
}

/* ── Progress: thin bars only, never bubbly ─────────────────────────────*/
.track { height: 4px; border-radius: 2px; }
.track .completed, .track .planned { border-radius: 2px; }

/* ── Inputs: dark field, hairline, accent border on focus ───────────────*/
input[type="text"], input[type="number"], input[type="time"],
input[type="date"], input[type="email"], input[type="password"],
textarea, select {
  background: var(--raised);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-family: var(--font);
}
input:focus-visible, textarea:focus-visible, select:focus-visible {
  border-color: var(--accent);
}
.field > label {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-2);
}

/* ── Modals float above the surface ─────────────────────────────────────*/
.modal {
  background: var(--overlay);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-overlay);
}
.modal-backdrop { backdrop-filter: blur(2px); }

/* ── Empty states: dashed container, not bare text ──────────────────────*/
.hcard-empty, .empty, .hempty {
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
  margin: var(--s3);
  padding: var(--s4);
}

/* ── Calendar grid: hour lines stronger than half-hours ─────────────────
   The real classes are .hline and .hline.half inside .timegrid — my first
   pass guessed .tg-hour/.tg-half, which the app never emits. */
.timegrid .hline { background: var(--border-strong); }
.timegrid .hline.half { background: var(--border); }

/* ── Corrections found by reading the markup, not guessing at it ─────────*/

/* Home's Review card is .hstat b / .hstat span — NOT the .stat .k above,
   which belongs to the Review VIEW. Both now read as one component. */
.hstat b {
  font-size: var(--fs-title); font-weight: var(--fw-bold);
  letter-spacing: var(--track-tight); font-variant-numeric: tabular-nums;
}
.hstat span {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-3);
}
.hstats .hstat {
  background: var(--raised); border: 1px solid var(--border);
  border-radius: var(--r-md);
}

/* The category dot becomes Google's vertical status marker. The element
   already carries the course colour as an inline background, so the ribbon
   costs no markup and no JS — only a change of shape. */
.hrow .hdot, .review-row .dot {
  width: 3px; height: 14px; border-radius: 2px;
}

/* Board chips: count in accent, label in caps — as in the reference. */
.hchip b, .hchip strong { color: var(--accent); font-weight: var(--fw-bold); }

/* Sidebar: the product gets a technical sub-label, and nav sits quieter
   until it is the active view. */
.nav-item, .nav a { font-weight: var(--fw-med); }
.nav-item.active, .nav a.active { font-weight: var(--fw-bold); }

/* ══════════════════════════════════════════════ PHASE 1 — TASKS / INBOX
   The mockup's dense table: one grid shared by the header and every row, so
   columns cannot drift apart. Hairline separators instead of gaps, actions
   revealed on hover, and the course colour carried as a 2px left ribbon. */
.task-list .task-head,
.task-list .task-row {
  display: grid;
  grid-template-columns: 22px 44px minmax(0, 1fr) 168px 132px 152px 92px;
  align-items: center;
  gap: var(--s3);
}

.task-head {
  padding: var(--s2) var(--s4);
  border-bottom: 1px solid var(--border-strong);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-3);
}

.task-list .task-row {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-left: 2px solid var(--rc, transparent);
  border-radius: 0;
  margin: 0;
  padding: var(--s3) var(--s4);
  transition: background var(--motion-quick) var(--ease);
}
.task-list .task-row:hover { background: var(--raised); }

/* Priority reads as a rank, not a decoration: semantic colour, tabular. */
.tk-prio {
  font-size: var(--fs-sm); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums; letter-spacing: var(--track-caps);
}
.tk-prio.p1 { color: var(--danger); }
.tk-prio.p2 { color: var(--warning); }
.tk-prio.p3 { color: var(--accent); }
.tk-prio.p4 { color: var(--text-3); }

.task-row .title { font-weight: var(--fw-med); }
.task-row .meta { font-size: var(--fs-xs); color: var(--text-3); }

/* Course chip: rectangular, tinted from the course's own colour. */
.tk-chip {
  display: inline-block; max-width: 100%;
  padding: 2px var(--s2); border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--rc) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--rc) 30%, transparent);
  color: var(--rc);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.tk-due { font-size: var(--fs-sm); color: var(--text-2); }
.tk-due .overdue { color: var(--danger); font-weight: var(--fw-med); }
.tk-none { color: var(--text-3); }

/* Actions stay out of the way until the row is under the cursor. Focus
   reveals them too, so the keyboard is not locked out of Delete. */
.tk-actions { opacity: 0; transition: opacity var(--motion-quick) var(--ease); }
.task-row:hover .tk-actions,
.task-row:focus-within .tk-actions { opacity: 1; }

.task-row.done .title { text-decoration: line-through; color: var(--text-3); }

@media (prefers-reduced-motion: reduce) {
  .task-list .task-row, .tk-actions { transition: none; }
}

/* ═══════════════════════════════════════════════════ PHASE 2 — HOME
   The mockup's hero, card grammar and row density, applied to the blocks
   that actually exist. No invented state: the hero is the REAL cover +
   identity strip, not a fabricated focus session.
   Per-card scoping uses .hcard[data-card], which the renderer already emits. */

/* ── Hero: cover and identity read as ONE module ────────────────────────
   The cover carries the same hairline as every other surface and loses its
   bottom radius; the identity strip closes the box underneath it. */
.home-cover {
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  /* views.css puts a 12px bottom margin here, which left a visible seam
     between the two halves of the hero. Measured, not guessed: the gap
     between cover.bottom and identity.top read 12px. */
  margin-bottom: 0;
}
.home-id {
  margin-top: 0;
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 0;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}
.id-avatar {
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: var(--fw-bold);
}
/* The persona chip is metadata, so it takes the uppercase label style. */
.id-chip {
  border-radius: var(--r-sm);
  background: var(--raised);
  border: 1px solid var(--border);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-2);
}
.id-chip:hover { border-color: var(--border-strong); color: var(--text); }

/* ── Greeting: the one display-scale element on the screen ──────────────*/
.home-head { margin: var(--s5) 0 var(--s4); }
.home-head h2 {
  font-size: var(--fs-display);
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-tight);
  line-height: 1.05;
}
.home-sub { font-size: var(--fs-lg); color: var(--text-2); }

/* ── Card grammar: tighter, hairline, no shadow on hover ────────────────*/
.hcard { border-radius: var(--r-md); }
.hcard-head { padding: var(--s3) var(--s4); }
.hcard-body { padding: var(--s2) 0; }

/* ── Rows ───────────────────────────────────────────────────────────────
   Today's events are raised blocks with a ribbon, as in the mockup. Every
   other list stays a flat separated row — the mockup draws them that way
   too, and it keeps the dense lists dense. */
.hcard[data-card="today"] .hrow {
  margin: var(--s1) var(--s3);
  padding: var(--s3);
  background: var(--raised);
  border-radius: var(--r-sm);
  border-left: 2px solid var(--rc, var(--accent));
}
.hcard[data-card="today"] .hrow .hdot { display: none; }
.hcard[data-card="today"] .hrow:hover {
  background: color-mix(in srgb, var(--text) 7%, var(--raised));
}

.hrow { padding: var(--s2) var(--s4); }
.hrow .ht { font-weight: var(--fw-med); }

/* ── This week: the day strip reads as data ─────────────────────────────*/
.hday .dw {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); color: var(--text-3);
}
.hday .dn { font-size: var(--fs-lg); font-weight: var(--fw-bold); }
.hday.today { background: var(--accent-soft); border-radius: var(--r-sm); }
.hday.today .dn { color: var(--accent); }

/* ── Board chips: rectangles with the count in accent ───────────────────*/
.hchip {
  background: var(--raised);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
.hchip:hover { border-color: var(--border-strong); }

/* ══════════════════════════════════════════════ PART A — THE FINISH LAYER
   What separates an engineered product from a styled one: uniform motion,
   designed emptiness, real focus, and a consistent elevation ladder.
   Nothing here invents data — it is finish, applied to what exists. */

/* ── 1 · MOTION ─────────────────────────────────────────────────────────
   ONE easing and three durations, everywhere. Inconsistent motion is the
   tell; uniform motion reads as engineered. Interactive surfaces share a
   single transition declaration so nothing can drift out of step. */
.btn, .hcard, .task-row, .hrow, .tmod, .swatch, .id-chip, .hchip,
.rail-cat, .tk-chip, .dd-btn, .nav-item, .mini-d, .tg-cell, .dl-check,
input, textarea, select, .segmented button, .pill-group .btn {
  transition:
    background-color var(--motion-quick) var(--ease),
    border-color     var(--motion-quick) var(--ease),
    color            var(--motion-quick) var(--ease),
    box-shadow       var(--motion-quick) var(--ease),
    opacity          var(--motion-quick) var(--ease),
    transform        var(--motion-instant) var(--ease);
}

/* Press: everything clickable gives the same small physical answer. */
.btn:active, .hchip:active, .id-chip:active, .swatch:active,
.dd-btn:active, .tk-chip:active { transform: scale(0.97); }

/* Panel mount — fade AND rise, never a pop. Applied to the surfaces a view
   swaps in, so navigating feels like settling rather than cutting. */
@keyframes tl-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.hcard, .tmod, .task-head, .task-row, .rail-cals, .review-row {
  animation: tl-rise var(--motion-settle) var(--ease) both;
}
/* A stagger so a list assembles instead of arriving all at once. Capped at
   six — beyond that the delay is longer than the animation and reads as lag. */
.task-row:nth-child(2)  { animation-delay: 20ms; }
.task-row:nth-child(3)  { animation-delay: 40ms; }
.task-row:nth-child(4)  { animation-delay: 60ms; }
.task-row:nth-child(5)  { animation-delay: 80ms; }
.task-row:nth-child(6)  { animation-delay: 100ms; }

/* Modal: overlay fades, content rises behind it. */
@keyframes tl-fade { from { opacity: 0; } to { opacity: 1; } }
.modal-backdrop { animation: tl-fade var(--motion-quick) var(--ease) both; }
.modal { animation: tl-rise var(--motion-settle) var(--ease) both; }
.dd-menu, .menu, .popover {
  animation: tl-rise var(--motion-quick) var(--ease) both;
}

/* ── 2 · EMPTY STATES ───────────────────────────────────────────────────
   Dashed container, a low-opacity mark, one line. The mark is drawn in CSS
   so no renderer had to change to gain an icon. */
.hcard-empty, .hempty, .empty,
.hcard-body > .hmeta:only-child:not([data-tasks-slot] *):not([data-deadlines-slot] *),
#cat-rows > .faint:only-child,
#ur-rail .faint:only-child {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--s2);
  margin: var(--s3); padding: var(--s5) var(--s4);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
  text-align: center;
  color: var(--text-2);
  font-size: var(--fs-sm);
}
.hcard-empty::before, .hempty::before, .empty::before,
.hcard-body > .hmeta:only-child:not([data-tasks-slot] *):not([data-deadlines-slot] *)::before,
#cat-rows > .faint:only-child::before,
#ur-rail .faint:only-child::before {
  content: "";
  width: 22px; height: 22px;
  border: 1.5px solid var(--text-3);
  border-radius: var(--r-sm);
  opacity: 0.35;
}
.hempty .hint, .hcard-empty .hmeta {
  color: var(--text-3); font-size: var(--fs-xs);
}

/* ── 3 · SPACING ────────────────────────────────────────────────────────
   Card internals on the 8px grid, so no two cards disagree. */
.hcard-body > *:first-child { margin-top: 0; }
.hcard-body > *:last-child  { margin-bottom: 0; }
.review h3 { margin: var(--s5) 0 var(--s3); }
.field { margin-bottom: var(--s4); }
.modal .actions { margin-top: var(--s5); gap: var(--s2); }

/* ── 4 · LOADING ────────────────────────────────────────────────────────
   A shimmer in --raised holding the right shape, never a spinner and never
   a layout jump. Applied to the async slots the app already renders. */
@keyframes tl-shimmer {
  from { background-position: -180% 0; }
  to   { background-position: 280% 0; }
}
/* MEASURED: the :empty selector never matched. Every async slot SHIPS a
   placeholder child ("…"), so it is never empty — the shimmer could not
   fire once. It targets that placeholder instead, which is a lone .hmeta
   while loading and is replaced by real rows or by .hcard-empty after.
   Loading and empty are therefore distinguishable, and neither can be
   mistaken for the other. */
.is-loading,
[data-tasks-slot] > .hmeta:only-child,
[data-deadlines-slot] > .hmeta:only-child,
[data-prog-slot] > .hmeta:only-child {
  display: block; min-height: 64px; margin: var(--s3);
  border-radius: var(--r-sm);
  background-image: linear-gradient(90deg,
    var(--raised) 0%,
    color-mix(in srgb, var(--text) 7%, var(--raised)) 50%,
    var(--raised) 100%);
  background-size: 220% 100%;
  animation: tl-shimmer 1.1s linear infinite;
}

/* ── 5 · DEPTH + FOCUS ──────────────────────────────────────────────────
   One ladder: bg → surface → raised → overlay, each with its hairline.
   And a real focus ring on everything reachable by keyboard — the system
   claims keyboard-first, so this is not optional. */
.hcard, .tmod, .rail-cals, .task-list { background: var(--surface); }
.modal, .dd-menu, .menu, .toast { background: var(--overlay); }

a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible,
.tg-cell:focus-visible, .mini-d:focus-visible, .swatch:focus-visible,
.hchip:focus-visible, .id-chip:focus-visible, .dd-btn:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--r-sm);
}

/* ── 6 · TYPOGRAPHIC RHYTHM ─────────────────────────────────────────────
   Body light, headings bold — the contrast IS the hierarchy. */
body, .hcard-body, .task-row .meta, .hrow, p { font-weight: var(--fw-body); }
h1, h2, h3, h4, .hcard-title, .rail-head, .modal h2, .tm-stat-n,
.hstat b, .stat .v { font-weight: var(--fw-bold); }
.btn, .nav-item, .task-row .title, .hrow .ht { font-weight: var(--fw-med); }
h1, h2, h3, h4 { letter-spacing: var(--track-normal); line-height: 1.25; }
.hrow, .task-row, .review-row { line-height: 1.45; }

@media (prefers-reduced-motion: reduce) {
  .hcard, .tmod, .task-head, .task-row, .rail-cals, .review-row,
  .modal, .modal-backdrop, .dd-menu, .menu, .popover { animation: none; }
  .is-loading,
  [data-tasks-slot] > .hmeta:only-child,
  [data-deadlines-slot] > .hmeta:only-child,
  [data-prog-slot] > .hmeta:only-child { animation: none; }
  .btn:active, .hchip:active, .id-chip:active, .swatch:active,
  .dd-btn:active, .tk-chip:active { transform: none; }
}

/* ═══════════════════════════════════════ PART B — REAL FURNITURE ONLY
   Search, pagination and the logo lockup. Every one runs on state the app
   already has. The five analytics panels from the mockups were OMITTED, not
   faked — see the commit message for why. */

/* ── Logo lockup ────────────────────────────────────────────────────────*/
.brand-name {
  display: flex; flex-direction: column; gap: 1px; line-height: 1.15;
  font-weight: var(--fw-bold);
}
.brand-sub {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-3);
}
.sb-collapsed .brand-sub { display: none; }

/* ── Search ─────────────────────────────────────────────────────────────*/
.tk-search {
  display: flex; align-items: center; gap: var(--s2);
  padding: 0 var(--s3);
  background: var(--raised);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  min-width: 220px;
  transition: border-color var(--motion-quick) var(--ease);
}
.tk-search:focus-within { border-color: var(--accent); }
.tk-search svg { color: var(--text-3); flex: none; }
.tk-search input {
  flex: 1; min-width: 0;
  background: none; border: 0; outline: none;
  padding: var(--s2) 0;
  color: var(--text); font-family: var(--font); font-size: var(--fs-sm);
}
.tk-search input::placeholder { color: var(--text-3); }
/* the native clear affordance is the wrong shape for this system */
.tk-search input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* ── Pager ──────────────────────────────────────────────────────────────*/
.tk-pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-top: 1px solid var(--border);
}
.tk-pager b { color: var(--text); font-weight: var(--fw-bold); }
.tk-pager-btns { display: flex; gap: var(--s2); }
.tk-pager .btn[disabled] { opacity: 0.4; pointer-events: none; }

/* ══════════════════════════════════════════════════════ PHASE 3 — BOARD
   High reuse, as audited: the cards already existed and already carried
   --chip-c. This is styling plus three fields that were in the payload all
   along (project, subtasks, depends_on) and simply were not shown. */

/* ── Column headers: uppercase label, tabular count ─────────────────────*/
.board .col > header {
  padding: var(--s2) var(--s2) var(--s3);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--s3);
}
.board .col-label {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-2);
}
.board .count {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  padding: 2px var(--s2); border-radius: var(--r-sm);
  background: var(--raised); border: 1px solid var(--border);
  color: var(--text-2);
}
/* The blocked column reads as a warning at the header, not the whole column. */
.board .col[data-status="blocked"] > header { border-bottom-color: var(--danger); }
.board .col[data-status="blocked"] .col-label,
.board .col[data-status="blocked"] .count { color: var(--danger); }

/* ── Card: hairline, ribbon, no shadow ──────────────────────────────────*/
.board .card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 2px solid var(--chip-c, var(--border-strong));
  border-radius: var(--r-md);
  padding: var(--s3);
  box-shadow: none;
  animation: tl-rise var(--motion-settle) var(--ease) both;
  transition: background-color var(--motion-quick) var(--ease),
              border-color var(--motion-quick) var(--ease),
              transform var(--motion-instant) var(--ease);
}
.board .card:hover { background: var(--raised); border-color: var(--border-strong); }
.board .card:active { cursor: grabbing; }
.board .card.dragging {
  opacity: 0.5; transform: scale(0.98);
  border-color: var(--accent);
}
.board .card .t {
  font-weight: var(--fw-bold); line-height: 1.3;
  margin-bottom: var(--s1);
}
.board .card .m {
  font-size: var(--fs-xs); color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

/* ── Course chip: tinted rectangle from the course's own colour ─────────*/
.bc-top { margin-bottom: var(--s2); }
.bc-chip {
  display: inline-block;
  padding: 2px var(--s2); border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--chip-c) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--chip-c) 32%, transparent);
  color: var(--chip-c);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
}

/* ── Progress: thin bar, real subtask counts ────────────────────────────*/
.bc-track {
  height: 4px; border-radius: 2px; margin-top: var(--s3);
  background: color-mix(in srgb, var(--text) 10%, transparent);
  overflow: hidden;
}
.bc-fill {
  height: 100%; border-radius: 2px; background: var(--accent);
  transition: width var(--motion-settle) var(--ease);
}
.bc-prog {
  display: flex; justify-content: space-between; gap: var(--s2);
  margin-top: var(--s2);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-3);
}

/* ── Blocked: danger hairline + the real dependency ─────────────────────*/
.board .card.is-blocked { border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.bc-blocked {
  margin-top: var(--s3); padding: var(--s2);
  border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border-radius: var(--r-sm);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--danger);
}

/* ── Empty column: the designed state, in every column ──────────────────*/
.board .cards:empty::before {
  content: "";
  display: block; margin: var(--s2);
  min-height: 76px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
}

@media (prefers-reduced-motion: reduce) {
  .board .card { animation: none; transition: none; }
  .bc-fill { transition: none; }
}

/* ═══════════════════════════════════════════════════ PHASE 4 — CALENDAR
   APPEARANCE ONLY. weekgrid.js positions every block with absolute pixel
   math (top / bottom-top from time), so nothing here may change a block's
   box. The rules below restyle INSIDE the box: colour, border, type,
   weight, motion. No padding, height, min-height or inset is set on
   .tg-block, because any of those would resize a box the math owns. */

/* ── Event block: ribbon + hairline, inside the existing box ────────────*/
.tg-block {
  border-left: 2px solid var(--chip-c, var(--accent));
  border-radius: var(--r-sm);
  transition: filter var(--motion-quick) var(--ease),
              border-color var(--motion-quick) var(--ease),
              box-shadow var(--motion-quick) var(--ease);
}
/* Hover brightens rather than growing — a transform or a padding change
   would move the block off its time. */
.tg-block:hover { filter: brightness(1.18); }
.tg-block:focus-visible { box-shadow: var(--ring); }

.tg-block .t {
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-normal);
}
.tg-block .when {
  font-size: var(--fs-eyebrow);
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}

/* The eyebrow. weekgrid.js already withholds it from .sm blocks, whose
   height is pinned too small to carry another line. line-height is set
   tight so it consumes as little of the fixed box as possible. */
.tg-eyebrow {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  line-height: 1.1;
  opacity: 0.72;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Grid lines: colour only, never position ────────────────────────────*/
.timegrid .hline { background: var(--border-strong); }
.timegrid .hline.half { background: var(--border); }

/* ── Current time: styled, NOT repositioned. `top` stays inline, owned by
   weekgrid.js; only colour and thickness are touched here. ─────────────*/
.timegrid .now-line { background: var(--danger); height: 1px; }
.timegrid .now-line::before {
  content: ""; position: absolute; left: -3px; top: -2.5px;
  width: 6px; height: 6px; border-radius: 999px; background: var(--danger);
}
.timegrid .now-dash { background: color-mix(in srgb, var(--danger) 45%, transparent); }

/* ── Day headers + gutter ───────────────────────────────────────────────*/
.timegrid .hour-label {
  font-size: var(--fs-eyebrow); font-variant-numeric: tabular-nums;
  color: var(--text-3); letter-spacing: var(--track-caps);
}

@media (prefers-reduced-motion: reduce) { .tg-block { transition: none; } }

/* ═══════════════════════════════════ FINISH PASS — PROJECTS (v2, grid)
   v1 styled the paint but kept full-width rows, which stretched a progress
   bar across ~1050px and read as three thin bands in an empty page. The
   reference is a CARD GRID, so that is what this is.

   Omitted deliberately, per "no fabricated data": course codes (no code
   field), credits (no column), "Fall Semester" (no term concept), and the
   Performance Velocity chart (no per-day completion history exists). */

.pj-strip {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s4); margin-bottom: var(--s5);
}
.pj-strip-stats { display: flex; gap: var(--s3); margin-left: auto; }
.pj-stat-tile {
  min-width: 132px; padding: var(--s3) var(--s4);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.pj-stat-tile .k {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-3);
}
.pj-stat-tile .v {
  margin-top: var(--s1);
  font-size: var(--fs-title); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums; letter-spacing: var(--track-tight);
}

/* ── The grid ───────────────────────────────────────────────────────────*/
.project-list {
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  /* align-items positions an item WITHIN its row; align-content positions
     the ROWS within the container. The container inherits a tall height, so
     with only align-items set the single row stretched to 804px around
     181px cards — ~620px of dead space that pushed the panels below the
     fold. align-content packs the rows to the top. */
  align-items: start;
  align-content: start;
  /* An inherited height:100% is left from when this was a scrolling LIST.
     As a grid it pinned the container to the full view height (805px around
     181px of cards) and pushed the panels below the fold. The grid should
     be as tall as its rows. */
  height: auto;
}

.project-row {
  display: flex; flex-direction: column; gap: var(--s3);
  /* MEASURED: an inherited align-items:center was still in force. On the
     row layout it centred vertically and was right; once this became a
     COLUMN flex the same property shrank every child to its content width
     and centred it — body measured 92px inside a 379px card, which is why
     the title floated and the track was a stub. */
  align-items: stretch;
  padding: var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 2px solid var(--pc, var(--border-strong));
  border-radius: var(--r-md);
  animation: tl-rise var(--motion-settle) var(--ease) both;
  transition: background-color var(--motion-quick) var(--ease),
              border-color var(--motion-quick) var(--ease);
}
.project-row:hover { background: var(--raised); border-color: var(--border-strong); }

.pj-head { display: flex; align-items: center; gap: var(--s2); }
/* The chip carries a REAL count. The mockup's "CS-402" is a course code and
   there is no such field, so a fabricated code is not shown in its place. */
.pj-chip {
  padding: 2px var(--s2); border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--pc) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--pc) 32%, transparent);
  color: var(--pc);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.pj-head .pj-actions { margin-left: auto; }

.project-row .body { display: flex; flex-direction: column; gap: var(--s2); }
.project-row .title {
  font-size: var(--fs-xl); font-weight: var(--fw-bold);
  letter-spacing: var(--track-normal); line-height: 1.2;
}
.pj-meta {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--s3);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-3); font-variant-numeric: tabular-nums;
}
.pj-pct { color: var(--text); font-size: var(--fs-sm); }
.pj-track {
  height: 4px; border-radius: 2px; overflow: hidden;
  background: color-mix(in srgb, var(--text) 10%, transparent);
}
.pj-fill {
  height: 100%; border-radius: 2px; background: var(--pc, var(--accent));
  transition: width var(--motion-settle) var(--ease);
}

/* ── Footer status: late, next, or clear — all from real deadlines ──────*/
.pj-foot {
  padding-top: var(--s3); border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.pj-stat { display: inline-flex; align-items: center; gap: var(--s2); }
.pj-stat::before {
  content: ""; width: 6px; height: 6px; border-radius: 999px; flex: none;
  background: currentColor;
}
.pj-stat.late { color: var(--danger); font-weight: var(--fw-med); }
.pj-stat.next { color: var(--text-2); }
.pj-stat.ok   { color: var(--success); }
.pj-stat.none { color: var(--text-3); }
.pj-when { color: var(--text-3); font-variant-numeric: tabular-nums; }

.pj-actions { opacity: 0; transition: opacity var(--motion-quick) var(--ease); }
.project-row:hover .pj-actions,
.project-row:focus-within .pj-actions { opacity: 1; }

/* ── "New project" as a card in the grid, per the reference ─────────────*/
.pj-new {
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: var(--s3);
  min-height: 168px; padding: var(--s4);
  background: none; border: 1px dashed var(--border-strong);
  border-radius: var(--r-md); cursor: pointer;
  color: var(--text-2); font-family: var(--font); font-size: var(--fs-sm);
  transition: border-color var(--motion-quick) var(--ease),
              color var(--motion-quick) var(--ease);
}
.pj-new:hover { border-color: var(--accent); color: var(--text); }
.pj-new-mark {
  display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 999px;
  background: var(--raised); color: var(--text-2);
}
.pj-new:hover .pj-new-mark { background: var(--accent-soft); color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .project-row, .pj-fill, .pj-actions, .pj-new { transition: none; animation: none; }
}

/* ═══════════════════════════════ CUSTOM CHECKBOX + PROJECT STATES
   .select-box was a NATIVE checkbox with only accent-color set, so it drew
   the OS control — a white rounded square on macOS that belongs to no
   design system. Rebuilt from the box up so it matches everything else. */
.select-box {
  -webkit-appearance: none; appearance: none;
  width: 16px; height: 16px; flex-shrink: 0;
  /* MEASURED: a global `input, select, textarea { padding: 6px 10px }` also
     matches a checkbox, and with border-box that padding forced the used
     width to 22px no matter what width said. Reclaim it explicitly. */
  padding: 0;
  margin: 0; cursor: pointer; position: relative;
  background: var(--raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  transition: background var(--motion-quick) var(--ease),
              border-color var(--motion-quick) var(--ease);
}
.select-box:hover { border-color: var(--accent); }
.select-box:checked { background: var(--accent); border-color: var(--accent); }
/* The tick is drawn, not a glyph — it scales cleanly and needs no font. */
.select-box:checked::after {
  content: ""; position: absolute;
  left: 4.5px; top: 1.5px; width: 4px; height: 8px;
  border: solid var(--on-accent);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.select-box:focus-visible { outline: none; box-shadow: var(--ring); }
.select-box:active { transform: scale(0.92); }

.select-all {
  display: inline-flex; align-items: center; gap: var(--s2);
  margin-bottom: var(--s3);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-3); cursor: pointer;
}
.select-all:hover { color: var(--text-2); }

/* ── Filter chips ───────────────────────────────────────────────────────*/
.pj-strip { align-items: center; }
.pj-filter { margin-right: auto; }
.pj-filter button {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-size: var(--fs-sm); font-weight: var(--fw-med);
}
.pj-filter .pj-n {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  padding: 1px 5px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--text) 8%, transparent);
  color: var(--text-3);
}
.pj-filter button.active .pj-n {
  background: var(--accent-soft); color: var(--accent);
}

/* ── Card states ────────────────────────────────────────────────────────
   A finished or archived course should recede, not shout. It stays fully
   legible — it is history, not noise. */
.project-row.st-completed .title { color: var(--text-2); }
.project-row.st-archived {
  border-style: dashed;
  border-left-style: solid;      /* the ribbon stays solid, it is identity */
  opacity: 0.72;
}
.project-row.st-archived:hover { opacity: 1; }
.project-row.st-archived .pj-chip { opacity: 0.8; }

@media (prefers-reduced-motion: reduce) {
  .select-box { transition: none; }
  .select-box:active { transform: none; }
}

/* ═══════════════════════ PROJECTS — VELOCITY + UPCOMING DEADLINES
   Both panels read REAL state and follow the active filter, so they
   describe the courses on screen rather than everything ever created.
   Velocity charts completed_at, a column that always existed and was
   simply never mapped out to the client. No sample data anywhere: with no
   completions the chart says so rather than drawing plausible bars. */

.pj-panels {
  display: grid; gap: var(--s3); margin-top: var(--s5);
  grid-template-columns: 1.6fr 1fr;
}
@media (max-width: 900px) { .pj-panels { grid-template-columns: 1fr; } }

.pj-panel {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); overflow: hidden;
  animation: tl-rise var(--motion-settle) var(--ease) both;
}
.pj-panel > header {
  display: flex; align-items: baseline; gap: var(--s3);
  padding: var(--s4) var(--s4) var(--s3);
  border-bottom: 1px solid var(--border);
}
.pj-panel > header .eyebrow { flex: none; }
.pj-panel > header h3 {
  margin: 0; font-size: var(--fs-xl); font-weight: var(--fw-bold);
  letter-spacing: var(--track-normal);
}
.pj-panel > header .hmeta { margin-left: auto; }
.pj-panel-body { padding: var(--s4); }

/* ── Velocity: hand-rolled bars, no chart library ───────────────────────*/
.pv-chart {
  display: flex; align-items: flex-end; gap: 4px;
  height: 132px;
}
.pv-col {
  flex: 1 1 0; height: 100%;
  display: flex; align-items: flex-end;
  border-radius: var(--r-sm);
}
.pv-bar {
  width: 100%; min-height: 3px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--accent) 32%, transparent);
  transition: background var(--motion-quick) var(--ease);
}
.pv-col:hover .pv-bar { background: color-mix(in srgb, var(--accent) 55%, transparent); }
/* Today is the one bar that gets the full accent — it is the reading that
   matters, and it needs no legend to say so. */
.pv-col.today .pv-bar { background: var(--accent); }
.pv-axis {
  display: flex; justify-content: space-between; margin-top: var(--s2);
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-3); font-variant-numeric: tabular-nums;
}

/* ── Deadlines: ribboned rows, tabular times ────────────────────────────*/
.pd-row {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--border);
}
.pd-row:last-child { border-bottom: 0; padding-bottom: 0; }
.pd-rib {
  width: 3px; height: 26px; border-radius: 2px; flex: none;
  background: var(--pc);
}
.pd-body { flex: 1; min-width: 0; }
.pd-t {
  font-weight: var(--fw-med);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pd-sub {
  font-size: var(--fs-eyebrow); font-weight: var(--fw-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--text-3);
}
.pd-when {
  text-align: right; font-size: var(--fs-sm); color: var(--text-2);
  font-variant-numeric: tabular-nums; flex: none;
}
.pd-when span { display: block; font-size: var(--fs-eyebrow); color: var(--text-3); }
.pd-when.late { color: var(--danger); font-weight: var(--fw-med); }

/* ── Honest emptiness in both panels ────────────────────────────────────*/
.pv-empty {
  display: flex; flex-direction: column; align-items: center; gap: var(--s2);
  padding: var(--s5) var(--s4); text-align: center;
  border: 1px dashed var(--border-strong); border-radius: var(--r-md);
  color: var(--text-2); font-size: var(--fs-sm);
}

@media (prefers-reduced-motion: reduce) {
  .pj-panel { animation: none; }
  .pv-bar { transition: none; }
}
