/* HES Course Planner — restyled to Harvard's design language.
   Tokens and component rules come from the design handoff
   ("HES Planner Redesign.dc.html"), which sources them from Harvard's
   HarvardSites design system and the Graphic Identity Standards Manual.

   Two rules from that spec are load-bearing and easy to undo by
   accident:
     - Crimson is an accent only -- buttons, links, rules, active-nav
       underline. Never body text.
     - Cards are 4px radius with a 1px hairline border and no shadow;
       the softer 8px+ rounding and drop shadows common elsewhere read
       as a different brand entirely.

   Dark mode follows the handoff's "recommended tokens": swap the
   surface/text tokens, keep crimson and the status colours, lightened
   for contrast on dark. Driven by prefers-color-scheme with a manual
   override class on <html> (see base.html). */

:root {
  color-scheme: light dark;

  --crimson: #A51C30;
  --crimson-hover: #7A1524;
  --ink: #1E1E1E;
  --paper: #FFFFFF;
  --cream: #F7F5F0;
  --line: #DDD8CE;
  --line-strong: #C9C2B4;
  /* Shared first-column width for the two Plan of Study tables, so
     "Courses" and "Title" line up. Fits "CSCI E-109A" (monospace) and
     "Spring 2026" with room to spare. */
  --plan-col1: 9rem;
  /* Shared width for both Plan of Study tables. Fits the planned-courses
     table's five columns at their natural sizes; the course-load table
     matches it rather than stopping short. */
  --plan-table-w: 60rem;
  /* ...and a shared second column. Both tables' first two columns are
     then the same width, so Load in one lines up with Term in the
     other -- the tables read as one grid rather than two. A percentage
     rather than a fixed size, so that holds at any window width. */
  --plan-col2: 45%;
  --muted: #6E6A61;

  --ok: #2E7D46;
  --ok-bg: #E9F5EC;
  --ok-line: #BFE0C9;
  --warn: #7A4E0A;
  --warn-bg: #FBF1DE;
  --warn-line: #E9CE96;
  --info: #2F5FA3;
  --info-bg: #EAF1FA;
  --info-line: #BBD3EC;

  --serif: "Libre Baskerville", Georgia, "Times New Roman", serif;
  --sans: "Source Sans 3", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --radius: 4px;
  --container: 1320px;
}

/* The design's full dark token set -- all 17, specified rather than
   derived, and checked: every foreground/background pair clears WCAG AA
   (crimson as link text is 5.17:1 on the page and 4.75:1 on a card).
   System preference only, per the decision to let the reader's own OS
   setting drive it with no in-app control to drift out of sync. */
@media (prefers-color-scheme: dark) {
  :root {
    --crimson: #D96072;
    --crimson-hover: #C24759;
    --ink: #EDEAE3;
    --paper: #151312;
    --cream: #1E1C1A;
    --line: #33302C;
    --line-strong: #46423C;
    --muted: #A8A29A;

    --ok: #4CAE6D;
    --ok-bg: #16241B;
    --ok-line: #2E4B36;
    --warn: #D9A441;
    --warn-bg: #2A2115;
    --warn-line: #4A3A20;
    --info: #6FA1DE;
    --info-bg: #17202B;
    --info-line: #2E4560;
  }
}


* { box-sizing: border-box; }

body {
  font-family: var(--sans);
  background: var(--paper);
  color: var(--ink);
  margin: 0;
  line-height: 1.5;
}

a { color: var(--crimson); text-decoration: none; }
a:hover { color: var(--crimson-hover); text-decoration: underline; }

/* ---- header ---------------------------------------------------------- */
/* White header with a thin crimson rule above it, replacing the solid
   crimson banner. Per the spec the seal + wordmark lockup carries the
   institutional identity and the serif product name sits under it. */

header.top {
  background: var(--paper);
  border-top: 4px solid var(--crimson);
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  padding: 0.85rem clamp(1rem, 4vw, 2rem);
  max-width: var(--container);
  margin: 0 auto;
}

header.top .brand {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex-shrink: 0;
  margin-right: 0.25rem;
}
header.top .brand:hover { text-decoration: none; }
/* Both marks carry their intrinsic width/height as attributes, and the
   ratio again here. Without a ratio to go on the browser reserves no
   width for an image it hasn't fetched yet, so the nav laid itself out
   around a zero-width seal and then jumped sideways when the SVG
   arrived. The attributes alone would do it in current browsers; the
   explicit aspect-ratio keeps it correct if the file is ever swapped
   for one with different dimensions and the attributes go stale. */
.brand-seal { height: 2.1rem; width: auto; aspect-ratio: 135 / 159; display: block; }
.brand-lockup { display: flex; flex-direction: column; align-items: flex-start; gap: 0.15rem; }
.brand-wordmark { height: 0.9rem; width: auto; aspect-ratio: 429 / 106; display: block; }
.brand-product {
  font-family: var(--serif);
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--ink);
  line-height: 1;
}

