/* Master Géosciences — ENS-PSL
   Visual register: a geological survey sheet. Cool paper, flat colour units,
   hairline rules, a depth-scale time axis.

   Pillar colours are derived, not listed: each pillar owns a hue, and every
   surface is built from that hue at a fixed lightness and chroma, so the five
   read as one family and stay balanced in both themes. The hues name the
   subject — slate for methods, marine blue for ocean and atmosphere, teal for
   the interfaces between them, amber-brown for solid earth, plum for society —
   and deliberately avoid the two hues this interface reserves for meaning,
   --warn and --accent. They no longer follow the department spreadsheet, which
   put methods on red and solid earth on green: those collided with the warning
   and success colours, so a clashing course and a compulsory one looked alike. */

/* Fonts are self-hosted, not fetched from Google: the department server is the
   only host a student's browser contacts, and dist/index.html works offline.
   Open Sans and Roboto Slab are the families www.geosciences.ens.fr uses, so
   the site reads as part of the department. Both files are variable fonts —
   one file covers every weight — subset to latin (SIL OFL 1.1, see
   fonts/README.txt). Replacing a font means renaming its file: the URL here is
   not content-hashed, so a same-name replacement would sit in caches. */
@font-face {
  font-family: 'Open Sans';
  src: url(fonts/open-sans-latin.woff2) format('woff2');
  font-style: normal;
  font-weight: 300 800;
  font-stretch: 100%;
  font-display: swap;
}
@font-face {
  font-family: 'Roboto Slab';
  src: url(fonts/roboto-slab-latin.woff2) format('woff2');
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
}
:root {
  /* replaced with the measured height by syncBarHeight() in app.js */
  --bar-h: 3.2rem;
  /* paper and ink */
  --ground:      #eff1f3;
  --ground-deep: #e3e7ea;
  --paper:       #ffffff;
  --ink:         #23272e;
  --ink-soft:    #4a515b;
  --stone:       #626b77;
  --rule:        #ccd3d9;
  --rule-soft:   #e2e7eb;
  --accent:      #1f6b4b;
  --accent-ink:  #17563c;
  --warn:        #a8442c;

  /* Pillar hues, chosen to say what the pillar is and to stay clear of the two
     hues the interface reserves for meaning: --warn at ~35 (clash, over) and
     --accent at ~160 (done). Methods sat on top of warn and Solid Earth on top
     of accent, so a clashing tile and a Methods tile were the same red, and a
     finished check and a Solid Earth tile the same green. */
  --h-methods:    250;   /* slate — computational, and see --chroma below */
  --h-ocean-atm:  235;   /* marine blue — ocean and atmosphere */
  --h-interfaces: 190;   /* teal — water, ice, soil: the critical zone */
  --h-solid-earth: 70;   /* amber-brown — rock and tectonics */
  --h-society:    320;   /* plum — the human pillar */

  /* How saturated a pillar is drawn. Methods is the one the level brings rather
     than the one you pick, and it is not a domain of the Earth like the others,
     so it is drawn desaturated: visibly a different kind of thing. */
  --chroma: 1;

  /* How a hue becomes a surface in this theme. These are lightness/chroma
     pairs, not finished colours: each is composed with the element's own --h
     at the point of use, because a custom property containing var(--h) would
     resolve here on :root, where no hue exists yet. */
  --h: 250;
  --fill-l: 0.925; --fill-c: 0.048;
  /* A course not in the semester, once a semester exists: no hue at all, just a
     neutral card. Colour is the state — a chosen course is the only tinted block
     on the grid — and the pillar is still named by the bar down its left edge,
     which keeps full strength whatever a student has chosen.

     Grey rather than white: the grid itself is --paper, so a white tile would
     dissolve into the empty cells around it and a reader could not tell a course
     from a free hour. Never a fade, either — dimming a tile dims its words, and
     the courses not yet taken are the ones still being read. */
  --idle-bg: #e3e7ea;
  /* ...and a course that IS in it: the accent, the colour this site already uses
     for a thing that is done, laid on thickly enough to be told from the grey
     across a whole screen of tiles. 13.0:1 against the title ink here and 9.4:1
     on the accent — the house floor for course text was 10:1, and this is the
     one surface that now sits below it, still well clear of AAA's 7:1. Going
     further is possible and costs legibility; that was the trade. */
  --pick-bg: color-mix(in srgb, var(--accent) 30%, var(--paper));
  --edge-l: 0.555; --edge-c: 0.135;
  --solid-l: 0.615; --solid-c: 0.145;
  --wash-l: 0.955; --wash-c: 0.028;

  --row: 27px;
  --time-col: 3.25rem;
  --measure: 68ch;
}

