/*
 * styles.css, the only stylesheet. Page.fs emits the class names used here.
 * Theme comes from data-theme (the toggle) or the system setting.
 */

:root {
  --desk: #e4e7eb;                      /* page behind the sheet, cool gray */
  --paper: #fafbfc;                     /* the sheet */
  --ink: #111317;                       /* body text */
  --muted: #6b717c;                     /* labels, captions, numbers */
  --rule: #d6d9df;                      /* hairlines */
  --accent: #1a66ff;                    /* electric blue, links, progress, figures */
  --code-string: #1f7a52;               /* listing strings */
  --code-number: #b4541b;               /* listing numbers and constants */
  --code-type: #7a3fc4;                 /* listing class and type names */
  --text: "CMU Serif", "Latin Modern Roman", Georgia, serif;              /* Computer Modern, the LaTeX face */
  --mono: "CMU Typewriter Text", "Latin Modern Mono", Consolas, monospace;
  --size: 1.1875rem;                    /* body size, larger than usual since CMU sets small */
  --heading-weight: 700;
  --measure: 40rem;                     /* text column inside the sheet */
  --figure: min(1060px, calc(100vw - 32px));   /* sheet and map width, the 2 always match */
}

/* with the rail showing, the sheet stops short of it on both sides so they never overlap.
   The rail is pinned 24px from the left and is 12.5rem wide, so the sheet gives up
   that much plus a 24px gap on each side. Change the rail and change this */
@media (min-width: 1240px) {
  :root { --figure: min(1060px, calc(100vw - 96px - 25rem)); }
}

/* dark values live twice, once for the system setting unless the toggle said light,
   once for the toggle saying dark. Change one block and change the other */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --desk: #090a0d;
    --paper: #12151a;
    --ink: #e4e7ec;
    --muted: #8b929e;
    --rule: #262a31;
    --accent: #3d8bff;
    --code-string: #7cc9a0;
    --code-number: #e59a66;
    --code-type: #b994f0;
  }
}

:root[data-theme="dark"] {
  --desk: #090a0d;
  --paper: #12151a;
  --ink: #e4e7ec;
  --muted: #8b929e;
  --rule: #262a31;
  --accent: #3d8bff;
  --code-string: #7cc9a0;
  --code-number: #e59a66;
  --code-type: #b994f0;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--desk);
  color: var(--ink);
  font-family: var(--text);
  font-size: var(--size);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  counter-reset: table figure listing equation;
}

::selection { background: var(--accent); color: var(--paper); }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

/*=======================================================
        FIXED CHROME, PROGRESS, TOGGLE, CONTENTS RAIL
========================================================*/

#progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  z-index: 10;
}

.theme-toggle {
  position: fixed;
  top: 14px;
  right: 16px;
  z-index: 11;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 200ms ease, color 200ms ease;
}
.theme-toggle:hover { border-color: var(--accent); color: var(--accent); }
.theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.theme-toggle svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* moon offers dark from light, sun offers light from dark, same 2 selectors as the palette */
.icon-sun { display: none; }
:root[data-theme="dark"] .icon-sun { display: block; }
:root[data-theme="dark"] .icon-moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .icon-sun { display: block; }
  :root:not([data-theme="light"]) .icon-moon { display: none; }
}

.rail { display: none; }

@media (min-width: 1240px) {
  .rail {
    display: block;
    position: fixed;
    top: 50%;
    left: 24px;                         /* pinned, the wide map figure is sized around it */
    transform: translateY(-50%);
    width: 12.5rem;
  }
  .rail ol { list-style: none; margin: 0; padding: 0; }
  .rail a {
    display: flex;
    gap: 0.75rem;
    padding: 0.28rem 0;
    font-family: var(--mono);
    font-size: 0.75rem;
    color: var(--muted);
    transition: color 200ms ease;
  }
  .rail a:hover { color: var(--ink); text-decoration: none; }
  .rail a.active { color: var(--ink); }
  .rail a.active .num { color: var(--accent); }
}