header.top nav { display: flex; flex: 1; justify-content: center; gap: 1.5rem; align-items: center; flex-wrap: wrap; }
header.top nav a {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  padding-bottom: 0.3rem;
  border-bottom: 2px solid transparent;
}
header.top nav a:hover { color: var(--ink); text-decoration: none; }
/* Active page marked with a crimson underline rather than a filled pill. */
header.top nav a.active { color: var(--ink); border-bottom-color: var(--crimson); }

/* nav takes the slack now, so this no longer pushes itself right. */
.logout-form { margin-left: 0; }

/* On a dark surface the marks sit on a light plate rather than being
   recoloured. Harvard's marks are locked to specific renderings, there is
   no official dark variant, and the brand guidelines don't permit
   inverting or recolouring them -- so the SVGs are left untouched and the
   background changes instead. Plate is #EDEAE3 (the dark palette's --ink,
   used in reverse) at the same 4px radius as every card. Applies anywhere
   the lockup sits on a dark or crimson field. */
@media (prefers-color-scheme: dark) {
  .brand, .login-lockup {
    background: #EDEAE3;
    border-radius: var(--radius);
    padding: 0.4rem 0.7rem;
  }
  /* The serif product name is inside the plate, so it takes the plate's
     ink rather than the page's. */
  .brand .brand-product, .login-lockup .brand-product { color: #1E1E1E; }
}

/* ---- layout ---------------------------------------------------------- */

main {
  max-width: var(--container);
  margin: 0 auto;
  padding: 2rem clamp(1rem, 4vw, 2rem) 3rem;
}

/* Headings are serif, left-aligned, never underlined -- per Harvard's
   clean-typography standard. */
h1, h2, h3 { font-family: var(--serif); line-height: 1.25; text-align: left; }
h1 { font-size: clamp(1.5rem, 5vw, 2.1rem); margin: 0 0 0.75rem; }
h2 { font-size: clamp(1.3rem, 4.5vw, 1.7rem); margin: 2.5rem 0 0.75rem; }
h2:first-child { margin-top: 0; }
/* H3 doubles as a section eyebrow: serif italic, muted. */
h3 { font-size: 1.05rem; font-weight: 400; font-style: italic; color: var(--muted); margin: 0 0 0.8rem; }

.muted { color: var(--muted); font-size: 0.9rem; }

/* ---- surfaces -------------------------------------------------------- */

.card {
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.1rem 1.3rem;
  margin-bottom: 1rem;
}

/* ---- tables ---------------------------------------------------------- */

table { border-collapse: collapse; width: 100%; font-size: 0.9rem; }
th, td { text-align: left; padding: 0.6rem; vertical-align: top; }
thead tr { border-bottom: 1px solid var(--line-strong); }
tbody tr { border-bottom: 1px solid var(--line); }
tbody tr:last-child { border-bottom: none; }
th {
  color: var(--muted);
  font-weight: 700;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* Wide tables scroll inside their own container rather than pushing the
   page sideways. */
.table-scroll { overflow-x: auto; }

code, .code { font-family: var(--mono); font-size: 0.9em; }
a.code { color: var(--crimson); font-weight: 600; }
a.code:hover { color: var(--crimson-hover); text-decoration: underline; }

/* ---- the three status forms ------------------------------------------
   Kept from the previous design because they solve a real clarity
   problem, and the handoff explicitly retains them. Three distinct
   visual forms for three distinct meanings:
     1. solid pill  -- facts about a section (instructor affiliation,
                       cross-listing, cancelled)
     2. dashed pill -- flags you set yourself (not interested, avoid in
                       summer)
     3. text only   -- plan status (completed / registered / in progress)
   The distinction must survive two of them happening to use the same
   colour, which is why form, not hue, carries the meaning. */

.pill {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 3px;
  font-size: 0.78rem;
  border: 1px solid var(--line);
  background: var(--cream);
  color: var(--muted);
  white-space: nowrap;
}
.pill.ok { background: var(--ok-bg); border-color: var(--ok-line); color: var(--ok); }
.pill.warn { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn); }
.pill.info { background: var(--info-bg); border-color: var(--info-line); color: var(--info); }
/* Form 2: your own flags. Dashed border is the whole differentiator, so
   it has to survive whatever colour the flag happens to use. */
.pill.flag { border-style: dashed; }

/* Form 3: plan status -- colour and weight on the text itself, never a
   badge, so it can sit beside a pill without reading as another one.
   No strikethrough: it was doing the same job as the colour and made a
   row of otherwise ordinary text look struck out and messy. */
.course-status-completed { color: var(--ok); font-weight: 600; }
.course-status-registered { color: var(--info); font-weight: 600; }
.course-status-in_progress { color: var(--warn); font-weight: 600; }
.course-status-taken-elsewhere { color: var(--muted); }

/* ---- alerts ---------------------------------------------------------- */

.alert {
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
  margin-bottom: 0.75rem;
  border: 1px solid var(--line);
  background: var(--cream);
  font-size: 0.9rem;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}
.alert.warn { background: var(--warn-bg); border-color: var(--warn-line); }
.alert.info { background: var(--info-bg); border-color: var(--info-line); }
/* Leading marker, coloured by severity -- carries the "something needs
   your attention" signal without an icon font or an image. */
.alert::before {
  content: "!";
  font-weight: 700;
  font-size: 1.1rem;
  line-height: 1;
  flex-shrink: 0;
  color: var(--muted);
}
.alert.warn::before { color: var(--warn); }
.alert.info::before { content: "i"; color: var(--info); }
.alert-detail { color: var(--muted); margin-top: 0.2rem; }
/* Several findings in one box, one line each -- the alternative was a
   stack of near-identical boxes, which is what auto-build used to do
   with its notes. */
.alert-list {
  margin: 0.35rem 0 0;
  padding-left: 1.1rem;
  color: var(--muted);
}
.alert-list li { margin-bottom: 0.3rem; }
.alert-list li:last-child { margin-bottom: 0; }
.alert strong + .alert-list { margin-top: 0.3rem; }

/* A course DCE has stopped listing. Deliberately recessive -- muted on
   the cream surface with a dashed hairline -- because it marks an
   absence rather than a live fact about an offering. */
.pill.retired {
  color: var(--muted);
  background: var(--cream);
  border: 1px solid var(--line-strong);
}

/* ---- rescrape banner ---------------------------------------------------
   Site-wide while a scrape runs. Warn palette rather than info: this is a
   temporary state that changes what the page is showing under you, not a
   neutral note. */

.scrape-banner {
  background: var(--warn-bg);
  border-top: 1px solid var(--warn-line);
  border-bottom: 1px solid var(--warn-line);
  color: var(--ink);
  font-size: 0.85rem;
}
.scrape-banner-inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0.6rem clamp(1rem, 4vw, 2rem);
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.scrape-banner-text { font-weight: 600; }
.scrape-banner-eta { color: var(--muted); }

.scrape-progress {
  flex: 1;
  min-width: 8rem;
  max-width: 20rem;
  height: 0.4rem;
  background: var(--warn-line);
  border-radius: 2px;
  overflow: hidden;
}
.scrape-progress-fill {
  width: 2%;
  height: 100%;
  background: var(--warn);
  transition: width 0.6s linear;
}

/* Error state. The palette has no danger tone -- ok/warn/info only --
   so this keeps the warn surface and takes crimson rules instead, which
   is crimson used as an accent rather than as text. */
.scrape-banner.error { border-top-color: var(--crimson); border-bottom-color: var(--crimson); }

.scrape-banner button {
  --btn-hover-bg: transparent;
  margin-left: auto;
  background: transparent;
  border: none;
  color: inherit;
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  padding: 0 0.3rem;
}

/* ---- forms and controls ----------------------------------------------- */

form.inline { display: inline; }

.form-row {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  align-items: end;
  margin-bottom: 1rem;
}
.form-row label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

input, select, textarea {
  font: inherit;
  font-size: 0.88rem;
  padding: 0.45rem 0.6rem;
  border-radius: 3px;
  border: 1px solid var(--line-strong);
  background: var(--paper);
  color: var(--ink);
}
/* One focus treatment for everything interactive, on :focus-visible only
   -- so it appears for keyboard and assistive tech but not on mouse
   click, which is the distinction that makes a visible outline tolerable
   to leave on site-wide. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--crimson);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Primary action is ink, not crimson: the spec reserves crimson for
   accents and the single sign-in call to action, so a page with a dozen
   buttons doesn't turn into a wall of red. */
button {
  --btn-hover-bg: #000;
  font: inherit;
  font-weight: 600;
  font-size: 0.85rem;
  padding: 0.5rem 0.9rem;
  border-radius: 3px;
  border: none;
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
}
/* Hover colour comes from a per-variant custom property rather than a
   bare `button:hover` rule. `button:hover` has higher specificity than a
   single class, so any variant that restyled `background` but forgot a
   matching :hover silently reverted to the primary button's near-black
   on hover -- which is what the consolidated filter control and the
   rescrape banner's dismiss both did. A variant now only has to declare
   its own --btn-hover-bg, and forgetting one is visible immediately
   rather than only on hover. */
button:hover:not(:disabled) { background: var(--btn-hover-bg); }
button.secondary {
  --btn-hover-bg: var(--cream);
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--line-strong);
}
button:disabled { opacity: 0.5; cursor: default; }


