/* ---------- fonts: self-hosted ----------
   These used to come from two render-blocking <link>s to fonts.googleapis.com,
   which cost ~1.5 s before first render: the browser had to fetch HTML, then
   Google's CSS, then the font files. Declaring @font-face here removes a whole
   round trip to a third party -- style.css is already on the critical path, so
   the fonts are discovered the moment it parses.
   Both families are VARIABLE: one file covers 400-700, which is why there are
   five files and not fifteen. Do not add an @import; that reintroduces the hop
   this change removed. Replacing a file means re-running the fetch in CLAUDE.md. */
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/source-serif-4-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/source-serif-4-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Young Serif';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/young-serif-alaidi.woff2') format('woff2');
  unicode-range: U+61, U+64, U+69, U+6c;
}

/* alaidi.net — a lecturer's course binder.
   Warm paper, ink, oxblood rules, an amber tab on anything you can take away.
   One stylesheet, no framework. */

/* Fonts load from a <link> in each page's <head>. Importing them here instead
   would serialise HTML -> CSS -> font CSS -> font files and block render. */

/* ---------- palette: light is the bare :root ---------- */
:root {
  --paper:       #f5f2ec;
  --surface:     #fffefb;
  --ink:         #1c1917;
  --muted:       #6f6559;
  --rule:        #e0d9cc;
  --rule-strong: #cdc2ae;
  --accent:      #9c3524;
  --accent-soft: #f7eae5;
  --mark:        #e8a317;
  --on-mark:     #1c1917;   /* amber stays amber in both themes, so this never flips */
  --shadow:      0 1px 2px rgba(43, 32, 20, .05), 0 4px 12px -4px rgba(43, 32, 20, .10);
  --shadow-lift: 0 2px 4px rgba(43, 32, 20, .06), 0 12px 28px -8px rgba(43, 32, 20, .18);

  --serif: "Source Serif 4", Iowan Old Style, Palatino, Georgia, serif;
  --sans:  "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Tahoma, sans-serif;
  /* Wordmark only, and the reason is the single-storey "a" -- it lands twice in
     "alaidi", which is what makes the mark read as a mark instead of a serif
     font. Google subsets it to those letters: 1.2 KB, unicode-range a,d,i,l, so
     it can never bleed onto other text. One weight only, 400 -- asking for 600
     here would get a synthesised bold. Source Serif is the swap fallback. */
  --display: "Young Serif", "Source Serif 4", Iowan Old Style, Palatino, Georgia, serif;
  --step:  .25rem;
  --ease:  cubic-bezier(.2, .7, .3, 1);

  color-scheme: light;
}

/* dark: follow the OS, unless the visitor explicitly chose light */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:       #16140f;
    --surface:     #211e18;
    --ink:         #f0ebe2;
    --muted:       #a89d8d;
    --rule:        #35302a;
    --rule-strong: #4a433a;
    --accent:      #e8917a;
    --accent-soft: #2e211c;
    --shadow:      0 1px 2px rgba(0, 0, 0, .4), 0 4px 12px -4px rgba(0, 0, 0, .5);
    --shadow-lift: 0 2px 4px rgba(0, 0, 0, .45), 0 12px 28px -8px rgba(0, 0, 0, .6);
    color-scheme: dark;
  }
}
/* dark: an explicit choice wins in both directions */
:root[data-theme="dark"] {
  --paper:       #16140f;
  --surface:     #211e18;
  --ink:         #f0ebe2;
  --muted:       #a89d8d;
  --rule:        #35302a;
  --rule-strong: #4a433a;
  --accent:      #e8917a;
  --accent-soft: #2e211c;
  --shadow:      0 1px 2px rgba(0, 0, 0, .4), 0 4px 12px -4px rgba(0, 0, 0, .5);
  --shadow-lift: 0 2px 4px rgba(0, 0, 0, .45), 0 12px 28px -8px rgba(0, 0, 0, .6);
  color-scheme: dark;
}

/* ---------- base ---------- */
* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
img { max-width: 100%; height: auto; }