/*=======================================================
        THE SHEET AND TITLE BLOCK
========================================================*/

/* the sheet is exactly as wide as the map. On a phone both fill the screen */
main {
  width: var(--figure);
  margin: 0 auto;
  padding: 5rem 16px 6rem;
  background: var(--paper);
}

@media (min-width: 760px) {
  main {
    margin: 4.5rem auto 6rem;
    padding: 5.5rem 4.5rem 6rem;
    border: 1px solid var(--rule);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 12px 40px rgba(0, 0, 0, 0.06);
  }
}

/* text keeps its reading width, centred in the wider sheet. The map is the only thing
   that spans the sheet, and its own centring rule puts it edge to edge */
main > .hero,
main .chapter > *:not(.map-scroll):not(.equation) {
  max-width: var(--measure);
  margin-left: auto;
  margin-right: auto;
}

@media (max-width: 759px) {
  main { width: 100%; }
}

.num {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.hero { text-align: center; padding-bottom: 2.5rem; }

.hero h1 {
  margin: 0;
  font-size: clamp(2.1rem, 6vw, 2.75rem);
  font-weight: var(--heading-weight);
  line-height: 1.1;
}

.role {
  margin: 0.9rem 0 2.75rem;
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--muted);
}

/* app screenshot under a paper title, sheet width, framed like a figure */
.preview { margin: 1.5rem 0 0.5rem; }
.preview img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
  border-radius: 6px;
  cursor: zoom-in;
}
/* expanded, the figure becomes a dimmed full window layer with the image fitted inside */
.preview.open {
  position: fixed;
  inset: 0;
  z-index: 100;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.85);
  cursor: zoom-out;
}
.preview.open img {
  width: auto;
  max-width: 100%;
  max-height: 100%;
  cursor: zoom-out;
}

.abstract {
  margin: 0 auto;
  max-width: 34rem;
  padding: 1.25rem 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  font-size: 0.94em;
  line-height: 1.6;
  text-align: left;
}
.abstract h2 {
  margin: 0 0 0.6rem;
  font-size: 0.95em;
  font-weight: var(--heading-weight);
  text-align: center;
}
.abstract p { margin: 0; }

.role a { color: var(--ink); }
.work h3 a { color: var(--accent); }
.abstract p + p { text-indent: 1.5em; }

/*=======================================================
        SECTIONS AND BODY TEXT
========================================================*/

.chapter {
  padding-top: 3rem;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 600ms ease, transform 600ms ease;
}
.chapter.in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .chapter { opacity: 1; transform: none; transition: none; }
}

.chapter > header {
  display: flex;
  align-items: baseline;
  gap: 0.9rem;
  margin-bottom: 0.9rem;
}

.chapter > header .num {
  font-family: var(--text);
  font-weight: var(--heading-weight);
  font-size: 1.3rem;
  color: var(--accent);
}

/* hint beside a heading, fades in while its section is hovered. Touch screens
   cannot hover, so there it is always shown */
.section-hint {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--accent);
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity 250ms ease, transform 250ms ease;
}
.chapter:hover .section-hint { opacity: 1; transform: none; }
@media (hover: none) {
  .section-hint { opacity: 1; transform: none; }
}

.chapter h2 {
  margin: 0;
  font-size: 1.3rem;
  font-weight: var(--heading-weight);
  line-height: 1.25;
}

/* paper paragraphs, no gap between them, first line indent on every one after the first */
.chapter p { margin: 0; }
.chapter p + p { text-indent: 1.5em; }

@media (min-width: 760px) {
  .chapter p, .abstract p { text-align: justify; text-wrap: pretty; }
}

/*=======================================================
        FIGURES, TABLES, REFERENCES, LINKS
========================================================*/

.stat {
  margin: 1.75rem 0;
  text-align: center;
  counter-increment: figure;
}