/* Consolidated filter popover: one button for Format + Instructor +
   Listing, its panel dropping below without displacing the page. Replaces
   three separate dropdowns that between them cost a whole row. */
.filter-popover { position: relative; }
.filter-popover-button {
  --btn-hover-bg: var(--cream);
  width: 100%;
  text-align: left;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  padding: 0.45rem 0.6rem;
  border-radius: 3px;
  border: 1px solid var(--line-strong);
  background: var(--paper);
  color: var(--muted);
}
.filter-popover-button::after { content: " \25BE"; }
.filter-popover.open .filter-popover-button { border-color: var(--crimson); color: var(--ink); }
.filter-popover.open .filter-popover-button::after { content: " \25B4"; }

.filter-popover-panel {
  position: absolute;
  top: calc(100% + 0.3rem);
  left: 0;
  z-index: 30;
  min-width: 16rem;
  max-width: 22rem;
  padding: 0.9rem 1rem;
  background: var(--paper);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  /* The only shadow in the sheet: a layer floating over content has to
     read as detached. Surfaces themselves stay flat. */
  box-shadow: 0 6px 16px rgba(30, 30, 30, 0.14);
}
.filter-group { margin-bottom: 0.9rem; }
.filter-group-title {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.35rem;
}
.filter-group label {
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
  font-size: 0.85rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--ink);
  cursor: pointer;
  padding: 0.1rem 0;
}
.filter-group input[type="checkbox"] { width: auto; margin: 0.2rem 0 0; flex-shrink: 0; }
.filter-popover-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  padding-top: 0.6rem;
  border-top: 1px solid var(--line);
}