/* Dark tokens are defined twice on purpose: once for the OS preference and
   once for an explicit choice, so a host that stamps data-theme still resolves. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:      #171b20;
    --ground-deep: #1f242b;
    --paper:       #212730;
    --ink:         #e7eaee;
    --ink-soft:    #bcc4cd;
    --stone:       #8b959f;
    --rule:        #39424c;
    --rule-soft:   #2b323a;
    --accent:      #5fae86;
    --accent-ink:  #86c7a3;
    --warn:        #dd8168;

    --fill-l: 0.325; --fill-c: 0.045;
    --idle-bg: #161a1f;                 /* below --paper, as light sits above it */
    --pick-bg: color-mix(in srgb, var(--accent) 24%, var(--paper));   /* 8.1:1 */
    --edge-l: 0.70;  --edge-c: 0.115;
    --solid-l: 0.66; --solid-c: 0.125;
    --wash-l: 0.26;  --wash-c: 0.028;
  }
}
:root[data-theme="dark"] {
  --ground:      #171b20;
  --ground-deep: #1f242b;
  --paper:       #212730;
  --ink:         #e7eaee;
  --ink-soft:    #bcc4cd;
  --stone:       #8b959f;
  --rule:        #39424c;
  --rule-soft:   #2b323a;
  --accent:      #5fae86;
  --accent-ink:  #86c7a3;
  --warn:        #dd8168;

  --fill-l: 0.325; --fill-c: 0.045;
  --idle-bg: #161a1f;
  --pick-bg: color-mix(in srgb, var(--accent) 24%, var(--paper));
  --edge-l: 0.70;  --edge-c: 0.115;
  --solid-l: 0.66; --solid-c: 0.125;
  --wash-l: 0.26;  --wash-c: 0.028;
}

[data-track="methods"]     { --h: var(--h-methods); --chroma: .38; }
[data-track="ocean-atm"]   { --h: var(--h-ocean-atm); }
[data-track="interfaces"]  { --h: var(--h-interfaces); }
[data-track="solid-earth"] { --h: var(--h-solid-earth); }
[data-track="society"]     { --h: var(--h-society); }

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; scroll-padding-top: 4.5rem; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation: none !important; transition: none !important; }
}

body {
  background: var(--ground);
  color: var(--ink);
  font-family: 'Open Sans', system-ui, sans-serif;
  /* 100%, not 16px: a reader who has raised their browser's default text size
     means it, and an absolute base would ignore them and leave this site small
     beside every other. Everything else here is in rem, so the whole page
     scales from this one value. */
  font-size: 100%;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, .slab {
  font-family: 'Roboto Slab', Georgia, serif;
  font-weight: 500;
  letter-spacing: -0.005em;
  line-height: 1.2;
  text-wrap: balance;
}

a { color: var(--accent-ink); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── masthead ─────────────────────────────────────────────── */

.masthead { background: var(--ground); padding: 1.75rem 1.5rem .9rem; }
.masthead-inner { max-width: 1180px; margin: 0 auto; }
.masthead h1 { font-size: clamp(1.5rem, 3.2vw, 2.1rem); }
/* No reading measure here: --measure caps prose so long lines stay comfortable,
   but this is one fixed 83-character line of metadata. At 68ch it overflowed by
   14px and dropped the year onto a line of its own, in a masthead 1180px wide. */
.masthead p { color: var(--stone); margin-top: .25rem; font-size: .95rem; }

/* The only piece of the department's navigation this page carries. Copying the
   real menu would mean importing the theme's scripts to make it open, and the
   copy would go stale silently the day someone edits the menu in WordPress; the
   way back is all a reader needs from it. Absolute, because the department page
   is at that address whatever host this site is served from. */
.masthead .up {
  display: inline-block; margin-bottom: .5rem;
  font-size: .85rem; color: var(--stone); text-decoration: none;
}
.masthead .up:hover { color: var(--accent); text-decoration: underline; }

/* ── sticky bar: tabs, selection, level ───────────────────── */

.bar {
  position: sticky; top: 0; z-index: 40;
  padding: .55rem 1.5rem;
  background: color-mix(in srgb, var(--ground) 92%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--rule);
}
.bar-inner { max-width: 1180px; margin: 0 auto; width: 100%;
  display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; align-items: center;
  justify-content: space-between; }

.tabs { display: flex; gap: .15rem; overflow-x: auto; scrollbar-width: none;
  margin: 0 -.35rem; padding: 0 .35rem; }
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  font: inherit; font-size: .93rem; white-space: nowrap; cursor: pointer;
  background: none; border: 0; border-bottom: 2px solid transparent;
  color: var(--stone); padding: .45rem .55rem .35rem;
}
.tabs button:hover { color: var(--ink); }
.tabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent);
  font-weight: 600; }