/* the surfaces we did not draw still belong to the design */
::selection { background: var(--mark); color: var(--on-mark); }
:root { caret-color: var(--accent); scrollbar-color: var(--rule-strong) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--rule-strong);
  border: 3px solid var(--paper);
  border-radius: 99px;
}
::-webkit-scrollbar-thumb:hover { background: var(--muted); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

.wrap { width: 100%; max-width: 68rem; margin: 0 auto; padding-inline: clamp(1rem, 4vw, 2rem); }

/* ---------- header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--rule);
}
.site-header .wrap {
  display: flex; align-items: center; gap: .5rem;
  min-height: 4rem;
}
.brand {
  display: inline-flex; align-items: center; gap: .5625rem;
  color: var(--ink);
  font-family: var(--display);
  font-size: 1.375rem; font-weight: 400; letter-spacing: -.01em;
}
.brand:hover,
.brand:focus-visible { text-decoration: none; color: var(--accent); }
.brand img { width: 1.75rem; height: 1.75rem; border-radius: 5px; }

/* The nav marks the current page with a rule under it; the wordmark answers with
   the same device on hover, so the header speaks one marking language, not two.
   line-height:1 is load-bearing: it collapses the inherited leading so the box
   bottom lands on the font's own descent (~4px) instead of half a line below it,
   which is what lets the rule key off the baseline rather than a magic number.
   "alaidi" has no descender, so it can sit that tight. The reduced-motion block
   further down already collapses the wipe to a plain appearance. */
.brand-name { position: relative; line-height: 1; }
.brand-name::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -.125rem;
  height: 2px; background: var(--accent);
  transform: scaleX(0); transform-origin: left;
  transition: transform .32s var(--ease);
}
.brand:hover .brand-name::after,
.brand:focus-visible .brand-name::after { transform: scaleX(1); }

.site-nav { margin-inline-start: auto; display: flex; align-items: center; gap: .125rem; }
.site-nav a {
  position: relative;
  /* "About me" is two words and must stay on one line: the header is a single
     flex row, so a wrapped label grows its height and knocks the row out of
     alignment. It has no slack left now that the AR/EN button shares the row. */
  white-space: nowrap;
  padding: .5rem .75rem;
  color: var(--muted);
  font-size: .9375rem; font-weight: 500;
  border-radius: 7px;
}
.site-nav a:hover { color: var(--ink); background: var(--accent-soft); text-decoration: none; }
.site-nav a[aria-current="page"] { color: var(--ink); }
/* the current page is marked by a rule, not a pill — quieter, and it reads as a tab */
.site-nav a[aria-current="page"]::after {
  content: "";
  position: absolute; left: .75rem; right: .75rem; bottom: -.0625rem;
  height: 2px; background: var(--accent);
}

/* ---------- page shell ---------- */
main { flex: 1; padding-block: clamp(2rem, 6vw, 3.5rem) 4rem; }

.page-title {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(2rem, 1.3rem + 3.2vw, 3.25rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -.025em;
  text-wrap: balance;
}
.page-sub {
  margin: .625rem 0 0;
  color: var(--muted);
  font-size: 1rem;
}

/* home: title block sits left, switch to its right — no centred-everything default */
.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  flex-wrap: wrap; gap: 1.25rem;
  padding-bottom: 1.5rem;
  margin-bottom: 2.5rem;
  border-bottom: 1px solid var(--rule);
}

/* ---------- breadcrumb ---------- */
.breadcrumb {
  margin: 0 0 1.25rem;
  color: var(--muted);
  font-size: .875rem;
}
.breadcrumb a { color: var(--muted); }
.breadcrumb a:hover { color: var(--accent); }
.breadcrumb [aria-current] { color: var(--ink); }
.breadcrumb .sep { padding-inline: .4rem; color: var(--rule-strong); }

/* ---------- course index ---------- */
.term { margin-bottom: 3.5rem; scroll-margin-top: 5.5rem; }
.term:last-of-type { margin-bottom: 0; }

/* term label reads as a binder tab: small, tracked, ruled across */
.term > h2 {
  display: flex; align-items: center; gap: 1rem;
  margin: 0 0 1.5rem;
  font-family: var(--sans);
  font-size: .8125rem; font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted);
}
.term > h2::after {
  content: ""; flex: 1; height: 1px; background: var(--rule);
}