/* ---- filter panel (Courses) ------------------------------------------- */

.filters {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  margin-bottom: 1.5rem;
  padding: 1.25rem 1.4rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cream);
}
.filters-search input {
  width: 100%;
  font-size: 0.95rem;
  padding: 0.6rem 0.8rem;
}
.filters-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.8rem 1.2rem;
  align-items: stretch;
}
.filters-grid label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.filters-toggles {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
}
.filters-toggles label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.85rem;
  color: var(--ink);
  cursor: pointer;
}
.filters-toggles input[type="checkbox"] { width: auto; margin: 0; }
.filters-actions { display: flex; gap: 0.6rem; }

/* ---- requirement categories (Dashboard) -------------------------------- */

/* Block label above a group of categories, e.g. "Admissions Courses" --
   the one place crimson appears as text, as a small-caps eyebrow rather
   than prose. */
.block-title {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crimson);
  margin: 2rem 0 0.9rem;
}

.category {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.1rem 1.3rem;
  margin-bottom: 0.9rem;
  background: var(--paper);
}
.category-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-bottom: 0.85rem;
}
.category-head strong { font-size: 1rem; }
.category-count {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
  font-weight: 600;
}
.category-count::before {
  content: "";
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: currentColor;
  display: inline-block;
}
.category-count.ok { color: var(--ok); }
.category-count.warn { color: var(--warn); }

.member-row {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  flex-wrap: wrap;
  padding: 0.15rem 0;
  font-size: 0.9rem;
}

/* "+N more courses qualify" -- a long category collapses to its first
   few members, because a 13-member list was where most of the visual
   clutter on this page came from. */
.more-toggle {
  --btn-hover-bg: transparent;
  align-self: flex-start;
  background: transparent;
  border: none;
  color: var(--crimson);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  padding: 0.2rem 0;
}
.more-toggle:hover { background: transparent; color: var(--crimson-hover); text-decoration: underline; }
details.category-more > summary { list-style: none; }
details.category-more > summary::-webkit-details-marker { display: none; }
details.category-more > summary::before { content: "+ " attr(data-count) " more courses qualify"; }
details.category-more[open] > summary::before { content: "Show fewer"; }

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

nav.subnav { margin-bottom: 1.25rem; }
nav.subnav a { margin-right: 1rem; font-size: 0.9rem; font-weight: 600; }


/* ---- sign in ---------------------------------------------------------- */
/* The one place a crimson button is correct: a single call to action on
   a page with nothing else on it. */

.login-wrap { max-width: 22rem; margin: 4rem auto; }
.login-lockup { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 2.5rem; }
.login-lockup .brand-seal { height: 2.6rem; }
.login-lockup .brand-wordmark { height: 1.15rem; }
.login-lockup .brand-product { font-size: 1.15rem; }

.login-form { display: flex; flex-direction: column; gap: 0; }
.login-form label {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 0.4rem;
}
.login-form input {
  width: 100%;
  font-size: 0.95rem;
  padding: 0.6rem 0.75rem;
  margin-bottom: 1.25rem;
}
.login-form input:last-of-type { margin-bottom: 1.75rem; }
.login-form button {
  --btn-hover-bg: var(--crimson-hover);
  width: 100%;
  background: var(--crimson);
  color: var(--paper);
  padding: 0.7rem;
  font-size: 0.95rem;
  font-weight: 700;
}


/* ---- footer ----------------------------------------------------------- */

footer.site {
  border-top: 1px solid var(--line);
  margin-top: 3rem;
}
footer.site .inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: 1.5rem clamp(1rem, 4vw, 2rem);
  font-size: 0.82rem;
  color: var(--muted);
}

/* One line per offering: term and format on the left, the action for
   that specific term right-aligned. Which term you're adding is then
   unambiguous without reading a label. */
/* ---- course list ------------------------------------------------------
   Not a table: one flex row per course, split into the course itself and
   its offerings. A table forced every course into the same column widths
   even though the description and the term list want quite different
   space, and it couldn't wrap sensibly on a narrow screen. */
.course-list { display: flex; flex-direction: column; }
.course-row {
  display: flex;
  gap: 1rem;
  padding: 0.9rem 0;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line-strong);
}
.course-row:last-child { border-bottom: none; }
.course-main { flex: 1; min-width: 0; }
/* A completed course takes the ok colour on its code, and colour plus a
   strikethrough on its title -- the code stays unstruck because it's how
   you still refer to the course. */