/* The level buttons, the pillar filter and the count are all about the week, so
   they live in the week panel rather than the site-wide bar. They stay sticky
   there: the grid is taller than a screen, so a reader ticking an afternoon
   course still needs to see what year they are filtering to and what is chosen.
   Offset by the bar's own height, since that is sticky above it. */
.week-controls { position: sticky; top: var(--bar-h); z-index: 30;
  display: flex; align-items: center; gap: .6rem 1rem; flex-wrap: wrap;
  padding: .5rem 0; margin-bottom: .2rem;
  background: color-mix(in srgb, var(--ground) 92%, transparent);
  backdrop-filter: blur(8px); }
.week-controls .legend { flex: 1 1 auto; margin: 0; }
/* the chip and the level buttons sit together on the right, as they did in the
   site bar before they moved in here */
.week-controls .chip { margin-left: auto; }
.week-controls .levels { flex: none; }
/* Clearing is something you do to the selection, so it sits with the buttons
   that filter it rather than among the things you do with a finished semester
   (export, print) at the foot of the page. */
.week-controls #clear-picks { flex: none; }
.chip {
  font: inherit; font-size: .82rem; cursor: pointer; white-space: nowrap;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  color: var(--accent-ink); padding: .18rem .5rem;
  font-variant-numeric: tabular-nums;
}
.chip:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.chip[data-clash="true"] { color: var(--warn);
  background: color-mix(in srgb, var(--warn) 13%, transparent);
  border-color: color-mix(in srgb, var(--warn) 40%, transparent); }

.levels { display: flex; border: 1px solid var(--rule); }
.levels button {
  font: inherit; font-size: .88rem; padding: .25rem .85rem; cursor: pointer;
  background: transparent; color: var(--ink-soft); border: 0;
  border-right: 1px solid var(--rule);
}
.levels button:last-child { border-right: 0; }
.levels button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

/* ── layout ───────────────────────────────────────────────── */

main { max-width: 1180px; margin: 0 auto; padding: 0 1.5rem 5rem; }
[hidden] { display: none !important; }
main > section { padding-top: 2rem; }
main > section:focus { outline: none; }
.panel-head { margin-bottom: 1.15rem; }
.panel-head h2 { font-size: 1.5rem; }
/* What is still missing, at the head of the semester it is missing from and
   above the two registration sections: you register once the choice is right,
   so the order reads as what you have, what is short, where to go. The sticky
   chip beside the level buttons is what carries this down the grid — it says
   how many and how much, and jumps here when clicked. */
/* No card: the planner is already one, and a bordered box inside a bordered box
   reads as a notice stuck on afterwards. These are the first lines of the
   semester, so they are set as its opening text, in the same rhythm as the
   sentence under them. */
.tally-now { padding: .7rem .9rem .1rem; font-size: .87rem; max-width: var(--measure); }
.tally-now ul { list-style: none; display: grid; gap: .15rem; }
.tally-now li { color: var(--ink-soft); padding-left: 1rem; position: relative; }
.tally-now li::before { position: absolute; left: 0; }
.tally-now li[data-ok="true"]::before  { content: "\2713"; color: var(--ok, var(--stone)); }
.tally-now li[data-ok="false"]::before { content: "\00b7"; }
.tally-now li[data-ok="bad"]::before   { content: "\0021"; color: var(--warn, var(--ink)); }

.lede { color: var(--stone); max-width: var(--measure); margin-top: .3rem; }
.result-count { align-self: center; color: var(--stone); font-size: .85rem;
  font-variant-numeric: tabular-nums; }
.prose { max-width: var(--measure); }
.prose p, .prose ul { margin-top: .8rem; }
.prose ul { padding-left: 1.1rem; }
.prose li { margin-top: .3rem; }
.prose h2 { font-size: 1.3rem; margin-top: 2.2rem; }
.prose h3 { font-size: 1.05rem; margin-top: 1.5rem; }