/* Landing from a breadcrumb -- ../../#spring-2025, or the /spring-2025/ redirect
   in .htaccess -- drops you mid-page with nothing saying which section you asked
   for; scroll-margin-top gets the heading clear of the header but the page still
   looks identical. :target inks that term's binder tab and lets it settle, which
   is the whole effect. No JS, and it costs nothing on the other 15 pages.
   --accent, never --mark: amber is the download affordance, and spending it on a
   heading would dilute the one signal a student is scanning for. */
.term:target > h2        { animation: term-landed 2.4s var(--ease) both; }
.term:target > h2::after { animation: term-landed-rule 2.4s var(--ease) both; }

@keyframes term-landed {
  0%, 45% { color: var(--accent); }
  100%    { color: var(--muted); }
}
@keyframes term-landed-rule {
  0%, 45% { background: var(--accent); height: 2px; }
  100%    { background: var(--rule);   height: 1px; }
}

/* The global reduced-motion block collapses animations to .01ms, and `both`
   holds the END state -- so the arrival would land completely unmarked. Drop the
   animation here and just leave the tab inked. */
@media (prefers-reduced-motion: reduce) {
  .term:target > h2,
  .term:target > h2::after { animation: none; }
  .term:target > h2        { color: var(--accent); }
  .term:target > h2::after { background: var(--accent); }
}

.grid {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
}

.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 14px;
  overflow: hidden;
  transition: border-color .25s var(--ease), box-shadow .25s var(--ease), transform .25s var(--ease);
}
.card:hover {
  border-color: var(--rule-strong);
  box-shadow: var(--shadow-lift);
  transform: translateY(-3px);
}
.card a { display: block; color: inherit; }
.card a:hover { text-decoration: none; }

.card figure { margin: 0; overflow: hidden; background: var(--accent-soft); }
.card img {
  display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover;
  transition: transform .5s var(--ease);
}
.card:hover img { transform: scale(1.04); }

.card-body { padding: 1rem 1.125rem 1.125rem; }
.card h3 {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.1875rem; font-weight: 600; line-height: 1.25;
  letter-spacing: -.01em;
  text-wrap: balance;
}
.card:hover h3 { color: var(--accent); }

/* the count is the reason to click, so it is typeset, not decorated */
.count {
  display: inline-block;   /* a block h3 already breaks the line in a card; inline lets
                              the course page read "12 files to download" on one line */
  margin-top: .5rem;
  color: var(--muted);
  font-size: .8125rem;
  font-variant-numeric: tabular-nums;
}
.count b { font-weight: 600; color: var(--ink); }
.count.none { font-style: italic; }

/* ---------- cards / table switch ---------- */
.view-switch {
  display: flex; gap: 2px; flex-shrink: 0;
  padding: 3px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 10px;
}
.view-switch button {
  display: inline-flex; align-items: center; gap: .4375rem;
  padding: .4375rem .75rem;
  border: 0; border-radius: 7px;
  background: none; color: var(--muted);
  font: inherit; font-size: .8125rem; font-weight: 500;
  cursor: pointer;
  transition: color .15s var(--ease), background-color .15s var(--ease);
}
.view-switch button svg { width: .875rem; height: .875rem; fill: currentColor; }
.view-switch button:hover { color: var(--ink); }
.view-switch button[aria-pressed="true"] {
  background: var(--accent-soft); color: var(--accent);
}

/* table view: the same markup, laid out as a ledger */
:root[data-view="table"] .grid {
  display: block;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 12px;
  overflow: hidden;
}
:root[data-view="table"] .card {
  background: none; border: 0; border-radius: 0; box-shadow: none; transform: none;
  border-bottom: 1px solid var(--rule);
}
:root[data-view="table"] .card:last-child { border-bottom: 0; }
:root[data-view="table"] .card:hover { background: var(--accent-soft); box-shadow: none; transform: none; }
:root[data-view="table"] .card a {
  display: flex; align-items: center; gap: .875rem;
  padding: .625rem .875rem;
}
:root[data-view="table"] .card figure { flex-shrink: 0; border-radius: 6px; }
:root[data-view="table"] .card img { width: 3.25rem; aspect-ratio: 3 / 2; }
:root[data-view="table"] .card:hover img { transform: none; }
:root[data-view="table"] .card-body {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  flex: 1; min-width: 0; padding: 0;
}
:root[data-view="table"] .card h3 { font-size: 1rem; font-weight: 500; }
:root[data-view="table"] .count { margin: 0; flex-shrink: 0; text-align: end; }