.course-codes.is-completed { color: var(--ok); }
.course-codes {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 0.95rem;
  margin-bottom: 0.4rem;
}
.course-title { font-weight: 600; margin-bottom: 0.3rem; }
.course-desc { font-size: 0.88rem; color: var(--muted); margin-bottom: 0.5rem; }
.course-meta { font-size: 0.82rem; color: var(--muted); margin-bottom: 0.5rem; }
.course-meta strong { color: var(--ink); font-weight: 600; }

/* Offerings: a comfortable column beside the course, and the first thing
   to go full-width as the row narrows -- three columns stop working
   sooner than the nav does, hence 700px rather than the 640px breakpoint. */
.course-terms {
  flex: 0 1 17.5rem;
  font-size: 0.85rem;
  color: var(--muted);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
@media (max-width: 700px) { .course-terms { flex: 1 1 100%; } }
.offering-when { min-width: 0; }
.offering-when strong { color: var(--ink); }
/* Enrolment state, sized down from a normal pill so it reads as a
   qualifier on the term rather than a badge in its own right. */
.status-pill {
  display: inline-block;
  font-size: 0.72rem;
  border-radius: 3px;
  white-space: nowrap;
  padding: 0.03rem 0.35rem;
  margin-left: 0.15rem;
  color: var(--muted);
  background: var(--cream);
  border: 1px solid var(--line-strong);
}
.status-pill.ok { color: var(--ok); background: var(--ok-bg); border-color: var(--ok-line); }
.status-pill.warn { color: var(--warn); background: var(--warn-bg); border-color: var(--warn-line); }

/* Plan state for one specific term, in the slot the add button would
   otherwise take. Colour and weight only -- never a pill, which stays
   reserved for facts about the section. */
.plan-state { font-weight: 600; font-size: 0.82rem; white-space: nowrap; }
.plan-state.completed { color: var(--ok); }
.plan-state.registered { color: var(--info); }
.plan-state.in_progress { color: var(--warn); }
.plan-state.planned { color: var(--ink); }

.offering-instructor { font-size: 0.8rem; margin-top: 0.15rem; }
/* Affiliation qualifies the instructor's name, so it's coloured text
   rather than a second pill competing with the status one above it. */
.offering-instructor .affil { font-size: 0.72rem; }
.offering-instructor .affil.harvard { color: var(--info); }
.offering-instructor .affil.mixed { color: var(--warn); }

.offering-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}
/* Crimson outline rather than the ink-filled primary: there can be three
   of these in a single row, and they're an invitation, not the page's
   main action. */
.btn-add {
  --btn-hover-bg: var(--crimson);
  /* Never wraps: it's the row's action, and a broken "+ Add / Fa26"
     reads as two controls. The term text beside it gives way instead. */
  flex-shrink: 0;
  white-space: nowrap;
  background: transparent;
  color: var(--crimson);
  border: 1px solid var(--crimson);
  border-radius: 3px;
  padding: 0.2rem 0.55rem;
  font-weight: 600;
  font-size: 0.76rem;
}
.btn-add:hover { background: var(--crimson); color: var(--paper); }

/* ---- empty states ------------------------------------------------------
   Same card shell as a populated screen: one sentence, one action where
   there is one, no illustration. */
.empty {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cream);
  padding: 2.5rem 1.5rem;
  text-align: center;
  color: var(--muted);
}
.empty strong { display: block; color: var(--ink); font-size: 1rem; margin-bottom: 0.3rem; }
.empty .empty-action { margin-top: 1rem; }

/* ---- narrow screens ----------------------------------------------------
   Below 640px the nav becomes a drawer and the Courses table stops being
   a table. Both Plan of Study tables keep their horizontal scroll, which
   they already had via .table-scroll. */

.nav-toggle, .nav-burger { display: none; }

@media (max-width: 640px) {
  header.top { gap: 0.75rem; }
  /* Seal and wordmark stay put; the links move into a slide-out panel. */
  .nav-burger {
    display: block;
    margin-left: auto;
    cursor: pointer;
    font-size: 1.4rem;
    line-height: 1;
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--line-strong);
    border-radius: 3px;
    color: var(--ink);
    user-select: none;
  }
  header.top nav { display: none; width: 100%; }
  .nav-toggle:checked ~ nav {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    border-top: 1px solid var(--line);
    margin-top: 0.6rem;
    padding-top: 0.4rem;
  }
  .nav-toggle:checked ~ nav a {
    padding: 0.6rem 0.2rem;
    border-bottom: none;
    border-left: 2px solid transparent;
    padding-left: 0.6rem;
  }
  .nav-toggle:checked ~ nav a.active { border-left-color: var(--crimson); }
  .logout-form { margin-left: 0; }

  /* Courses: the two columns stack, and the offerings column has already
     gone full-width at 700px (see .course-terms). */
  .course-row { gap: 0.5rem; }

}

/* Auto-build draft header: title and the two whole-draft actions on one
   line, wrapping to two on narrow screens. */