.stat-figure {
  font-size: clamp(2.2rem, 7vw, 3rem);
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.stat figcaption {
  margin: 0.7rem auto 0;
  max-width: 28rem;
  font-size: 0.88em;
  line-height: 1.5;
  color: var(--muted);
}

.stat figcaption::before {
  content: "Figure " counter(figure) ". ";
  font-weight: var(--heading-weight);
  color: var(--ink);
}

/* code listings, numbered like a paper's code figures. Long lines scroll inside the
   listing so the page never scrolls sideways */
.listing {
  margin: 1.5rem 0 1.75rem;
  counter-increment: listing;
}

.listing figcaption {
  margin-bottom: 0.5rem;
  font-size: 0.88em;
  line-height: 1.5;
  color: var(--muted);
}

.listing figcaption::before {
  content: "Listing " counter(listing) ". ";
  font-weight: var(--heading-weight);
  color: var(--ink);
}

.listing pre {
  margin: 0;
  padding: 0.9rem 1rem;
  overflow-x: auto;
  background: var(--desk);
  border-top: 1.5px solid var(--ink);
  border-bottom: 1.5px solid var(--ink);
  font-family: var(--mono);
  font-size: 0.8rem;
  line-height: 1.55;
  tab-size: 4;
}

/* display equations, centred, numbered (N) on the right like a paper. Wide ones scroll
   inside their own box so the page never scrolls sideways */
.equation {
  position: relative;
  margin: 1.25rem 0;
  padding: 0.75rem 3rem;                /* room above and below for tall fractions and limits */
  overflow-x: auto;
  overflow-y: hidden;                   /* the padding holds the tall parts, so no vertical scrollbar */
  counter-increment: equation;
  color: var(--ink);
}
.equation::after {
  content: "(" counter(equation) ")";
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--text);
  color: var(--muted);
}
.equation .katex-display { margin: 0; }
.equation .katex { font-size: 1.05em; }   /* KaTeX defaults to 1.21em, too wide for the longer equations */
.equation > code { display: block; text-align: center; font-family: var(--mono); font-size: 0.85rem; color: var(--muted); }

/* Prism token colours, drawn from the palette so they follow the theme */
.listing .token.comment,
.listing .token.prolog,
.listing .token.triple-quoted-string { color: var(--muted); font-style: italic; }
.listing .token.keyword,
.listing .token.builtin,
.listing .token.important { color: var(--accent); }
.listing .token.string,
.listing .token.char,
.listing .token.template-string,
.listing .token.regex { color: var(--code-string); }
.listing .token.number,
.listing .token.boolean,
.listing .token.constant { color: var(--code-number); }
.listing .token.class-name,
.listing .token.annotation,
.listing .token.decorator { color: var(--code-type); }
.listing .token.function { color: var(--ink); font-weight: 700; }
.listing .token.operator,
.listing .token.punctuation { color: var(--muted); }

/* booktabs, a heavy rule top and bottom, a light one under the header, no verticals */
table {
  width: 100%;
  margin: 1.5rem 0 2rem;
  border-collapse: collapse;
  border-top: 1.5px solid var(--ink);
  border-bottom: 1.5px solid var(--ink);
  font-size: 0.92em;
  line-height: 1.45;
  counter-increment: table;
}

caption {
  caption-side: top;
  padding-bottom: 0.6rem;
  font-size: 0.92em;
  text-align: center;
}
caption::before {
  content: "Table " counter(table) ".";
  font-weight: var(--heading-weight);
}

th {
  text-align: left;
  font-weight: var(--heading-weight);
  padding: 0.5rem 1rem 0.45rem 0;
  border-bottom: 1px solid var(--ink);
}

td {
  padding: 0.4rem 1rem 0.4rem 0;
  vertical-align: top;
}

td:first-child { white-space: nowrap; }

/* project map, scrolls sideways inside its box on narrow screens instead of shrinking
   the labels past reading size. The page itself never scrolls sideways */