/* ── the week grid ────────────────────────────────────────── */

.grid-scroll { overflow-x: auto; padding-bottom: .5rem; }

.week {
  display: grid;
  grid-template-columns: var(--time-col) repeat(10, minmax(76px, 1fr));
  min-width: 860px;
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  background: var(--paper);
}
.week .dayhead {
  grid-row: 1; grid-column: span 2;
  font-size: .8rem; font-weight: 600; letter-spacing: .04em;
  padding: .4rem .5rem; color: var(--ink-soft);
  border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  background: var(--ground-deep);
}
.week .corner { grid-row: 1; grid-column: 1;
  border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  background: var(--ground-deep); }

.week .tick {
  grid-column: 1;
  font-size: .72rem; font-variant-numeric: tabular-nums;
  color: var(--stone); padding: 1px .5rem 0 0; text-align: right;
  border-right: 1px solid var(--rule);
}
.week .tick.hour { border-top: 1px solid var(--rule-soft); color: var(--ink-soft); }
.week .lane { border-right: 1px solid var(--rule-soft); }
.week .lane.day-end { border-right: 1px solid var(--rule); }
.week .hourline { border-top: 1px solid var(--rule-soft); pointer-events: none; }

.course {
  position: relative;
  margin: 0 1px 1px 0;
  padding: .22rem .3rem .3rem calc(.35rem + 3px);
  border: 0;
  /* Every state of a tile changes ONE thing, this: both marks read it, so a
     fixture or a chosen course sets a colour once instead of twice. */
  --mark: oklch(var(--edge-l) calc(var(--edge-c) * var(--chroma)) var(--h));
  background: var(--idle-bg);
  color: var(--ink);
  font: inherit; font-size: .755rem; line-height: 1.25;
  text-align: left; cursor: pointer;
  display: flex; flex-direction: column; gap: .1rem;
  overflow: hidden;
  transition: background-color .12s ease, filter .12s ease;
}
/* Two marks in the corner, each four fifths of its side. Stopping short is the
   whole point: rules that run wall to wall close the tile into a box and put a
   second grid on top of the grid, while these read as a mark ON the tile. The
   top one also says where the course starts, which matters most where two sit
   stacked in one column — most of the grid, every day having two.

   Pseudo-elements rather than borders, which cannot be given a length; the left
   one's 3px comes out of the padding so the text sits where it always did. */
.course::before, .course::after {
  content: ''; position: absolute; top: 0; left: 0; background: var(--mark);
}
.course::before { width: 80%; height: 3px; }
.course::after  { width: 3px; height: 80%; }
/* A chosen course wears its marks heavier. Free to do: both are positioned, so
   a fourth pixel moves no text, and weight carries the state for a reader who
   cannot separate the accent from the grey. */
.course[data-picked="true"]::before { height: 4px; }
.course[data-picked="true"]::after  { width: 4px; }
.course:hover { filter: brightness(1.03) saturate(1.12); }
/* hyphens:auto is deliberately not set: no hyphenation dictionary is in play in
   the browsers this is read in, so it silently does nothing and the wrapping
   falls to overflow-wrap anyway. Saying so beats leaving a declaration that
   looks like it is working. */
.course .t { font-weight: 600; overflow-wrap: break-word; }
.course .m { color: var(--ink-soft); font-size: .72rem; overflow-wrap: break-word; }
/* The room is its own line: it is the one thing a student reads while walking,
   so it should not have to be picked out of the middle of the teacher line. */
.course .r {
  color: var(--ink-soft); font-size: .72rem;
  font-variant-numeric: tabular-nums; letter-spacing: .01em;
}
.course .lvl {
  align-self: flex-start; font-size: .64rem; font-weight: 600;
  border: 1px solid color-mix(in srgb, var(--ink) 30%, transparent);
  padding: 0 .22rem; margin-top: .1rem;
}
/* Bottom-right, not top-right: in that corner it would have to be kept clear of
   the title's first line, which cost that line 16px of the ~90px a column has —
   the reason long titles used to break mid-word on line one and nowhere else.
   Down here it sits in the slack every block has below its text.

   Quiet until wanted: the tile's own fill reports whether the course is in, so
   this glyph only has to be findable by someone who wants to read. */