/* ---------- article ---------- */
.prose {
  max-width: 72ch;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 14px;
  padding: clamp(1.5rem, 4vw, 2.5rem);
}
.prose.wide { max-width: none; }
/* a wide article is for the tables; its running text keeps a reading measure */
.prose.wide > p,
.prose.wide > ul,
.prose.wide > ol,
.prose.wide > blockquote { max-width: 68ch; }
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }

.prose h2 {
  margin: 2.75rem 0 1rem;
  font-family: var(--serif);
  font-size: 1.5rem; font-weight: 600; line-height: 1.2;
  letter-spacing: -.015em;
}
.prose h3 { margin: 2rem 0 .625rem; font-size: 1.0625rem; font-weight: 600; }
.prose p { margin: 0 0 1rem; }
.prose ul, .prose ol { margin: 0 0 1rem; padding-inline-start: 1.25rem; }
.prose li { margin-bottom: .375rem; }
.prose li::marker { color: var(--rule-strong); }

/* a textbook is a citation, so it is set like one */
.prose blockquote {
  margin: 0 0 .75rem;
  padding-block: .75rem;
  padding-inline: 1.125rem 0;
  border-inline-start: 1px solid var(--rule-strong);
  color: var(--muted);
  font-family: var(--serif);
  font-size: 1.0625rem; line-height: 1.5;
}
.prose blockquote p { margin: 0; }
.prose hr { border: 0; border-top: 1px solid var(--rule); margin: 2.5rem 0; }

/* ---------- syllabus tables ---------- */
.table-scroll {
  margin: 0 0 1.25rem;
  border: 1px solid var(--rule);
  border-radius: 10px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  /* the two `local` layers ride with the content and uncover the two `scroll`
     shadows only while there is more table in that direction */
  background:
    linear-gradient(to right, var(--surface) 40%, transparent) left  / 2.5rem 100% no-repeat local,
    linear-gradient(to left,  var(--surface) 40%, transparent) right / 2.5rem 100% no-repeat local,
    radial-gradient(farthest-side at 0    50%, rgba(28,25,23,.16), transparent) left  / .875rem 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(28,25,23,.16), transparent) right / .875rem 100% no-repeat scroll;
}
.prose table {
  width: 100%; min-width: 40rem;
  border-collapse: collapse;
  font-size: .9375rem;
  font-variant-numeric: tabular-nums;
}
.prose thead th {
  padding: .75rem .875rem;
  background: var(--paper);
  border-bottom: 1px solid var(--rule-strong);
  color: var(--muted);
  text-align: start;
  font-size: .75rem; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase;
  white-space: nowrap;
}
.prose td {
  padding: .8125rem .875rem;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
  line-height: 1.55;
}
.prose tbody tr:last-child td { border-bottom: 0; }
.prose tbody tr:hover { background: color-mix(in srgb, var(--accent-soft) 55%, transparent); }
/* week number: the reader's anchor down a 16-row table */
.prose td:first-child, .prose th:first-child {
  width: 1%; white-space: nowrap;
  color: var(--muted);
  font-weight: 600; font-size: .875rem;
}
.prose td:nth-child(2) { white-space: nowrap; color: var(--muted); font-size: .875rem; }
.prose strong { font-weight: 600; }

