/* From Fundamentals to AI Apocalypse sign system. Loaded before each page's own CSS.
   Subject colour families: one OKLCH hue per subject, steps I-III walk down
   in lightness (L 0.84 / 0.76 / 0.68, +/-0.04 per subject so pairs stay apart
   under deutan, protan and tritan simulation). Dark ink labels pass 4.9:1 or
   better on every step. Add a subject's II and III by using its next step. */

:root {
  --fau: #04316a;
  --fau-deep: #021d40;
  --fau-tint: #dce5f1;
  --fau-mark: #04316a;
  --band-overdue: #04316a;
  --on-fau: #ffffff;
  --on-fau-soft: #c9d6ea;
  --paper: #ffffff;
  --paper-2: #eef2f7;
  --ink: #0b1b33;
  --muted: #45556d;
  --line: #d3dbe6;
  --act: #04316a;
  --act-ink: #ffffff;
  /* APDR: violet, oklch(L 0.12 290) */
  --apdr-1: #bcb2ff;
  --apdr-2: #a497ea;
  --apdr-3: #8c7ed0;
  /* MEI: green, oklch(L 0.12 130) */
  --mei-1: #bde892;
  --mei-2: #a4cd79;
  --mei-3: #8cb460;
  /* EEI: amber, oklch(L 0.12 80) */
  --eei-1: #e6b55d;
  --eei-2: #cc9c42;
  --eei-3: #b28324;
  /* MATLAB: cyan, oklch(L 0.10 200) */
  --matlab-1: #80ecf1;
  --matlab-2: #64d1d7;
  --matlab-3: #48b7bd;
  --course: var(--paper-2);
  --code-ink: #0b1b33;

  --bg: var(--paper-2);
  --surface: var(--paper);
  --hl: var(--fau-tint);
  --accent: var(--fau);
  --link: var(--fau);

  --f-display: "Hanken Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
  --f-body: "Hanken Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
  --f-mono: "Roboto Mono", ui-monospace, Menlo, Consolas, monospace;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  /* Forward signal: the colour of progress reached. Lines and small fills only, never text or controls. */
  --signal-a: #80ecf1;
  --signal-b: #9be34a;
  --signal: linear-gradient(90deg, var(--signal-a), var(--signal-b));
  /* Soft glow for signal lines and HUD marks; brackets and rulers use --hud-c. */
  --glow: 0 0 6px rgb(128 236 241 / 0.55);
  --hud-c: var(--fau-mark);
  color-scheme: light;
}
/* Subject classes: set on a page's <body> or on a code square */
.s-apdr-1 { --course: var(--apdr-1); }
.s-apdr-2 { --course: var(--apdr-2); }
.s-apdr-3 { --course: var(--apdr-3); }
.s-mei-1 { --course: var(--mei-1); }
.s-mei-2 { --course: var(--mei-2); }
.s-mei-3 { --course: var(--mei-3); }
.s-eei-1 { --course: var(--eei-1); }
.s-eei-2 { --course: var(--eei-2); }
.s-eei-3 { --course: var(--eei-3); }
.s-matlab-1 { --course: var(--matlab-1); }
.s-matlab-2 { --course: var(--matlab-2); }
.s-matlab-3 { --course: var(--matlab-3); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --fau-deep: #021530;
    --fau-tint: #15305a;
    --fau-mark: #3a68ad;
    --band-overdue: #2b5797;
    --paper: #0c1a30;
    --paper-2: #07111f;
    --ink: #e6ecf5;
    --muted: #a6b4c9;
    --line: #233653;
    --act: #a9c4ec;
    --act-ink: #07111f;
    --bg: var(--paper-2);
    --surface: var(--paper);
    --hl: var(--fau-tint);
    --accent: #a9c4ec;
    --link: #a9c4ec;
    color-scheme: dark;
    --hud-c: var(--signal-a);
    --glow: 0 0 8px rgb(128 236 241 / 0.7);
  }
}
:root[data-theme="dark"] {
  --fau-deep: #021530;
  --fau-tint: #15305a;
  --fau-mark: #3a68ad;
  --band-overdue: #2b5797;
  --paper: #0c1a30;
  --paper-2: #07111f;
  --ink: #e6ecf5;
  --muted: #a6b4c9;
  --line: #233653;
  --act: #a9c4ec;
  --act-ink: #07111f;
  --bg: var(--paper-2);
  --surface: var(--paper);
  --hl: var(--fau-tint);
  --accent: #a9c4ec;
  --link: #a9c4ec;
  color-scheme: dark;
  --hud-c: var(--signal-a);
  --glow: 0 0 8px rgb(128 236 241 / 0.7);
}