.course .info {
  position: absolute; bottom: 2px; right: 3px;
  font-size: 12px; line-height: 1;
  color: color-mix(in srgb, currentColor 55%, transparent);
}
.course:hover .info, .course:focus-visible .info { color: currentColor; }
/* A fixture (the Seminar) belongs to no pillar, so there is no hue to build
   from: it takes the paper tones and a plain edge, which is also the point —
   it should not look like one of the courses you are choosing between. */
.course[data-fixture="true"] {
  background: var(--ground-deep);
  --mark: var(--rule);
  color: var(--ink-soft);
}
.course[data-fixture="true"] .t { font-weight: 400; font-style: italic; }

/* Compulsory: brought by the level, so it is drawn picked and has no control to
   argue with. The fill is the whole statement — the same thing the always-ticked
   box used to say, with one fewer glyph in a 107px tile. */
.course[data-compulsory="true"] { cursor: pointer; }
/* In the semester. Two fills, and the pillar is not one of them: it is said by
   the two marks in the corner, and saying it in the fill as well left every tile
   tinted with the one difference that matters — chosen or not — the faintest
   thing on the grid. The words stay at full strength in both states: a student
   reads the tiles they have NOT taken more than the ones they have, which is
   what choosing is, so fading them faded the half of the grid still in use. */
.course[data-picked="true"] {
  background: var(--pick-bg);
  --mark: oklch(var(--solid-l) calc(var(--solid-c) * var(--chroma)) var(--h));
}
.course[data-picked="true"] .t { font-weight: 700; }
.course.clash { box-shadow: inset 0 0 0 2px var(--warn); }

/* ── legend ───────────────────────────────────────────────── */

.legend { display: flex; flex-wrap: wrap; gap: .3rem 1.1rem; margin: 0 0 .9rem;
  align-items: center; }
.legend button {
  display: inline-flex; align-items: center; gap: .4rem;
  font: inherit; font-size: .85rem; color: var(--ink-soft);
  background: none; border: 0; cursor: pointer; padding: .15rem 0;
  border-bottom: 2px solid transparent;
}
.legend button[aria-pressed="false"] { opacity: .5; }
.legend button:hover { border-bottom-color: var(--rule); }
/* All carries the five pillar bars side by side, in the order the legend lists
   them: it stands for all of them, so it shows all of them. Five bars across
   rather than five bands down, because a bar is what a pillar looks like
   everywhere else on this page. Hard stops, not a blend — a gradient would read
   as a sixth colour rather than as the five. */
.legend .all { padding-right: .55rem; margin-right: .2rem;
  border-right: 1px solid var(--rule-soft); }
.legend .all .sw {
  width: 24px;
  background: linear-gradient(to right,
    oklch(var(--edge-l) var(--edge-c) var(--h-methods))      0 4px,
    transparent                                             4px 5px,
    oklch(var(--edge-l) var(--edge-c) var(--h-ocean-atm))    5px 9px,
    transparent                                             9px 10px,
    oklch(var(--edge-l) var(--edge-c) var(--h-interfaces))  10px 14px,
    transparent                                            14px 15px,
    oklch(var(--edge-l) var(--edge-c) var(--h-solid-earth)) 15px 19px,
    transparent                                            19px 20px,
    oklch(var(--edge-l) var(--edge-c) var(--h-society))     20px 24px);
}
/* The bar, not a filled block: a tile's fill says chosen or not chosen now, and
   the pillar is carried by the marks in its corner. A pastel square here would
   be a key to a colour the grid no longer uses. */
.legend .sw { width: 4px; height: 15px;
  background: oklch(var(--edge-l) calc(var(--edge-c) * var(--chroma)) var(--h)); }

/* ── planner ──────────────────────────────────────────────── */

.planner { margin-top: 1.5rem; border: 1px solid var(--rule); background: var(--paper); }
.planner-head {
  display: flex; flex-wrap: wrap; gap: .6rem 1.5rem; align-items: center;
  justify-content: space-between; padding: .7rem .9rem;
  border-bottom: 1px solid var(--rule-soft);
}
.planner-head strong { font-size: 1.05rem; }
.planner-head .count { font-variant-numeric: tabular-nums; color: var(--stone); }
.planner-actions { display: flex; gap: .45rem; flex-wrap: wrap; }
.enrol-head { display: flex; align-items: baseline; justify-content: space-between;
  gap: .8rem; }
.enrol-head .btn { padding: .12rem .5rem; font-size: .78rem; }