.map-scroll {
  width: var(--figure);
  margin: 1rem 0 0 calc(50% - var(--figure) / 2);
  position: relative;
  overflow-x: auto;
  background: var(--paper);
  border: 1px solid var(--rule);
}

/* legend strip under the map */
.map-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 2rem;
  padding: 0.85rem 1.25rem;
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
}
.legend-item { display: flex; align-items: center; gap: 0.6rem; }
.legend-bubbles { height: 22px; width: auto; }
.legend-bubbles circle { fill: var(--paper); stroke: var(--muted); stroke-width: 1.5; }
.legend-ramp {
  display: inline-block;
  width: 7rem;
  height: 0.6rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
}
.legend-note { margin-left: auto; }

/* hover card, placed by Main.fs beside the hovered node */
.map-card {
  position: absolute;
  z-index: 2;
  width: 17rem;
  padding: 0.75rem 0.9rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-left: 2px solid var(--accent);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.12);
  pointer-events: none;
  font-size: 0.85rem;
  line-height: 1.45;
}
.map-card[hidden] { display: none; }
.map-card-title { display: block; font-weight: var(--heading-weight); }
.map-card-stack { display: block; margin: 0.1rem 0 0.4rem; font-family: var(--mono); font-size: 0.75rem; color: var(--accent); }
.map-card-summary { margin: 0; color: var(--muted); }


.map {
  display: block;
  width: 100%;
  min-width: 720px;
  height: auto;
}

.map .edge {
  stroke: var(--rule);
  stroke-width: 1.2;
  fill: none;
  transition: opacity 250ms ease, stroke 250ms ease;
}

.map .iteration {
  stroke: var(--accent);
  stroke-dasharray: 5 5;
  opacity: 0.7;
}

.map marker path { fill: var(--accent); }

.map .node { transition: opacity 250ms ease; }

.map .dot {
  fill: var(--paper);
  stroke: var(--muted);
  stroke-width: 1.5;
  transition: fill 200ms ease, stroke 200ms ease;
}

.map .runtime .dot { fill: var(--ink); stroke: var(--ink); }
.map .has-paper .dot { fill: var(--accent); stroke: var(--accent); }
/* leaves are filled by heat inline. The outline keeps a white, silent leaf visible on
   the light sheet, blue when the leaf opens a paper, gray when it does not */
.map .project .dot { stroke: var(--muted); stroke-width: 1.5; }
.map .project.has-paper .dot { stroke: var(--accent); stroke-width: 2; }
.map .center .dot { stroke-width: 0; }

/* every label gets a halo in the sheet colour, so a line passing behind never strikes through it */
.map text {
  fill: var(--ink);
  font-family: var(--text);
  paint-order: stroke;
  stroke: var(--paper);
  stroke-width: 5px;
  stroke-linejoin: round;
}

.map { user-select: none; -webkit-user-select: none; }
/* a pointer says click. The grab cursor only shows once a press has become a drag */
.map .project { cursor: pointer; touch-action: none; }
.map.is-dragging, .map.is-dragging * { cursor: grabbing; }

/* the arrow on every label that opens a paper */
.map .go { fill: var(--accent); font-family: var(--text); }
.map .sub { font-size: 13px; fill: var(--muted); }

/* dots grow from their own centre on hover, not from the corner of the map */
.map .dot { transform-box: fill-box; transform-origin: center; }
.map .has-paper .dot { transition: transform 200ms ease, fill 200ms ease; }
.map .has-paper:hover .dot { transform: scale(1.55); }

/* the first time the map scrolls into view, the paper nodes pulse once in turn */
.chapter.in .map .project.has-paper .dot { animation: beckon 900ms ease 1.2s 1 both; }
.chapter.in .map .project.has-paper:nth-of-type(2n) .dot { animation-delay: 1.45s; }
.chapter.in .map .project.has-paper:nth-of-type(3n) .dot { animation-delay: 1.7s; }