.draft-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 2.5rem 0 0.5rem;
}

/* ---- course flags -----------------------------------------------------
   Each flag is a button, not a label: unset it reads as an invitation
   (muted on paper, dashed hairline), set it fills with its own palette
   and grows an adjacent Undo. The dashed border therefore marks
   "actionable", and the fill marks "on".

   Note this diverges from the style guide's status-system line, which
   describes your own flags as *always* dashed to keep them distinct from
   solid section-fact pills. The Courses mockup draws set flags solid, and
   that's what's built here -- flagged in the handoff notes, since a set
   "avoid in summer" now looks much like a "possibly cross-listed" fact
   sitting in the same row. */
.flag-btn {
  --btn-hover-bg: var(--cream);
  font-size: 0.78rem;
  font-weight: 400;
  white-space: nowrap;
  padding: 0.1rem 0.5rem;
  border-radius: 3px;
  background: var(--paper);
  color: var(--muted);
  border: 1px dashed var(--line-strong);
}
.flag-btn:hover { color: var(--ink); }
/* A set flag keeps its own fill on hover -- it's already "on", so
   darkening it would suggest a state it doesn't have. The border stays
   dashed in both states: per the status system, dashed is what
   distinguishes a flag you set from a fact about the course, and info is
   reserved for facts so no flag may use it. */
.flag-btn.on.ok { --btn-hover-bg: var(--ok-bg); color: var(--ok); background: var(--ok-bg); border-color: var(--ok-line); }
.flag-btn.on.warn { --btn-hover-bg: var(--warn-bg); color: var(--warn); background: var(--warn-bg); border-color: var(--warn-line); }
.flag-btn.on:hover { color: inherit; }

.flag-row { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; margin-top: 0.4rem; }

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

/* Title on the left, state and its action inline on the right. Baseline
   alignment so the timestamp sits on the heading's baseline rather than
   floating above it. */
.page-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1.75rem;
}
.page-head h2 { margin: 0; }
.page-head-actions {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  font-size: 0.88rem;
  color: var(--muted);
}

/* A single-line settings strip: label, controls and Save all inline on
   the cream surface. */
.settings-bar {
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
  margin-bottom: 2rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.settings-bar select { white-space: nowrap; }

/* The warning box sits directly in the flow, with no heading above it. */
.alert { margin-bottom: 2rem; }

/* Category head: name (and its editor) left, progress and provenance
   right. */
.category-name { font-size: 1rem; }
.category-meta { display: flex; align-items: center; gap: 0.6rem; }
.category-count.info { color: var(--info); }



/* Two action cards side by side, collapsing to one column when there
   isn't room -- the same auto-fit grid the design uses. */
.action-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1.25rem;
  margin-bottom: 1.75rem;
}
.action-grid .card { margin-bottom: 0; }
.card-label {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.6rem;
}
.card-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.add-course { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.add-course input[type="text"] { flex: 1; min-width: 9rem; }

/* ---- max courses per term ------------------------------------------
   One saved number for every term (see plan_load_target), with a
   per-term override available from each row of the course-load table
   below. Deliberately manual rather than a season rule: see
   planning.term_load_target. */
.load-target-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.load-target-row .muted { font-size: 0.82rem; }

/* The three controls read as one field: the border belongs to the
   wrapper, the parts inside only carry the dividers. */
.stepper {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  overflow: hidden;
}
.stepper input[type="number"] {
  width: 2.6rem;
  text-align: center;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  padding: 0.45rem 0;
  border: none;
  border-radius: 0;
  background: var(--paper);
  color: var(--ink);
  -moz-appearance: textfield;
}
.stepper input[type="number"]::-webkit-outer-spin-button,
.stepper input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.stepper-step {
  --btn-hover-bg: var(--cream);
  width: 1.9rem;
  background: var(--paper);
  color: var(--ink);
  border: none;
  border-radius: 0;
  font: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  padding: 0;
  cursor: pointer;
}
.stepper-step[data-step="-1"] { border-right: 1px solid var(--line-strong); }
.stepper-step[data-step="1"] { border-left: 1px solid var(--line-strong); }

/* ---- course load per term -------------------------------------------
   Term and Load are sized to their content and Courses takes the rest,
   with Load wide enough for its fullest state -- otherwise setting an
   override adds a pill and a reset link, and every column shifts to
   make room for them. */
/* Both Plan of Study tables are the same width -- two blocks of
   different lengths stacked on one page read as a mistake.
   --plan-table-w is what they share.

   The trick to sharing a width without reopening the gap this had
   before: every column except the last is sized to its content, so the
   slack pools in the *last* column instead of a middle one. Its content
   is left-aligned, so that space lands at the right edge of the table
   where nobody reads it as a hole. */
.term-load-table,
.plan-table { width: 100%; max-width: var(--plan-table-w); }
.term-load-table th:first-child,
.term-load-table td:first-child { width: var(--plan-col1); white-space: nowrap; }
/* Courses takes a share of the table rather than shrinking to its
   codes: at width:1% all three columns bunched into the left third and
   the rest of the table was empty. A percentage scales with the table,
   so the spacing holds at any window width. */
.term-load-table th:nth-child(2),
.term-load-table td:nth-child(2) { width: var(--plan-col2); padding-right: 1.5rem; }
/* Load keeps a floor wide enough for its fullest state (count, override
   pill, reset) so setting an override doesn't move anything. */
.term-load-table th:last-child,
.term-load-table td:last-child { min-width: 12rem; }

/* One line: the count, the override control, and reset. Flex on this
   wrapper rather than on the <td> -- a table cell set to display:flex
   is no longer a table cell, and the column model comes apart. */
.term-load-line {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  flex-wrap: nowrap;
  white-space: nowrap;
}
/* Colour and weight rather than a pill: this is the count itself, not a
   label attached to it. At your target is what you're aiming for; both
   under and over are worth another look. */
.load-count { font-weight: 600; }
.load-count.ok { color: var(--ok); }
.load-count.warn { color: var(--warn); }

/* <details> rather than a JS toggle so the editor works without script
   and closes itself on the page swap after a save. */
/* The editor is positioned out of flow, so opening it doesn't change
   the row's height or push anything sideways. */
.override { position: relative; display: inline-block; }
.override > summary {
  display: inline;
  list-style: none;
  cursor: pointer;
  font-size: 0.78rem;
  color: var(--muted);
  text-decoration: underline;
}
.override > summary::-webkit-details-marker { display: none; }
.override[open] > summary { text-decoration: none; }
/* Once set, the pill *is* the summary, so an override can be edited
   without being reset first. */
.override-pill {
  font-size: 0.72rem;
  color: var(--muted);
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  white-space: nowrap;
  padding: 0.03rem 0.35rem;
  text-decoration: none;
}
.override-form {
  position: absolute;
  top: calc(100% + 0.3rem);
  left: 0;
  z-index: 5;
  display: flex;
  gap: 0.4rem;
  align-items: center;
  padding: 0.45rem;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  background: var(--paper);
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.12);
}
.override-form input[type="number"] { width: 3.5rem; }