/* the enrolment checklist: one row per chosen course, ticked off as the student
   walks it. The colour bar is composed at the point of use from the pillar hue
   the row carries, like every other coloured element here. */
/* The planner's own sentences: what the rules ask, and that registration takes
   two places. Both sit a step quieter than the lines they frame — the rules
   above the standing against them, the registration line above the two sections
   that act on it. */
.planner-line { padding: .55rem .9rem .1rem; font-size: .87rem; color: var(--stone);
  max-width: var(--measure); }
#reg-intro { padding-bottom: .75rem; }
/* Both registrations on one line of their own, a middot apart: two links a
   hand's width apart read as alternatives rather than as the two halves of one
   errand, and each label stays whole rather than wrapping mid-phrase. */
.reg-links { display: block; margin-top: .4rem; }
.reg-links a { white-space: nowrap; }
.reg-links a + a::before { content: "\00b7"; color: var(--stone);
  margin: 0 .4em; text-decoration: none; display: inline-block; }

.enrol { padding: 0 .9rem 1.1rem; border-top: 1px solid var(--rule); margin-top: -.2rem; }
.enrol h4 { font-size: .78rem; letter-spacing: .06em; color: var(--stone);
  font-weight: 600; margin: .9rem 0 .35rem; }
.enrol .lede { font-size: .85rem; color: var(--stone); margin-bottom: .6rem; }
.enrol-list { list-style: none; display: grid; gap: .3rem; }
.enrol-list li { display: flex; align-items: center; gap: .55rem;
  padding: .4rem .6rem; border: 1px solid var(--rule);
  border-left: 3px solid oklch(var(--edge-l) calc(var(--edge-c) * var(--chroma)) var(--h));
  background: oklch(var(--fill-l) calc(var(--fill-c) * var(--chroma)) var(--h)); }
.enrol-list .name { flex: 1; font-size: .89rem;
  display: flex; align-items: baseline; gap: .4rem; }
/* When a row is not part of the semester on the grid, it says when it is. */
.enrol-list .when { font-size: .78rem; color: var(--stone); margin-left: .45rem; }
.enrol-list .tag { font-size: .68rem; letter-spacing: .05em; text-transform: uppercase;
  color: var(--stone); }
.enrol-list .none { font-size: .78rem; color: var(--stone); font-style: italic; }
.enrol-list .btn { padding: .12rem .5rem; font-size: .78rem; }

.checks li { list-style: none; padding: .2rem 0 .2rem 1.3rem; position: relative;
  font-size: .87rem; }
.checks li::before { position: absolute; left: 0; font-weight: 700; }
.checks li[data-ok="true"]::before  { content: '✓'; color: var(--accent); }
.checks li[data-ok="false"]::before { content: '·'; color: var(--stone); }
.checks li[data-ok="bad"]::before   { content: '!'; color: var(--warn); }
.checks li[data-ok="bad"] { color: var(--warn); }

.btn {
  font: inherit; font-size: .85rem; padding: .3rem .7rem; cursor: pointer;
  background: transparent; border: 1px solid var(--rule); color: var(--ink-soft);
}
.btn:hover { border-color: var(--stone); color: var(--ink); }
.btn:disabled { opacity: .45; cursor: default; }

/* ── catalogue ────────────────────────────────────────────── */

.tools { display: flex; flex-wrap: wrap; gap: .6rem; margin-bottom: 1rem; }
.tools input {
  font: inherit; padding: .4rem .6rem; min-width: min(340px, 100%);
  border: 1px solid var(--rule); background: var(--paper); color: var(--ink);
}
.cards { border-top: 1px solid var(--rule); }
/* The row carries the rule and the hover; the button inside it carries the
   layout, so the Moodle link can sit outside the button (a link may not be
   nested in one) while still looking like part of the same row. */