@keyframes beckon {
  0%, 100% { transform: scale(1); }
  45% { transform: scale(1.9); }
}

@media (prefers-reduced-motion: reduce) {
  .chapter.in .map .project.has-paper .dot { animation: none; }
}
.map .runtime { cursor: pointer; }
.map .runtime .dot { transition: r 200ms ease, stroke 200ms ease; }

/* the focused branch node gets a blue ring, 1 rule per runtime */
.map[data-focus="python"] .runtime[data-runtime="python"] .dot,
.map[data-focus="dotnet"] .runtime[data-runtime="dotnet"] .dot,
.map[data-focus="node"] .runtime[data-runtime="node"] .dot { stroke: var(--accent); stroke-width: 4; }
.map .node-label { font-size: 16.5px; }
.map .runtime-label { font-family: var(--mono); font-size: 15px; fill: var(--muted); }
.map .center-label { font-size: 20px; font-weight: var(--heading-weight); }
.map .project:not(.has-paper) .node-label { fill: var(--muted); }

.map a { cursor: pointer; }
.map a:hover .dot { fill: var(--paper); }
.map .center a:hover .dot { fill: var(--accent); }
.map a:hover text { fill: var(--accent); text-decoration: underline; }

/* slow ring around the hub */
.map .pulse {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.2;
  transform-box: fill-box;              /* scale from the ring's own centre, wherever the root sits */
  transform-origin: center;
  animation: hub 3.2s ease-out infinite;
}

@keyframes hub {
  from { transform: scale(1); opacity: 0.7; }
  to { transform: scale(2.6); opacity: 0; }
}

/* hovering a node dims every branch that is not its own, 1 rule per runtime */
.map[data-active="python"] [data-runtime]:not([data-runtime~="python"]),
.map[data-active="dotnet"] [data-runtime]:not([data-runtime~="dotnet"]),
.map[data-active="node"] [data-runtime]:not([data-runtime~="node"]) { opacity: 0.18; }

.map[data-active] .edge[data-runtime] { stroke: var(--accent); }
.map[data-active] .iteration { opacity: 0.9; }

/* edges draw in the first time the section scrolls into view */
.chapter .map .edge:not(.iteration) { stroke-dasharray: 700; stroke-dashoffset: 700; }
.chapter.in .map .edge:not(.iteration) { animation: draw 1.1s ease forwards; }

@keyframes draw { to { stroke-dashoffset: 0; } }

@media (prefers-reduced-motion: reduce) {
  .map .pulse { animation: none; opacity: 0; }
  .chapter .map .edge:not(.iteration) { stroke-dasharray: none; stroke-dashoffset: 0; animation: none; }
}

/* the project list reads as a numbered reference list with a hanging label */
.work { list-style: none; margin: 0.5rem 0 0; padding: 0; counter-reset: work; }

.work li {
  position: relative;
  padding: 0 0 0.9rem 2.6rem;
  counter-increment: work;
}

.work li::before {
  content: "[" counter(work) "]";
  position: absolute;
  left: 0;
  top: 0.05rem;
  font-family: var(--mono);
  font-size: 0.85em;
  color: var(--accent);
}

.work h3 { display: inline; margin: 0; font-size: 1em; font-weight: var(--heading-weight); }
.work h3::after { content: ". "; }
.work-stack { display: inline; margin: 0; font-style: italic; color: var(--muted); }
.work-stack::after { content: "."; }
.work p:last-child { margin: 0.15rem 0 0; text-indent: 0; }

.links { list-style: none; margin: 0.5rem 0 1rem; padding: 0; }
.links li { display: flex; gap: 1.5rem; padding: 0.25rem 0; }

.link-label {
  width: 4.5rem;
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--muted);
  padding-top: 0.2rem;
}

@media (max-width: 520px) {
  body { font-size: calc(var(--size) - 0.0625rem); }
  td:first-child { white-space: normal; }
}