/* ---------- downloads ---------- */
/* one drawn glyph, defined once, tinted by currentColor via mask */
.dl {
  display: inline-flex; align-items: center; gap: .375rem;
  margin-block: .125rem;
  margin-inline: 0 .25rem;
  padding-block: .1875rem;
  padding-inline: .4375rem .5rem;
  border: 1px solid color-mix(in srgb, var(--mark) 55%, transparent);
  border-radius: 6px;
  background: color-mix(in srgb, var(--mark) 15%, transparent);
  color: var(--ink);
  font-size: .8125rem; font-weight: 500; line-height: 1.4;
  transition: background-color .15s var(--ease), border-color .15s var(--ease);
}
.dl::before {
  content: "";
  width: .75rem; height: .75rem; flex-shrink: 0;
  background: currentColor;
  -webkit-mask: var(--icon-dl) center / contain no-repeat;
  mask: var(--icon-dl) center / contain no-repeat;
}
:root {
  --icon-dl: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="black" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v8"/><path d="M4.5 7 8 10.5 11.5 7"/><path d="M2.5 13.5h11"/></svg>');
}
.dl:hover {
  background: var(--mark); color: var(--on-mark);
  border-color: var(--mark);
  text-decoration: none;
}

/* a file the old site already lost — say so, don't pretend it is a link */
.dead {
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  cursor: not-allowed;
}

/* ---------- about ---------- */
.bio { display: flex; gap: 2rem; flex-wrap: wrap; align-items: flex-start; }
.bio img { width: 13.5rem; border-radius: 12px; flex-shrink: 0; }
.bio-body { flex: 1 1 20rem; min-width: 0; max-width: 68ch; }
.bio-name {
  margin: .25rem 0 1rem;
  font-family: var(--serif);
  font-size: 1.875rem; font-weight: 700; line-height: 1.15; letter-spacing: -.02em;
}
.eyebrow {
  color: var(--accent);
  font-size: .75rem; font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase;
}

.pub {
  padding: 1rem 0;
  border-bottom: 1px solid var(--rule);
}
.pub:last-of-type { border-bottom: 0; }
.pub h3 {
  margin: 0 0 .375rem;
  font-family: var(--serif);
  font-size: 1.0625rem; font-weight: 600; line-height: 1.35;
}
.pub p { margin: 0; color: var(--muted); font-size: .875rem; }
.pub, .timeline { max-width: 68ch; }
.pub .authors { color: var(--ink); font-weight: 600; }