.card {
  display: grid; grid-template-columns: 1fr auto; gap: 0 .85rem;
  align-items: baseline; border-bottom: 1px solid var(--rule-soft);
}
.card .open {
  display: grid; grid-template-columns: 3px 1fr auto; gap: 0 .85rem;
  padding: .8rem 0; align-items: baseline; width: 100%; text-align: left;
  background: none; border: 0; font: inherit; color: inherit; cursor: pointer;
}
.card:hover { background: oklch(var(--wash-l) calc(var(--wash-c) * var(--chroma)) var(--h)); }
.card .moodle {
  align-self: center; padding: .8rem 0 .8rem .2rem;
  font-size: .8rem; white-space: nowrap; color: var(--stone);
  text-decoration-thickness: 1px; text-underline-offset: 2px;
}
.card .moodle:hover, .card .moodle:focus-visible { color: var(--accent); }
.card .bar-c { grid-row: 1 / -1; background: oklch(var(--solid-l) calc(var(--solid-c) * var(--chroma)) var(--h)); align-self: stretch; }
.card h3 { font-size: 1rem; }
.card .meta { color: var(--stone); font-size: .85rem; margin-top: .15rem; }
.card .when { color: var(--ink-soft); font-size: .82rem; font-variant-numeric: tabular-nums;
  white-space: nowrap; }
.empty { color: var(--stone); padding: 1.5rem 0; }

/* ── course dialog ────────────────────────────────────────── */

