/* Xiaole (Alex) Jiang — one stylesheet, no framework.
   Wide: photo | list | write-up.  Narrower: photo | list, with write-ups
   opening inline under their entry. Then one column; phones add a sticky
   section navigation. */

@font-face { font-family: "Charter"; src: url("../fonts/charter-regular.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Charter"; src: url("../fonts/charter-italic.woff2") format("woff2"); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: "Charter"; src: url("../fonts/charter-bold.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Charter"; src: url("../fonts/charter-bold-italic.woff2") format("woff2"); font-weight: 700; font-style: italic; font-display: swap; }

:root {
  /* colors: light (the dark theme is further down) */
  color-scheme: light;
  --bg: #f2efe9;
  --text: #111;
  --gray: #5a5a5a;
  --faint: #8a8a8a;
  --dots: #777;
  --link: #2b4f81;       /* links stay this color once visited too */
  --title: #1d5663;      /* paper titles: teal */
  --red: #a83a2e;        /* brick, not signal red: quieter beside the walnut */
  --box: #eae7e1;        /* entry with a write-up */
  --box-hover: #e3e1db;
  --box-open: #dddbd5;
  --code-bg: #eae7e1;
  --photo-bg: #e3e1db;
  --rule: 1px dotted #888;
  /* the background's particles and antiparticles (assets/js/qft.js) */
  --field-plus: #63452c;   /* walnut */
  --field-minus: #002b36;  /* deep slate */
  --creation-cursor: url("../img/creation-cursor.svg?v=plain-math-1") 8 7, default;
  --glass: 55%;          /* how much of the page colour the text's frosted ground has */
  --frost: 6px;          /* how far the ground is blurred; readability comes from both */

  /* Reading scale; keep the root size independent of layout dimensions. */
  --type-small: 1rem;
  --type-body: 1.125rem;
  --type-heading: 1.25rem;

  /* Column widths. The list is --list wide, or the screen less --phone-pad on
     each side when that is narrower; wider windows never stretch it. The photo
     column outranks the write-up: it stays whenever it fits beside the list,
     and the write-up gets a column of its own only when all three fit.
     Otherwise it opens inside its entry. So the breakpoints are:
       phone below:      list + 2*phone-pad                           = 36rem
       photo column:     2*pad + side + colgap + list                 = 57rem
       write-up column:  2*pad + side + colgap + list + colgap + read = 109rem
     Media queries can't use variables, so these numbers are written out there
     and in site.js; keep them in sync. (Media-query em is the browser's default
     font size, the same as rem here since html sets no font size.) */
  --side: 11rem;
  --list: 34rem;
  --read: 46rem;
  --colgap: 6rem;        /* 2rem of clear space between the frosted panes */
  --pad: 3rem;           /* outer margin, at least */
  --top: 3.5rem;         /* space above the columns */
  --phone-pad: 1rem;
  --bleed: 0.7rem;       /* how far gray boxes and dot leaders reach past the text */
}

/* dark: Midnight. It follows the system setting unless the [night]/[day]
   switch has set data-theme (site.js); screen only, so printing stays
   dark-on-white. The two blocks are the same; keep them in sync. */
@media screen and (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #0e1219;
    --text: #e3e5e8;
    --gray: #a9a59c;
    --faint: #7d7a73;
    --dots: #6c6a65;
    --link: #9fc6ee;
    --title: #8fc9d8;
    --red: #dc9a8e;
    --box: #171c25;
    --box-hover: #1d232d;
    --box-open: #232a35;
    --code-bg: #171c25;
    --photo-bg: #1d232d;
    --rule: 1px dotted #5c5f66;
    --field-plus: #f2b27a;
    --field-minus: #8cc2ea;
    --creation-cursor: url("../img/creation-cursor-dark.svg?v=plain-math-1") 8 7, default;
  }
}
@media screen {
  :root[data-theme="dark"] {
    color-scheme: dark;
    --bg: #0e1219;
    --text: #e3e5e8;
    --gray: #a9a59c;
    --faint: #7d7a73;
    --dots: #6c6a65;
    --link: #9fc6ee;
    --title: #8fc9d8;
    --red: #dc9a8e;
    --box: #171c25;
    --box-hover: #1d232d;
    --box-open: #232a35;
    --code-bg: #171c25;
    --photo-bg: #1d232d;
    --rule: 1px dotted #5c5f66;
    --field-plus: #f2b27a;
    --field-minus: #8cc2ea;
    --creation-cursor: url("../img/creation-cursor-dark.svg?v=plain-math-1") 8 7, default;
  }
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Charter", "Bitstream Charter", Georgia, serif;
  font-size: var(--type-body);
  line-height: 1.55;
  font-kerning: normal;
  overflow-wrap: break-word;
}

/* Background: a slowly rising slice of a loop soup (assets/js/qft.js). */
#qft { position: fixed; inset: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none; }
/* Resting on the background reveals a small creation-operator cursor.
   Its hotspot is the crossbar intersection, so clicks stay precise. */
@media screen and (min-width: 36em) {
  .creation-ready { cursor: var(--creation-cursor); }
}
/* The text columns sit on frosted glass: a see-through, blurred pane whose
   edge feathers into the background. Browsers without color-mix get it
   opaque. Phones don't draw the background, so they need none, and below
   109em the write-up opens inside the list, on its pane. */
:root {
  --feather: 1.75rem;    /* how far the pane fades in from its edge */
  --edge-x: linear-gradient(to right, transparent, #000 var(--feather), #000 calc(100% - var(--feather)), transparent);
  --edge-y: linear-gradient(to bottom, transparent, #000 var(--feather), #000 calc(100% - var(--feather)), transparent);
  --edge: var(--edge-x), var(--edge-y);
}
@media (min-width: 36em) {
  :root { --phone-pad: 2rem; }   /* the panes reach 2rem past the text; room for that */
  .me, .list { z-index: 0; }
  .list { position: relative; }
  .me::before, .list::before {
    content: ""; position: absolute; inset: -2rem; z-index: -1; pointer-events: none;
    background: var(--bg);
    background: color-mix(in srgb, var(--bg) var(--glass), transparent);
    -webkit-backdrop-filter: blur(var(--frost)); backdrop-filter: blur(var(--frost));
    -webkit-mask-image: var(--edge); -webkit-mask-composite: source-in;
    mask-image: var(--edge); mask-composite: intersect;
  }
}
/* stacked (36em to 57em): the name block and the list share one pane, joined without a seam */
@media (min-width: 36em) and (max-width: 56.99em) {
  .me::before { bottom: 0; --edge: var(--edge-x), linear-gradient(to bottom, transparent, #000 var(--feather)); }
  .list::before { top: 0; --edge: var(--edge-x), linear-gradient(to top, transparent, #000 var(--feather)); }
}
@media (min-width: 109em) {
  .reader.is-open::before {
    content: ""; position: absolute; z-index: -1; pointer-events: none;
    /* a feather past the box on three sides; on the right the fade starts at the bar */
    inset: calc(-1 * var(--feather)) calc(var(--scrollbar, 0px) - var(--feather)) calc(-1 * var(--feather)) calc(-1 * var(--feather));
    background: var(--bg);
    background: color-mix(in srgb, var(--bg) var(--glass), transparent);
    -webkit-backdrop-filter: blur(var(--frost)); backdrop-filter: blur(var(--frost));
    -webkit-mask-image: var(--edge); -webkit-mask-composite: source-in;
    mask-image: var(--edge); mask-composite: intersect;
  }
}
/* Safari < 18 (prefixed property only) doesn't blur behind a masked element,
   so the blur moves to an unmasked layer that ends where the feather starts. */
@supports (-webkit-backdrop-filter: none) and (not (backdrop-filter: none)) {
  .me::before, .list::before, .reader.is-open::before { -webkit-backdrop-filter: none; backdrop-filter: none; }
  @media (min-width: 36em) {
    .me::after, .list::after, .reader.is-open::after {
      content: ""; position: absolute; z-index: -1; pointer-events: none;
      -webkit-backdrop-filter: blur(var(--frost)); backdrop-filter: blur(var(--frost));
    }
    .me::after, .list::after { inset: calc(var(--feather) - 2rem); }
  }
  @media (min-width: 36em) and (max-width: 56.99em) {
    .me::after { bottom: 0; }
    .list::after { top: 0; }
  }
  @media (min-width: 109em) {
    .reader.is-open::after { inset: 0 var(--scrollbar, 0px) 0 0; }
  }
  @media not all and (min-width: 109em) {
    .reader.is-open::after { content: none; }
  }
}

a { color: var(--link); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.16em; }
a:visited { color: var(--link); }
/* Underlined text goes somewhere; gray [bracketed] text acts on this page. */
.bibbtn, .copybtn, .switch, .tab, .x .close { color: var(--gray); text-decoration: none; text-decoration-thickness: 1px; text-underline-offset: 0.16em; }
.bibbtn:hover, .copybtn:hover, .switch:hover, .tab:hover, .x .close:hover { color: var(--text); text-decoration-line: underline; }
a:focus-visible, button:focus-visible, summary:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }

h1, h2 { font-weight: 700; line-height: 1.25; }
img { max-width: 100%; height: auto; }

/* ---------- three columns (≥ 109em): photo | list | write-up ---------- */

/* The columns sit centered as a group; only the write-up column grows. */
.page {
  display: grid;
  grid-template-columns: var(--side) var(--list) minmax(0, var(--read));
  justify-content: center;
  column-gap: var(--colgap);
  padding: 0 var(--pad);
}

/* left column: stays put. The space above is margin, not padding, so the
   column's frosted pane starts 2rem above the photo, as it does at the sides. */
.me {
  position: sticky;
  top: var(--top);
  align-self: start;
  margin: var(--top) 0 0;
}
.mobile-nav { display: none; }
.me .photo { display: block; width: var(--side); aspect-ratio: 1; object-fit: cover; background: var(--photo-bg); }
.me .photo[hidden] { display: none; }   /* the photos not shown (site.js) */
.me h1 { font-size: 1.9rem; font-weight: 400; line-height: 1.15; margin: 1.1rem 0 0.4rem; }
.me h1 .aka { display: block; font-size: var(--type-heading); line-height: 1.4; margin-top: 0.15rem; }
.me h1 .zh { letter-spacing: 0.08em; }
.me .tag { margin: 0 0 1.1rem; color: var(--gray); font-size: var(--type-small); line-height: 1.5; }
.contact a { display: block; width: max-content; }
.contact-inline { display: none; }   /* phone-only copy of the contact links */
/* X goes by its logo. Text decoration skips an SVG, so the logo carries its own
   underline: a border 0.14em below the baseline, where the text links' sits. */
.xlink { text-decoration: none; }
.xlink svg { box-sizing: content-box; width: 0.68em; height: 0.68em; fill: currentColor; padding-bottom: 0.14em; border-bottom: 1px solid; vertical-align: calc(-0.14em - 1px); }

/* [night] [hide text] [play]: they need JS, so they show only with it.
   Below 36em the background isn't drawn, so there is nothing to show without
   the text and nothing to animate. [play] is for those whose system asks
   for reduced motion, which holds the background still. */
.switches, .unbare, .motion-switch { display: none; }
.js .me .switches { display: block; margin: 0.9rem 0 0; color: var(--gray); font-size: var(--type-small); white-space: nowrap; }
.switch-item { white-space: nowrap; }
@media (prefers-reduced-motion: reduce) and (min-width: 36em) { .motion-switch { display: inline; } }
@media not all and (min-width: 36em) { .bare-switch { display: none; } }

/* hide text: only the background stays, and a [show text] in the corner. The
   text fades out, then turns invisible; it comes back visible at once, so
   focus can return to it. Only where the background is drawn: narrower, the
   text stays whatever the switch says. */
.page { transition: opacity 0.25s; }
@media (min-width: 36em) {
  .bare .page { opacity: 0; visibility: hidden; transition: opacity 0.25s, visibility 0s 0.25s; }
  .bare .unbare {
    display: block; position: fixed; left: 1rem; bottom: 0.75rem; z-index: 5;
    font-size: var(--type-small);
  }
}
.unbare::before { content: "["; color: var(--gray); }
.unbare::after { content: "]"; color: var(--gray); }

/* middle column: about + paper lists */
/* The space above is margin, and there is none below, so the column's frosted
   pane reaches 2rem past the first and last lines, as it does at the sides;
   it sits at the row's start, so it never stretches to the screen bottom. */
.list { margin: var(--top) 0 0; min-width: 0; align-self: start; }
.about { margin: 0 0 2rem; }
.about p { margin: 0 0 1em; }
.about p:last-child { margin-bottom: 0; }

/* Section titles read like a table of contents: ▾ Title . . . . . . up to the box edge */
.sec { margin: 0 0 1.75rem; }
.sec:last-child { margin-bottom: 0; }
.sec > summary {
  display: flex; align-items: baseline; gap: 0.45rem;
  list-style: none; cursor: pointer;
  margin: 0 calc(-1 * var(--bleed)) 0.75rem 0;
}
.sec > summary::-webkit-details-marker { display: none; }
.sec > summary::before { content: "\25B8"; font-size: var(--type-small); color: var(--title); }
.sec[open] > summary::before { content: "\25BE"; }
.sec > summary::after {
  content: ""; flex: 1; height: 0.25em;
  background: radial-gradient(circle, var(--dots) 0.09em, transparent 0.11em) left bottom / 0.5em 0.25em;
  background-repeat: space no-repeat;   /* whole dots only, last one flush with the box edge */
}
.sec > summary h2 { font-size: var(--type-heading); margin: 0; }
.section-note { margin: -0.35rem 0 0.75rem; color: var(--gray); font-size: var(--type-small); line-height: 1.5; }

.papers { list-style: none; margin: 0; padding: 0; }
.paper { margin: 0 0 1.15rem; scroll-margin-top: 1rem; }

/* A paper's hint (papers.yml): a speech bubble below and to the right of the
   pointer, its tail at the cursor. site.js sets --x/--y to the pointer, .on
   while it shows, and .l/.u to open it leftward or upward at the window's
   edge. It moves by transform alone, so following the pointer costs no layout. */
.hint {
  --t: 0.45em;                           /* how far the tail reaches past the corner, along each edge */
  --off: calc(var(--t) + 0.35em);        /* from the cursor to the box's corner: the tail, plus air */
  --dx: var(--off); --dy: var(--off); --sx: 1; --sy: 1;
  position: fixed; left: 0; top: 0; z-index: 2;
  transform: translate(var(--x, 0), var(--y, 0)) translate(var(--dx), var(--dy));
  will-change: transform;
  width: max-content; max-width: 13.5rem; padding: 0.15rem 0.45rem;
  background: var(--bg); color: var(--text); border: 1px solid var(--text);
  font-size: var(--type-small); line-height: 1.3; text-align: center;
  visibility: hidden; opacity: 0; pointer-events: none;
  transition: opacity 0.15s, visibility 0.15s;
}
.hint.on { visibility: visible; opacity: 1; }
.hint.l { --dx: calc(-100% - var(--off)); --sx: -1; }
.hint.u { --dy: calc(-100% - var(--off)); --sy: -1; }
/* The tail (paper.html) is a 2t square centred on the border's corner, its tip
   at the square's outer corner: a triangle in the page colour covers the
   corner of the border, and an open path draws the two edges at 1px. */
.hint .tail {
  --c: calc(-1 * var(--t) - 0.5px);      /* the border is drawn half a pixel in from the box edge */
  position: absolute; top: var(--c); left: var(--c);
  width: calc(2 * var(--t)); height: calc(2 * var(--t));
  display: block; overflow: visible;
  transform: scale(var(--sx), var(--sy));
}
.hint.l .tail { left: auto; right: var(--c); }
.hint.u .tail { top: auto; bottom: var(--c); }
.hint .tail path:first-child { fill: var(--bg); }
.hint .tail path:last-child { fill: none; stroke: var(--text); stroke-width: 1; vector-effect: non-scaling-stroke; }

/* Titles go to the paper. An entry with a write-up sits in a light gray box,
   and a click anywhere else in the box opens it. The negative margins keep the
   text lined up with the entries around it. */
.paper[data-w] {
  cursor: pointer;
  background: var(--box);
  padding: 0.55rem var(--bleed);
  margin-left: calc(-1 * var(--bleed));
  margin-right: calc(-1 * var(--bleed));
}
@media (hover: hover) { .paper[data-w]:hover { background: var(--box-hover); } }
.paper[data-w].is-open { background: var(--box-open); }
.paper > a:not(.intro), .paper > a:not(.intro):visited { color: var(--title); text-decoration-color: color-mix(in srgb, var(--title) 40%, transparent); }
.paper > a:not(.intro):hover { text-decoration-color: currentColor; }
/* [+] / [−] on an entry with a write-up, like [close] */
.pm { display: inline-block; margin-right: 0.3em; }
.pm span { color: var(--gray); }
.pm span::before { content: "+"; }
.paper.is-open .pm span::before { content: "\2212"; }
.rail { display: none; }   /* the clickable line beside an inline write-up */

/* keyboard/screen-reader handle for the write-up; visible only when focused */
.intro { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.intro:focus-visible { position: static; width: auto; height: auto; clip-path: none; margin-left: 0.4em; font-size: var(--type-small); }

.ur { color: var(--red); white-space: nowrap; }
.by, .ln { font-size: var(--type-small); }
.by { color: var(--gray); }
.by b { color: var(--text); }

.bibbtn, .copybtn, .switch { font: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
.bib {
  margin: 0.4rem 0 0; padding: 0.6rem 0.75rem; background: var(--code-bg);
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: var(--type-small); line-height: 1.45;
  white-space: pre-wrap; overflow-wrap: anywhere; user-select: all;
}

/* right column: the open write-up, scrolling in a box of its own. The box is
   the solid part of the frosted pane, --gap past the text, with the scrollbar
   --bar-gap from it. The pane (at 109em, above) reaches a feather past the
   box, so the fade lies outside the bar's track. site.js measures the bar's
   width into --scrollbar. */
.reader {
  --gap: 0.25rem;
  --bar-gap: 0.5rem;
  position: sticky;
  top: calc(var(--top) - var(--gap));
  align-self: start;
  z-index: 0;
  margin: calc(var(--top) - var(--gap)) calc(-1 * (var(--bar-gap) + var(--scrollbar, 0px))) 1rem calc(-1 * var(--gap));
  min-width: 0;
}
/* The box fits the text, capped by the viewport (the pane's fade ends --top
   above its bottom) and by the middle column (--reader-column-limit, site.js). */
.reader .scroll {
  padding: var(--gap) var(--bar-gap) var(--gap) var(--gap);
  max-height: min(calc(100vh - 2 * var(--top) + var(--gap) - var(--feather)), var(--reader-column-limit, 100vh));
  max-height: min(calc(100dvh - 2 * var(--top) + var(--gap) - var(--feather)), var(--reader-column-limit, 100dvh));
  overflow-y: auto;
  scrollbar-gutter: stable;   /* so --scrollbar can be measured before anything scrolls */
}
/* Chrome (seen in 131 and 144) leaves unpainted a text run that lies wholly within
   the first few pixels of this box when it has scrollbar-gutter: stable, such as
   the [ of a tab row or an <em>k</em> starting a paragraph. A layer of its own
   for the write-up paints them; the layout is unchanged. */
.reader .scroll > .writeup { will-change: transform; }
/* Scrolling past the write-up's ends stays in it; closed, the empty box
   would swallow the wheel instead of passing it on to the page. */
.reader.is-open .scroll { overscroll-behavior: contain; }
/* With JS, the column has one [close], beside the title's first line. It sits
   outside the scrolling box, and once that scrolls, the box's text fades out
   along the top edge and around the [close] (a notch in the mask), so
   nothing half-cut shows. The scrollbar stays unfaded. */
.reader-close { display: none; }
@media (min-width: 109em) {
  .reader { --close-w: calc(3.4rem + var(--bar-gap)); --close-h: 1.9rem; }   /* the notch, just around [close] */
  .js .reader.is-open .reader-close {
    display: block; position: absolute; z-index: 1;
    top: calc(var(--gap) + 0.2rem); right: calc(var(--bar-gap) + var(--scrollbar, 0px));
    margin: 0; font-size: var(--type-small);
  }
  .js .reader .writeup .x { display: none; }
  .js .reader .writeup h2 { padding-right: 3.4rem; }
  /* scrollbar ∪ (top fade ∩ (beside the notch ∪ below it)), the notch's edges feathered */
  .reader .scroll.scrolled {
    --mask:
      linear-gradient(#000, #000) right top / var(--scrollbar, 0px) 100% no-repeat,
      linear-gradient(transparent, #000 1rem),
      linear-gradient(transparent, #000 0.6rem) right var(--scrollbar, 0px) top var(--close-h) / calc(var(--close-w) + 0.6rem) 100% no-repeat,
      linear-gradient(to left, transparent, #000 0.6rem) left top / calc(100% - var(--scrollbar, 0px) - var(--close-w)) 100% no-repeat;
    -webkit-mask: var(--mask); -webkit-mask-composite: source-over, source-in, source-over;
    mask: var(--mask); mask-composite: add, intersect, add;
  }
}

/* JS adds .open; without JS, :target does the same job */
.writeup { display: none; line-height: 1.6; }
.writeup:target, .js .writeup.open { display: block; }
.js .writeup:target:not(.open) { display: none; }
.writeup:focus { outline: none; }
.writeup h2 { font-size: var(--type-heading); margin: 0 0 1.25rem; }
.writeup .meta { color: var(--gray); font-size: var(--type-small); margin: 0 0 1.25rem; }
.writeup .x { float: right; margin: 0 0 0.5rem 1rem; font-size: var(--type-small); }
.writeup p { margin: 0 0 1em; }
.writeup .katex { font-size: 1em; }
.writeup .katex-display { overflow-x: auto; overflow-y: hidden; padding: 0.15em 0; }
.writeup img, .writeup video { display: block; max-width: 100%; margin: 1rem 0; }
.writeup figure { margin: 1rem 0; }
.writeup figure svg { display: block; max-width: 100%; height: auto; }
.writeup figcaption { color: var(--gray); font-size: var(--type-small); margin-top: 0.35rem; }

/* notes to self in drafts; the write-ups include them only on `jekyll serve` */
.todo { color: var(--faint); font-style: italic; }

/* ---------- photo | list (< 109em): write-ups open inside their entry ---------- */

@media not all and (min-width: 109em) {
  .page { grid-template-columns: var(--side) var(--list); }

  /* Without JS the targeted write-up shows below the lists (the aside is empty,
     so zero height, otherwise). With JS the write-up moves into its entry. */
  .reader { position: static; grid-column: 2; margin: 0; }
  .reader .scroll { padding: 0; max-height: none; overflow: visible; }
  .reader .writeup:target { border-top: var(--rule); padding: 1.5rem 0 3rem; }
  .js .reader { display: none; }

  .paper > .writeup {
    margin: 0.75rem 0 1.5rem;
    padding-left: 0.9rem;
    border-left: var(--rule);
  }
  .paper > .writeup h2 { display: none; }
  /* The [close] stays in view while the write-up is. The line down its left
     side closes it too, and lights up under the pointer. */
  .paper > .writeup { position: relative; }
  .paper > .writeup .x {
    position: -webkit-sticky; position: sticky; top: 0.75rem; z-index: 1;
    margin-bottom: 0; padding: 0.1rem 0.25rem; margin-right: -0.25rem; line-height: 1.25;
    background: var(--box-open); box-shadow: 0 0 0.3rem 0.2rem var(--box-open);   /* feathered edges */
  }
  .paper > .writeup .rail {
    display: block; position: absolute; top: 0; bottom: 0; left: calc(-0.6rem - 1px); width: 1.2rem;
    padding: 0; border: 0; background: none; cursor: pointer;
  }
  .rail:hover::before {
    content: ""; position: absolute; top: 0; bottom: 0; left: calc(0.6rem - 0.5px);
    border-left: 2px solid var(--text);
  }
}
@media (prefers-reduced-motion: reduce) { .page { transition: none !important; } }

/* ---------- one column (< 57em) ---------- */

@media not all and (min-width: 57em) {
  /* the list, centered, never closer than --phone-pad to the screen edge */
  .page {
    display: block;
    padding: 0 var(--phone-pad);   /* fallback for browsers without max() */
    padding: 0 max(var(--phone-pad), (100% - var(--list)) / 2);
  }

  /* Keep the square portrait beside the name, tagline and links. */
  .me {
    position: relative;   /* holds its frosted ground */
    top: auto;            /* the sticky offset above doesn't apply */
    display: grid;
    grid-template-columns: var(--side) minmax(0, 1fr);
    grid-template-rows: auto auto auto 1fr;
    column-gap: 1rem;
    align-items: start;
    margin: 3rem 0 0;
  }
  .me .photo { grid-row: 1 / span 4; }
  .me h1 { font-size: 1.6rem; margin: 0 0 0.2rem; }
  .me .tag { margin: 0 0 0.35rem; }
  .me .contact { margin: 0; grid-column: 2; grid-row: 3; align-self: baseline; }
  .me .contact a { display: inline; margin-right: 0.6em; }
  .js .me .switches { grid-column: 2; grid-row: 4; margin: 0.5rem 0 0; }
  .list { margin-top: 0; padding-top: 1.75rem; }
}

/* ---------- phone (< 36em): scrolling introduction, sticky navigation ---------- */

@media not all and (min-width: 36em) {
  html { scroll-padding-top: 3rem; }
  .mobile-nav {
    position: sticky; top: 0; z-index: 2;
    display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 0 2rem;
    padding: 0.25rem var(--phone-pad);
    background: var(--bg);
    border-bottom: var(--rule);
    font-size: var(--type-small);
  }
  .mobile-nav a { display: flex; align-items: center; min-height: 2rem; }
  .me { display: block; margin: 1.5rem 0 0; }
  .me h1 { font-size: 1.9rem; margin: 1rem 0 0.4rem; }
  .me .tag { margin: 0; }
  .me .contact, .js .me .switches { display: none; }

  .list { margin: 0; padding: 1.25rem 0 4rem; }
  .about { margin-bottom: 0.5rem; }
  .contact-inline { display: inline-block; margin: 0 0 1.75rem; }
  .contact-inline a { display: inline; margin-right: 0.75em; }
  .js .switches-inline { display: inline-block; margin: 0 0 1.75rem; color: var(--gray); font-size: inherit; }

  /* Root scroll padding clears the navigation for every anchor destination. */
  .paper, .reader .writeup:target { scroll-margin-top: 0; }
  .paper > .writeup .x { top: 3rem; }   /* below the navigation */
  .ln a, .bibbtn, .copybtn { padding: 0.2rem 0; }
}

/* Wider phones have room for the full portrait and a short text column.
   Smaller phones stack them so the tagline remains comfortable to read. */
@media (min-width: 24em) and (max-width: 35.99em) {
  .me { display: grid; grid-template-rows: auto 1fr; }
  .me .photo { grid-row: 1 / span 2; }
  .me h1 { font-size: 1.6rem; margin: 0 0 0.4rem; }
}

@media print {
  .page { display: block; }
  .me { position: static; }
  .mobile-nav, .reader, .bibbtn, .copy, .contact-inline, .switches, .unbare, #qft { display: none !important; }
  a { color: inherit; }
}

/* ---------- tabbed figures in write-ups ---------- */

/* [k-WM] [next-step]: each tab opens one pane; the open one reads as plain
   bold text. A pane wider than the column scrolls sideways on its own. */
.tabs { margin: 0 0 0.35rem; font-size: var(--type-small); color: var(--gray); }
.tab {
  font: inherit; background: none; border: 0; padding: 0; cursor: pointer;
}
.tab[aria-selected="true"] { color: var(--text); font-weight: 700; text-decoration: none; cursor: default; }
.tabfig .pane { overflow-x: auto; }
.tabfig .pane > svg, .wide > svg { min-width: 30rem; margin: 0; }
.wide { overflow-x: auto; }
.tabfig figcaption { margin-top: 0.5rem; }

/* The k-WM method diagram (fig-kwm-method-*.svg) carries its build steps in
   data-ms. With JS the inference outputs appear one predictor call at a time
   and the predictor block flashes for each call (site.js); without, all show. */
.js .method .ms { opacity: 0; transition: opacity 0.5s ease; }
.js .method .ms.show { opacity: 1; }
.method .pulse { animation: method-pulse 0.9s ease; }
@keyframes method-pulse { 0% { fill: #e2edf3; } 35% { fill: #cdb6e4; } 100% { fill: #e2edf3; } }
@media (prefers-reduced-motion: reduce) {
  .js .method .ms { opacity: 1; transition: none; }
  .method .pulse { animation: none; }
}