/* career: a ruled timeline, the dot sitting on the rule */
.timeline { margin: 0; padding-inline-start: 1.5rem; list-style: none; border-inline-start: 1px solid var(--rule); }
.timeline li { position: relative; padding: 0 0 2rem 0; }
.timeline li:last-child { padding-bottom: 0; }
.timeline li::before {
  content: "";
  position: absolute; inset-inline-start: -1.5rem; top: .45rem;
  width: 9px; height: 9px; margin-inline-start: -5px;
  border-radius: 50%;
  background: var(--rule-strong);
  outline: 3px solid var(--surface);
}
.timeline li.now::before { background: var(--accent); }
.when {
  display: block;
  color: var(--muted);
  font-size: .75rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.timeline li.now .when { color: var(--accent); }
.timeline h3 {
  margin: .25rem 0 .125rem;
  font-family: var(--serif);
  font-size: 1.125rem; font-weight: 600; line-height: 1.3;
}
.timeline p { margin: 0; color: var(--muted); font-size: .875rem; }

/* ---------- theme toggle ---------- */
.theme-toggle,
.lang-toggle {
  margin-inline-start: .25rem; padding: .5rem;
  display: inline-flex; border: 0; border-radius: 7px;
  background: none; color: var(--muted); cursor: pointer;
  transition: color .15s var(--ease), background-color .15s var(--ease);
}
.theme-toggle:hover,
.lang-toggle:hover { color: var(--ink); background: var(--accent-soft); }
.theme-toggle svg { width: 1.125rem; height: 1.125rem; display: block; }
.theme-toggle .sun { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .sun  { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .moon { display: none; }
}
:root[data-theme="dark"]  .theme-toggle .sun  { display: block; }
:root[data-theme="dark"]  .theme-toggle .moon { display: none; }
:root[data-theme="light"] .theme-toggle .sun  { display: none; }
:root[data-theme="light"] .theme-toggle .moon { display: block; }

/* ---------- footer ---------- */
.site-footer {
  border-top: 1px solid var(--rule);
  padding-block: 2rem;
  color: var(--muted);
  font-size: .875rem;
}
.site-footer .wrap { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.site-footer strong { color: var(--ink); font-family: var(--serif); font-weight: 600; }
/* 24px minimum target size (WCAG 2.2 AA 2.5.8); the bare link box is 18px. */
.site-footer a { color: var(--muted); display: inline-block; padding-block: .25rem; }
.site-footer a:hover { color: var(--accent); }

/* ---------- responsive ---------- */
@media (max-width: 40rem) {
  /* four items in one row on a 360px phone: buy the width back from the padding
     rather than letting anything wrap or scroll sideways */
  .site-nav a { padding-inline: .375rem; }
  .theme-toggle,
  .lang-toggle { margin-inline-start: 0; }
  .lang-toggle { min-width: 1.875rem; padding-inline: .25rem; }

  .page-head { display: block; }
  .view-switch { margin-top: 1.25rem; width: fit-content; }
  .grid { grid-template-columns: 1fr; gap: 1.125rem; }
  .bio img { width: 100%; max-width: 15rem; }
  .prose blockquote { font-size: 1rem; }
  /* 26px pills are fine for a mouse and wrong for a thumb */
  .dl { padding-block: .5rem; padding-inline: .625rem .75rem; margin-block: .1875rem; font-size: .875rem; }
  .prose td { padding: .875rem; }
  :root[data-view="table"] .card-body { display: block; }
  :root[data-view="table"] .count { text-align: start; margin-top: .125rem; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

@media print {
  .site-header, .site-footer, .view-switch, .breadcrumb { display: none; }
  body { background: #fff; color: #000; }
  .prose, .table-scroll { border: 0; padding: 0; }
  .dl { border: 0; background: none; }
}

/* ---------- skip link ---------- */
.skip {
  position: absolute; inset-inline-start: .5rem; top: -4rem; z-index: 30;
  padding: .625rem 1rem; border-radius: 8px;
  background: var(--accent); color: var(--surface);
  font-size: .875rem; font-weight: 600;
  transition: top .15s var(--ease);
}
.skip:focus { top: .5rem; text-decoration: none; }

/* ---------- download label + weight ---------- */
/* a student on mobile data should know what they are about to pull down */
.dl-label { font-weight: 500; }
.dl-meta {
  color: var(--muted);
  font-size: .6875rem; font-weight: 500;
  letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.dl:hover .dl-meta { color: color-mix(in srgb, var(--on-mark) 72%, transparent); }

/* ---------- search ---------- */
.search { position: relative; margin-bottom: 2.5rem; }
.search svg {
  position: absolute; inset-inline-start: .875rem; top: 50%; translate: 0 -50%;
  width: 1.125rem; height: 1.125rem;
  fill: none; stroke: var(--muted); stroke-width: 1.8; stroke-linecap: round;
  pointer-events: none;
}
.search input {
  width: 100%;
  padding-block: .8125rem;
  padding-inline: 2.75rem 1rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 10px;
  color: var(--ink);
  font: inherit; font-size: .9375rem;
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.search input::placeholder { color: var(--muted); }
.search input:hover { border-color: var(--rule-strong); }
.search input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.search input:focus + * { }
.search input::-webkit-search-cancel-button { cursor: pointer; }

.results-count {
  margin: 0 0 1rem;
  color: var(--muted);
  font-size: .875rem;
}
.hit {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .125rem .875rem;
  padding: .8125rem 1rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 10px;
  margin-bottom: .5rem;
  color: inherit;
}
.hit:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  text-decoration: none;
}
.hit-main { grid-column: 1; font-size: .9375rem; font-weight: 500; line-height: 1.45; }
.hit-sub  { grid-column: 1; color: var(--muted); font-size: .8125rem; }
.hit-meta {
  grid-column: 2; grid-row: 1 / span 2;
  align-self: center;
  color: var(--muted);
  font-size: .6875rem; font-weight: 500;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.hit mark {
  background: color-mix(in srgb, var(--mark) 42%, transparent);
  color: inherit;
  border-radius: 2px;
  padding: 0 .0625rem;
}

/* ---------- button ---------- */
/* navigation, not a download: no arrow, and it carries the accent rather than the mark */
.btn {
  display: inline-block;
  padding: .625rem 1.125rem;
  border-radius: 9px;
  background: var(--accent);
  color: var(--surface);
  font-size: .9375rem; font-weight: 600;
}
.btn:hover { text-decoration: none; filter: brightness(1.08); }

/* ---------- a course with no materials yet ---------- */
/* A blank ruled sheet: the binder metaphor doing the work a stock photo would
   do badly. Signals "nothing filed here yet" without faking content. */
.card figure.blank {
  aspect-ratio: 16 / 10;
  background:
    repeating-linear-gradient(to bottom,
      transparent 0 1.375rem, var(--rule) 1.375rem 1.4375rem),
    var(--accent-soft);
}
.card figure.blank::after {
  content: ""; display: block; height: 100%;
  border-inline-start: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  margin-inline-start: 2rem;
}
:root[data-view="table"] .card figure.blank {
  width: 3.25rem; aspect-ratio: 3 / 2;
  background-size: auto, auto 8px, auto;
}
:root[data-view="table"] .card figure.blank::after { margin-inline-start: .6rem; }

/* ---------- Arabic ---------- */
/* Three pieces make the site bilingual and none of them duplicates a page:
   - the inline <head> script sets lang/dir from localStorage before first paint,
     the same trick and the same try/catch as the theme;
   - i18n.js swaps the shared chrome from a text-keyed dictionary;
   - the two prose pages carry both languages as .en / .ar siblings, because
     paragraphs with links and <code> in them do not survive a text swap.
   The .en/.ar pair is CSS-only on purpose: it is correct on the very first paint
   and it keeps working if i18n.js never loads. */
.ar { display: none; }
:root[lang="ar"] .ar { display: revert; }
:root[lang="ar"] .en { display: none; }

/* Syllabus tables and textbook citations stay left-to-right in Arabic: their
   content is deliberately still English, so mirroring the column order or
   throwing the citation rule to the right side only makes them harder to read.
   The <th> labels inside are Arabic and bidi places them correctly. */
:root[lang="ar"] .prose table,
:root[lang="ar"] .prose blockquote { direction: ltr; }
:root[lang="ar"] .prose blockquote { text-align: start; }
/* ...but a citation also keeps a 68ch reading measure, and a box narrower than
   its container goes flush to the INLINE START -- which on an RTL page is the
   right. That left the oxblood rule stranded mid-card with dead space beside it,
   lined up with nothing. Push the island to the left instead, so its rule lands
   on the card's content edge, in line with the syllabus table underneath.
   Physical margin on purpose: the box is direction:ltr, so margin-inline-* here
   would resolve against the island and push it the wrong way. */
:root[lang="ar"] .prose blockquote { margin-right: auto; }

/* Arabic has no matching webfont here (see CLAUDE.md) -- it falls back to the
   system face, which runs smaller than Plex at the same size and sits tight. */
:root[lang="ar"] body { font-size: 1.0625rem; line-height: 1.75; }

/* Two runs of Latin text that bidi gets wrong inside an Arabic paragraph, and
   both are load-bearing rather than cosmetic:
   - a career date range reorders to read END - START, i.e. it states the wrong
     dates. direction:ltr fixes the order, text-align:end keeps the block itself
     ranged right with the rest of the timeline.
   - inline <code> is the opt-out instruction on privacy-policy/. Left to bidi,
     localStorage.setItem("disable-rybbit", "1") comes out with its brackets and
     quotes transposed, and a visitor who retypes what they see gets an error. */
:root[lang="ar"] .when { direction: ltr; text-align: end; }
:root[lang="ar"] code { unicode-bidi: isolate; direction: ltr; }

/* Pre-dates the Arabic work and affects both languages: the opt-out command is
   one unbreakable token wider than a 320px phone, so the whole page scrolled
   sideways. Breaking mid-token is ugly and correct -- it is the only way the
   full command is readable at that width. */
.prose code { overflow-wrap: anywhere; }

/* the AR/EN button borrows the theme toggle's box; only the glyph differs */
.lang-toggle {
  font-family: var(--sans);
  font-size: .875rem; font-weight: 600; line-height: 1.125rem;
  min-width: 2.125rem; justify-content: center;
}