/* A button that has to read as a plain text link -- "reset" beside an
   override pill. */
.link-button {
  --btn-hover-bg: transparent;
  background: transparent;
  color: var(--muted);
  border: none;
  border-radius: 0;
  padding: 0;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 400;
  text-decoration: underline;
  cursor: pointer;
}
.link-button:hover:not(:disabled) { color: var(--ink); }

/* A link that has to sit beside a button and match it. */
.button-link {
  display: inline-block;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  white-space: nowrap;
  padding: 0.45rem 0.9rem;
  font-weight: 600;
  font-size: 0.85rem;
  text-decoration: none;
}
.button-link:hover { background: var(--cream); color: var(--ink); text-decoration: none; }

/* Both plan tables keep a comfortable minimum and scroll inside their
   own container rather than widening the page. */
.table-scroll { overflow-x: auto; margin-bottom: 2rem; }
.table-scroll table { min-width: 28rem; }

/* Change-report index: a plain list of dates, given a little air. */
.report-list { list-style: none; margin: 0; padding: 0; }
.report-list li { border-bottom: 1px solid var(--line); padding: 0.6rem 0; }
.report-list li:last-child { border-bottom: none; }

/* A change report is pre-formatted Markdown -- kept monospaced and
   wrapped rather than reflowed, since its alignment is meaningful. */
pre.report {
  white-space: pre-wrap;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
  font-size: 0.88rem;
  font-family: var(--mono);
  overflow-x: auto;
}

/* ---- plan of study table ----------------------------------------------
   Reads as the design's plain-text table -- code, title, term, status,
   remove -- but term and status are still the controls that change them,
   since this is the only place a plan item can be edited. They sit
   borderless until hovered or focused, so the row is calm at rest and
   obviously editable once you reach for it. */
.plan-table td { vertical-align: top; }
/* Code here and Term in the course-load table above are both the first
   column, and both hold a short fixed-shape string -- so they share a
   width, and the two tables' second columns start at the same place
   rather than at whatever each table's own content happened to need. */
.plan-table th:first-child,
.plan-table td:first-child { width: var(--plan-col1); white-space: nowrap; }
/* Same share as Courses above it (see --plan-col2). The long warning
   sentence under a title wraps within it rather than setting the
   column's width. */
.plan-table th:nth-child(2),
.plan-table td:nth-child(2) { width: var(--plan-col2); padding-right: 1.5rem; }
.plan-code { font-weight: 600; }

.plan-edit select {
  border: 1px solid transparent;
  background: transparent;
  padding: 0.1rem 0.25rem;
  font: inherit;
  font-size: 0.9rem;
  color: var(--muted);
  cursor: pointer;
}
.plan-edit select:hover { border-color: var(--line-strong); background: var(--paper); }
.plan-edit select:focus { border-color: var(--crimson); background: var(--paper); }
/* Status keeps its plan colour and weight, as everywhere else. */
.plan-edit select.plan-state { color: var(--ink); }
.plan-edit select.plan-state.completed { color: var(--ok); }
.plan-edit select.plan-state.registered { color: var(--info); }
.plan-edit select.plan-state.in_progress { color: var(--warn); }