/* Browser surfaces */
::selection {
  background: var(--fau);
  color: var(--on-fau);
}
html {
  accent-color: var(--fau);
  scrollbar-color: var(--muted) transparent;
}
body {
  font-family: var(--f-body);
  -webkit-font-smoothing: antialiased;
}
a {
  color: var(--link);
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}
a {
  transition: text-decoration-thickness 150ms var(--ease), color 150ms var(--ease);
}
a:hover {
  text-decoration-thickness: 2px;
}
:focus-visible {
  outline: 3px solid var(--act);
  outline-offset: 2px;
}

/* Course code sign: a solid square holding a short code */
.code {
  display: inline-grid;
  place-items: center;
  min-width: 2.75em;
  height: 2.75em;
  padding: 0 0.45em;
  font: 800 0.95rem/1 var(--f-display);
  letter-spacing: 0.01em;
  color: var(--code-ink);
  background: var(--course);
  white-space: nowrap;
  flex: none;
}

/* Site mark: square sign */
.mark {
  flex: none;
  display: block;
}

/* The mark's navy tile drops away on dark backgrounds, so it sits on the page colour. */
.field .mark rect,
:root[data-theme="dark"] .mark rect {
  fill: transparent;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .mark rect {
    fill: transparent;
  }
}

/* Site mark: the Singularity. Orbits precess, the charge travels its orbit, the core pulses. */
.mark .mk-orbit,
.mark .mk-glow {
  transform-origin: 20px 20px;
}
.mark .mk-orbit-a {
  animation: mk-spin 24s linear infinite;
}
.mark .mk-orbit-b {
  animation: mk-spin 36s linear infinite reverse;
}
.mark .mk-glow {
  animation: mk-pulse 3.2s ease-in-out infinite;
}
/* Without motion-path support the charge rests on the orbit's left end. */
.mark .mk-charge {
  transform: translate(6px, 20px);
}
@supports (offset-path: path("M0 0")) {
  .mark .mk-charge {
    transform: none;
    offset-path: path("M6 20 A14 6.2 0 1 1 34 20 A14 6.2 0 1 1 6 20");
    offset-rotate: 0deg;
    animation: mk-travel 6s linear infinite;
  }
}
@keyframes mk-spin {
  to { transform: rotate(360deg); }
}
@keyframes mk-pulse {
  0%, 100% { transform: scale(0.85); opacity: 0.8; }
  50% { transform: scale(1.12); opacity: 1; }
}
@keyframes mk-travel {
  from { offset-distance: 0%; }
  to { offset-distance: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .mark * {
    animation: none !important;
  }
}

/* Sign-field header shared by guides and labs */
.sign {
  background: var(--fau);
  color: var(--on-fau);
  padding: 28px 32px 30px;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 18px;
  row-gap: 10px;
  align-items: start;
}
.sign .code {
  grid-row: 1 / span 2;
  width: 64px;
  height: 64px;
  font-size: 1.15rem;
}
.sign .course {
  grid-column: 2;
  grid-row: 2;
  margin: 0;
  font: 700 0.95rem/1.3 var(--f-display);
  color: var(--on-fau-soft);
}
.sign h1 {
  grid-column: 2;
  grid-row: 1;
  margin: 0;
  color: var(--on-fau);
  font: 800 clamp(1.9rem, 1.2rem + 2.6vw, 3rem)/1.05 var(--f-display);
  letter-spacing: -0.025em;
  text-wrap: balance;
  /* A long word ("Representation") breaks with a hyphen on a phone instead of pushing the page sideways. */
  hyphens: auto;
  overflow-wrap: break-word;
}
.sign .meta {
  grid-column: 2;
  grid-row: 3;
  margin: 6px 0 0;
  font: 500 0.95rem/1.4 var(--f-display);
  color: var(--on-fau-soft);
}
.sign .meta:empty {
  display: none;
}
@media (max-width: 560px) {
  .sign {
    padding: 22px 18px 24px;
    column-gap: 14px;
  }
  .sign .code {
    width: 48px;
    height: 48px;
    font-size: 1rem;
  }
}

/* Buttons: the only things carrying the action colour */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 18px;
  font: 700 0.95rem/1 var(--f-display);
  text-decoration: none;
  color: var(--act);
  background: transparent;
  border: 2px solid var(--act);
  cursor: pointer;
  transition:
    background-color 150ms var(--ease),
    color 150ms var(--ease);
}
.btn:hover {
  background: var(--fau-tint);
  text-decoration: none;
}
.btn.primary {
  background: var(--act);
  color: var(--act-ink);
}
.btn.primary:hover {
  background: var(--fau-deep);
  color: var(--on-fau);
}
@media (prefers-reduced-motion: no-preference) {
  .btn {
    transition:
      background-color 150ms var(--ease),
      color 150ms var(--ease),
      transform 100ms var(--ease);
  }
  .btn:active:not(:disabled) {
    transform: translateY(1px);
  }
}
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Tick bar: faint fill = not yet, solid = ticked, then the count as a number */
.ticks {
  display: flex;
  gap: 3px;
  align-items: center;
}
.ticks i {
  /* Shrinks toward 3px when a long skill list meets a narrow screen, so the bar never widens the page. */
  flex: 0 1 7px;
  min-width: 3px;
  height: 22px;
  background: currentColor;
  opacity: 0.16;
}
.ticks b {
  margin-left: 10px;
  font: 700 0.9rem/1 var(--f-display);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Guide and lab headers: the bar sits under the meta line, in the sign's own white. */
.sign .ticks {
  grid-column: 2;
  grid-row: 4;
  margin-top: 4px;
  justify-self: start;
}
/* After the first paint a newly ticked segment fills at once, not staggered. */
.ticks.painted i.on {
  animation-delay: 0ms;
}
/* The ticked skill's square on its way to the bar. */
.tick-dot {
  position: fixed;
  top: 0;
  left: 0;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  background: linear-gradient(90deg, var(--signal-a), var(--signal-b));
  pointer-events: none;
  z-index: 60;
}
/* Above the checklist: a bar close to the boxes, so a ticked skill has somewhere to land on screen. */
.list-ticks {
  margin: 4px 0 14px;
}
@media print {
  .sign .ticks,
  .review .ticks,
  .list-ticks,
  .tick-dot {
    display: none;
  }
}
/* Every skill ticked: the chapter is mastered, marked by a signal line under the bar. */
.ticks.mastered {
  padding-bottom: 5px;
  background: var(--signal) left bottom / 100% 2px no-repeat;
}
@media print {
  .ticks.mastered {
    background: none;
    padding-bottom: 0;
  }
}
/* A gap separates the guide's skills from the lab's experiments. */
.ticks i.lab-start {
  margin-left: 7px;
}
.ticks i.on {
  opacity: 1;
}

/* Tick bars fill bottom-up, one bar after another, when progress is drawn. */
@media (prefers-reduced-motion: no-preference) {
  .ticks i.on {
    animation: tick-fill 420ms var(--ease) both;
    animation-delay: calc(var(--i, 0) * 28ms);
  }
}
@keyframes tick-fill {
  from {
    clip-path: inset(100% 0 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

/* Tooltip: a small sign above (or below) the thing it explains. Filled by js/tooltip.js from data-tip. */
.tip {
  position: fixed;
  z-index: 50;
  max-width: 30ch;
  padding: 7px 11px 8px;
  background: var(--fau);
  color: var(--on-fau);
  font: 600 0.82rem/1.35 var(--f-display);
  letter-spacing: 0.005em;
  pointer-events: none;
  opacity: 0;
  transition: opacity 120ms var(--ease);
}
.tip::after {
  content: "";
  position: absolute;
  left: calc(var(--arrow, 50%) - 5px);
  width: 10px;
  height: 10px;
  background: inherit;
  transform: rotate(45deg);
}
.tip[data-side="above"]::after {
  bottom: -5px;
}
.tip[data-side="below"]::after {
  top: -5px;
}
.tip.on {
  opacity: 1;
}
@media (prefers-reduced-motion: no-preference) {
  .tip {
    transition:
      opacity 120ms var(--ease),
      transform 180ms var(--ease);
  }
  .tip[data-side="above"] {
    transform: translateY(4px);
  }
  .tip[data-side="below"] {
    transform: translateY(-4px);
  }
  .tip.on {
    transform: none;
  }
}
[data-tip] {
  cursor: help;
}
a[data-tip],
button[data-tip] {
  cursor: pointer;
}
@media print {
  .tip {
    display: none;
  }
}

/* A ring that widens and fades from a point that is "here": the furthest chapter on the hub, the section in the rail. */
@keyframes beacon {
  0% {
    transform: scale(1);
    opacity: 0.9;
  }
  70%,
  100% {
    transform: scale(2.6);
    opacity: 0;
  }
}
/* Site settings (js/settings.js). Motion "Reduced" stops every animation and transition, whatever the device says;
   "Large" text scales the whole rem scale up one step. */
:root[data-motion="reduced"] *,
:root[data-motion="reduced"] *::before,
:root[data-motion="reduced"] *::after {
  animation: none !important;
  transition: none !important;
}
:root[data-motion="reduced"] {
  scroll-behavior: auto !important;
}
:root[data-motion="reduced"]::view-transition-group(*),
:root[data-motion="reduced"]::view-transition-old(*),
:root[data-motion="reduced"]::view-transition-new(*) {
  animation: none !important;
}
:root[data-text="large"] {
  font-size: 112.5%;
}
.settings-btn {
  display: grid;
  place-items: center;
  flex: none;
  width: 44px;
  min-height: 40px;
  padding: 0;
  border: 2px solid var(--act);
  background: transparent;
  color: var(--act);
  cursor: pointer;
  transition: background-color 150ms var(--ease);
}
.settings-btn:hover {
  background: var(--fau-tint);
}
@media (prefers-reduced-motion: no-preference) {
  .settings-btn svg {
    transition: transform 400ms var(--ease);
  }
  .settings-btn:hover svg {
    transform: rotate(60deg);
  }
}
dialog.settings {
  width: min(420px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  padding: 24px;
  border: 0;
  border-top: 4px solid var(--fau-mark);
  background: var(--surface);
  color: var(--ink);
}
dialog.settings::backdrop {
  background: rgb(2 16 36 / 0.55);
}
dialog.settings h2 {
  margin: 0 0 12px;
  font: 800 1.35rem/1.2 var(--f-display);
}
dialog.settings fieldset {
  margin: 0 0 16px;
  padding: 0;
  border: 0;
}
dialog.settings legend {
  margin-bottom: 8px;
  padding: 0;
  font: 700 0.95rem/1.2 var(--f-display);
}
dialog.settings .seg {
  display: flex;
  border: 2px solid var(--act);
}
dialog.settings .seg label {
  flex: 1;
  position: relative;
}
dialog.settings .seg input {
  position: absolute;
  opacity: 0;
  inset: 0;
  margin: 0;
  cursor: pointer;
}
dialog.settings .seg span {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 8px;
  color: var(--act);
  font: 700 0.95rem/1.2 var(--f-display);
  transition: background-color 150ms var(--ease), color 150ms var(--ease);
}
dialog.settings .seg label + label span {
  border-left: 2px solid var(--act);
}
dialog.settings .seg input:checked + span {
  background: var(--act);
  color: var(--act-ink);
}
dialog.settings .seg input:focus-visible + span {
  outline: 3px solid var(--fau-mark);
  outline-offset: -6px;
}
dialog.settings .seg label:hover input:not(:checked) + span {
  background: var(--fau-tint);
}
.settings-note {
  margin: 0 0 16px;
  color: var(--muted);
  font-size: 0.9rem;
}
dialog.settings .btn {
  width: 100%;
}
@media (prefers-reduced-motion: no-preference) {
  dialog.settings[open] {
    animation: settings-in 220ms var(--ease) both;
  }
}
@keyframes settings-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}
@media print {
  .settings-btn,
  dialog.settings {
    display: none !important;
  }
}

/* Reduced motion: no movement and no smooth scrolling; colour and state changes stay. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto !important;
  }
}

/* Motion layer 2: pages hand over to each other, reading shows its progress, content arrives as it is reached.
   Every effect starts from visible content, so a browser without support simply shows the page. */
@view-transition {
  navigation: auto;
}
nav.toc {
  view-transition-name: rail;
}
.chapter-switch {
  view-transition-name: switch;
}
/* The sweep: the new page is uncovered top to bottom while a 2px signal line rides the edge. Cross-document transitions
   give no script hook, so the line is a fixed layer parked just above the viewport on every page. It has its own
   transition name, and only its group animation (below) ever carries it into view. */
html::before {
  content: "";
  position: fixed;
  top: -2px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--signal);
  pointer-events: none;
  view-transition-name: sweep;
}
:root {
  /* the shared ease is too front-loaded here: the line would be gone before it could be seen */
  --sweep-ease: cubic-bezier(0.45, 0, 0.25, 1);
}
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) {
    animation: vt-out 200ms var(--ease) both;
  }
  ::view-transition-new(root) {
    animation: vt-sweep 420ms var(--sweep-ease) both;
  }
  ::view-transition-group(sweep) {
    animation: vt-line 420ms var(--sweep-ease) both;
  }
  ::view-transition-old(sweep) {
    display: none;
  }
  ::view-transition-group(switch),
  ::view-transition-group(rail) {
    animation-duration: 320ms;
    animation-timing-function: var(--ease);
  }
}
@media print {
  html::before {
    display: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 120ms;
  }
}
@keyframes vt-out {
  to {
    opacity: 0;
  }
}
@keyframes vt-sweep {
  from {
    clip-path: inset(0 0 100% 0);
  }
  to {
    clip-path: inset(0);
  }
}
@keyframes vt-line {
  from {
    transform: translateY(2px);
  }
  to {
    transform: translateY(calc(100vh + 2px));
  }
}

/* Reading progress: a thin bar in the subject colour fills with the page scroll. */
@supports (animation-timeline: scroll()) {
  body:has(nav.toc)::after {
    content: "";
    position: fixed;
    inset: 0 0 auto 0;
    height: 4px;
    background: var(--signal);
    transform-origin: 0 50%;
    transform: scaleX(0);
    animation: read-progress linear both;
    animation-timeline: scroll(root block);
    z-index: 50;
    pointer-events: none;
  }
  @media print {
    body:has(nav.toc)::after {
      display: none;
    }
  }
}
@keyframes read-progress {
  to {
    transform: scaleX(1);
  }
}

/* Content arrives as it is scrolled to: sections and lab experiments rise and clear in, their sign square grows. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (not print) {
    main > section,
    main .experiment {
      animation: arrive linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 40%;
    }
    main > section > h2::before {
      animation: sign-grow linear both;
      animation-timeline: view();
      animation-range: entry 10% entry 60%;
    }
  }
}
@keyframes arrive {
  from {
    opacity: 0.15;
    transform: translateY(28px);
    clip-path: inset(0 0 12% 0);
  }
}
@keyframes sign-grow {
  from {
    transform: translateY(-1px) scale(0.2) rotate(-45deg);
  }
}

/* Hub: each subject sets down as it scrolls into view, its chapter rows following in order. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (not print) {
    .subjects > .subject {
      animation: arrive linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 50%;
    }
    .subjects .chapter {
      animation: chapter-in linear both;
      animation-timeline: view();
      animation-range: entry calc(var(--c, 0) * 6%) entry calc(60% + var(--c, 0) * 6%);
    }
  }
}
@keyframes chapter-in {
  from {
    opacity: 0;
    transform: translateX(-14px);
  }
}

/* Soft glow on the signal lines (owner's request, 2026-11-04: lines may now glow). */
html::before,
.sweep-line,
.subjects::after,
.route-pulse::before,
.chapter.here::after,
nav.toc > ol::after,
nav.toc > ol li a[aria-current]::before,
nav.toc > ol li a[aria-current]::after,
body:has(nav.toc)::after,
.tick-dot {
  box-shadow: var(--glow);
}
@media print {
  html::before,
  nav.toc > ol::after,
  .tick-dot {
    box-shadow: none;
  }
}

/* Heading decode (js/hud.js): the 2px signal line that sweeps across a heading while its letters settle. */
.decode-line {
  position: absolute;
  z-index: 5;
  width: 2px;
  margin-left: -1px;
  background: linear-gradient(to bottom, var(--signal-a), var(--signal-b));
  box-shadow: var(--glow);
  pointer-events: none;
}
/* Site pages (How to use, Version log) take the signal green as their tile colour. */
.s-site {
  --course: var(--signal-b);
}
.sign .code:empty {
  display: none;
}
/* Links to How to use and the Version log, under the settings choices. */
.settings-links {
  display: flex;
  gap: 18px;
  /* Same space above (after the note's 16px) as below, before Done. */
  margin: 0 0 16px;
  font-weight: 600;
}
.settings-links a {
  color: var(--link);
}
@media print {
  .decode-line {
    display: none;
  }
}
/* The settings heading sits under the panel's own 4px rule, so it drops the page headings' top rule. */
dialog.settings h2 {
  border-top: 0;
  padding-top: 0;
}