dialog {
  /* margin:auto is what centres a modal dialog; the global * { margin: 0 } reset
     above removes the UA default, so it has to be restored here. */
  margin: auto;
  border: 1px solid var(--rule); background: var(--paper); color: var(--ink);
  padding: 0; max-width: min(660px, 92vw); width: 100%;
  max-height: min(84vh, 760px);
}
dialog::backdrop { background: color-mix(in srgb, #0b0e12 55%, transparent); }
.dlg-head { padding: 1.1rem 1.3rem .9rem; border-bottom: 1px solid var(--rule-soft);
  border-top: 4px solid oklch(var(--edge-l) calc(var(--edge-c) * var(--chroma)) var(--h)); background: oklch(var(--wash-l) calc(var(--wash-c) * var(--chroma)) var(--h)); }
.dlg-head h3 { font-size: 1.25rem; }
.dlg-head .meta { color: var(--ink-soft); font-size: .9rem; margin-top: .25rem; }
.dlg-body { padding: 1rem 1.3rem 1.3rem; max-height: 62vh; overflow-y: auto; }
.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: .6rem 1rem; margin-bottom: 1rem; }
.facts div { font-size: .87rem; }
.facts dt { color: var(--stone); font-size: .78rem; }
.facts dd { margin-top: .1rem; }
.dlg-body p { margin-top: .7rem; max-width: var(--measure); }
.dlg-foot { display: flex; flex-wrap: wrap; gap: .6rem; justify-content: space-between;
  align-items: center; padding: .8rem 1.3rem; border-top: 1px solid var(--rule-soft);
  background: var(--ground-deep); }

/* ── dates ────────────────────────────────────────────────── */

.dates { border-top: 1px solid var(--rule); }
.date-row { display: grid; grid-template-columns: 11.5rem 1fr; gap: .3rem 1.1rem;
  padding: .65rem 0; border-bottom: 1px solid var(--rule-soft); }
.date-row .when { font-variant-numeric: tabular-nums; color: var(--ink-soft); font-size: .9rem; }
.date-row .what { font-weight: 600; }
.date-row .note { color: var(--stone); font-size: .87rem; font-weight: 400;
  max-width: var(--measure); margin-top: .15rem; }
.date-row[data-next="true"] { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.date-row[data-next="true"] .when { color: var(--accent-ink); font-weight: 600; }
.date-row[data-past="true"] { opacity: .45; }

/* A PSL-wide entry the master does not follow. Shown, not hidden: students see
   it on the PSL calendar and need to know it does not apply to them. */
.date-row[data-applies="false"] .what { color: var(--stone); font-weight: 400; }
.date-row[data-applies="false"] .when { text-decoration: line-through;
  text-decoration-thickness: 1px; }
.tag {
  display: inline-block; margin-left: .5rem; vertical-align: 1px;
  font-size: .72rem; font-weight: 600; letter-spacing: .01em;
  color: var(--warn); padding: 0 .32rem;
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
}
/* Same chip, but registration is an invitation, not a warning, so it takes the
   accent rather than the warn colour. */
.tag.enrol {
  color: var(--accent-ink);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  font-weight: 400; text-transform: lowercase;
}
/* The two things you can do with a course sit together; Close stays opposite. */
.dlg-actions { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }
a.btn { text-decoration: none; display: inline-block; }
a.btn:hover { border-color: var(--accent); color: var(--accent); }

/* ── practical ────────────────────────────────────────────── */

.two-col { display: grid; gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.stack { list-style: none; border-top: 1px solid var(--rule); }
.stack li { padding: .55rem 0; border-bottom: 1px solid var(--rule-soft); font-size: .93rem; }
/* A heading that follows content needs air above it; the first in a column takes
   its spacing from the panel head. */
.stack + .slab, .prose + .slab { margin-top: 1.6rem; }
/* The regulations sit under the rules they are the source of, so they hold the
   same reading measure as the prose above rather than running the page width. */
#degree .stack { max-width: var(--measure); }
.stack .role { color: var(--stone); font-size: .82rem; }
footer.site { border-top: 1px solid var(--rule); margin-top: 4rem; padding: 1.5rem 1.5rem 3rem;
  color: var(--stone); font-size: .85rem; }
footer.site .inner { max-width: var(--measure); margin: 0 auto; display: grid; gap: .6rem; }

/* ── mobile: the grid becomes a day list ──────────────────── */

/* The day list is the narrow-screen form of the grid. Its base rule has to come
   BEFORE the media query: both selectors have the same specificity, so whichever
   is written last would win regardless of the query. */
.daylist { display: none; }
.daylist h3 { font-size: .95rem; padding: .5rem 0 .3rem; border-bottom: 1px solid var(--rule); }
.daylist .course { width: 100%; margin: .4rem 0 0; padding: .5rem .6rem;
  font-size: .85rem; }
.daylist .course .m { font-size: .78rem; }
.daylist .course .t::before { display: none; }
.daylist section { padding-top: 1.25rem; }

@media (max-width: 720px) {
  .masthead { padding: 1.15rem 1rem .7rem; }
  .bar { padding: .5rem 1rem; }
  /* Six tabs are wider than a phone. The strip scrolls sideways, but its
     scrollbar is hidden, so "Practical" sat off the right edge with nothing to
     say it was there. Let the tabs wrap instead: slightly smaller, and onto a
     second line when they must. Nothing is ever off-screen, so nothing has to
     hint that it is — which is why this needs no script and no fade. */
  .tabs { flex-wrap: wrap; overflow-x: visible; }
  .tabs button { font-size: .86rem; padding: .4rem .4rem .3rem; }
  main { padding: 0 1rem 4rem; }
  /* Not sticky here. The row sticks on a wide screen because the grid is taller
     than the window and a reader ticking an afternoon course still needs to see
     which year they are filtering to. A phone shows the day list instead, which
     is linear, and the row costs 160px of a 758px screen — frozen, before any
     course is visible. Scrolling back up for a filter is cheap; losing a fifth
     of the screen to it on every scroll is not. */
  .week-controls { position: static; }
  .grid-scroll { display: none; }
  .daylist { display: block; }
  .date-row { grid-template-columns: 1fr; }
  .planner-body { gap: 1rem; }
}

/* ── print: the timetable, on paper ───────────────────────── */

@media print {
  @page { size: A4 landscape; margin: 12mm; }

  :root {
    --ground: #fff; --ground-deep: #f2f2f2; --paper: #fff;
    --ink: #000; --ink-soft: #333; --stone: #555;
    --rule: #999; --rule-soft: #ccc;
    --fill-l: 0.93; --fill-c: 0.05;
    --idle-bg: #ededed;
    --pick-bg: #c6ded3;
    --edge-l: 0.50; --edge-c: 0.14;
    --solid-l: 0.60; --solid-c: 0.14;
  }
  * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  .bar, .planner-actions, .tools, footer.site, dialog, .course .info,
  .masthead .up { display: none !important; }
  .masthead { padding: 0 0 .5rem; }
  .masthead p { font-size: 10pt; }
  main { padding: 0; max-width: none; }
  main > section { padding-top: 0; }
  .panel-head .lede { display: none; }

  /* print whichever tab is open, and always the week */
  main > section[hidden] { display: none !important; }
  #timetable[hidden] { display: block !important; }
  #timetable[hidden] ~ section:not([hidden]) { display: none !important; }

  .grid-scroll { display: block !important; overflow: visible; }
  .daylist { display: none !important; }
  .week { min-width: 0; page-break-inside: avoid; }
  .course { font-size: 7.5pt; }
  .course .m { font-size: 7pt; }
  .course[data-picked="true"] { background: var(--pick-bg); }

  .planner { page-break-inside: avoid; margin-top: .6rem; border-color: #999; }
  .legend { margin-bottom: .5rem; }
  .cards, .dates { page-break-inside: auto; }
  .card, .date-row { page-break-inside: avoid; }
  a { text-decoration: none; color: #000; }
}