/* Notes live under the title now rather than in their own column. */
.plan-notes {
  display: block;
  width: 100%;
  max-width: 22rem;
  margin-top: 0.25rem;
  border: 1px solid transparent;
  background: transparent;
  padding: 0.1rem 0.25rem;
  font-size: 0.82rem;
  color: var(--muted);
}
.plan-notes:hover { border-color: var(--line); }
.plan-notes:focus { border-color: var(--crimson); background: var(--paper); }

/* Per-row warnings: the Dashboard groups these into one box, so here
   they only need to say which row is affected -- text, not a pill. */
.plan-warning { font-size: 0.8rem; margin-top: 0.25rem; max-width: 26rem; }
.plan-warning.warn { color: var(--warn); }
.plan-warning.info { color: var(--info); }

.plan-remove { text-align: right; }
.plan-remove button { font-size: 0.8rem; padding: 0.3rem 0.7rem; color: var(--muted); }

/* Between roughly a tablet and a small laptop the planned-courses
   table's five columns need more than 45% leaves them, so it starts
   shrinking the first column and the two tables stop lining up. Giving
   the shared column a smaller share keeps them in step down to the
   point where both become cards anyway. */
@media (max-width: 1000px) {
  :root { --plan-col2: 34%; }
}

/* ---- narrow screens, part two -----------------------------------------
   Deliberately at the end of the file rather than in the 640px block
   further up: these override rules for .member-row and the two Plan of
   Study tables that are *defined below* that block, and at equal
   specificity the later rule wins. Put these first and they lose
   silently -- which is exactly what happened to the load column's
   min-width on the first attempt. */
@media (max-width: 640px) {

  /* ---- Dashboard: category members stack ---------------------------
     Code, title and pills share a line on a wide screen. At this width
     they wrap at arbitrary points -- one member breaking after its code
     and the next not -- so the list stops reading as rows at all. Each
     part gets its own line instead, in the order you'd read them. */
  .member-row {
    display: block;
    padding: 0.5rem 0;
    border-top: 1px solid var(--line);
  }
  .member-row:first-of-type { border-top: none; }
  .member-row > a.code { display: block; margin-bottom: 0.1rem; }
  .member-row > span:not(.pill) { display: block; }
  /* Pills stay a row of their own, wrapping among themselves. */
  .member-row > .pill { margin: 0.3rem 0.3rem 0 0; }

  /* ---- Plan of Study: both tables become cards ---------------------
     Five columns don't survive 390px -- the planned-courses table was
     562px wide in a 390px viewport, so Status and Remove sat off-screen
     with no indication they were there. Each row becomes a card: the
     identity and its headline number on the first line, the substance
     below it, and any action on a footer line.

     Done by re-ordering the existing cells rather than emitting
     different markup, so there's still one template and one set of
     forms to keep working. */
  .table-scroll { overflow-x: visible; }
  .table-scroll table,
  .table-scroll table.term-load-table,
  .table-scroll table.plan-table { min-width: 0; }
  .term-load-table thead,
  .plan-table thead { display: none; }
  .term-load-table,
  .term-load-table tbody,
  .plan-table,
  .plan-table tbody { display: block; width: auto; max-width: none; }
  .term-load-table tr,
  .plan-table tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.8rem 1rem;
    margin-bottom: 0.75rem;
  }
  .term-load-table td,
  .plan-table td { display: block; width: auto; max-width: none; padding: 0; }

  /* Term left, load right, courses on the line below. */
  .term-load-table td:first-child { order: 1; font-weight: 600; }
  .term-load-table td:last-child { order: 2; margin-left: auto; min-width: 0; }
  .term-load-table td:nth-child(2) {
    order: 3;
    flex: 1 1 100%;
    margin-top: 0.5rem;
    padding-right: 0;
    white-space: normal;
  }

  /* Code left, term right; title and note beneath; status and Remove on
     a footer line under a rule. */
  .plan-table td:first-child { order: 1; }
  .plan-table td:nth-child(3) { order: 2; margin-left: auto; }
  .plan-table td:nth-child(2) { order: 3; flex: 1 1 100%; margin-top: 0.4rem; padding-right: 0; }
  .plan-table td:nth-child(4),
  .plan-table td:last-child {
    order: 4;
    margin-top: 0.7rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--line);
  }
  /* Status flexes so the two borders meet and read as one rule. */
  .plan-table td:nth-child(4) { flex: 1 1 auto; }
  .plan-table td:last-child { flex: 0 0 auto; }
  .plan-notes { max-width: none; }
  /* The override editor is positioned against its own row; at this
     width let it use the card's full breadth. */
  .override-form { left: auto; right: 0; }

  /* ---- Courses: the offerings become the card's footer -------------
     The two columns have already stacked by here (see .course-terms at
     700px). Stacked, though, the offerings run straight on from the
     description with nothing between them -- so they get the rule the
     design draws above them. */
  .course-terms {
    border-top: 1px solid var(--line);
    padding-top: 0.6rem;
    margin-top: 0.2rem;
  }
}
