/* Each colour is light-dark(light, dark). The active scheme follows the system
   unless the theme switch sets data-theme on <html>. */
:root {
  color-scheme: light dark;

  --paper: light-dark(#f5f7f8, #151b23);
  --ink: light-dark(#1a2332, #c9ced5);
  --slate: light-dark(#5b6775, #8c97a4);
  --link: light-dark(#0f4c81, #f0eee9); /* Classic Blue (Pantone 2020) / Cloud Dancer (Pantone 2026) */
  --magenta: light-dark(#a62e6b, #e584b5);
  --red: light-dark(#d3072b, #ff4f64); /* Michelin red: awards and stars */
  --rule: light-dark(#d3dae0, #2c3541);
  --code-bg: light-dark(#e9eef2, #1f2731);
  --award-bg: light-dark(rgb(211 7 43 / 0.09), rgb(255 79 100 / 0.14));
  --bar-bg: light-dark(rgb(245 247 248 / 0.72), rgb(21 27 35 / 0.72));
  --bar-line: light-dark(rgb(26 35 50 / 0.08), rgb(255 255 255 / 0.08));
  --shadow: 0 6px 20px light-dark(rgb(26 35 50 / 0.12), rgb(0 0 0 / 0.45));
  --tok-string: light-dark(#2f6b3a, #93d3a2);
  --tok-function: light-dark(#6b3fa0, #c7a8ff);
  --tok-macro: light-dark(#8a5a00, #e3b866);
  --tok-punct: light-dark(#3d4856, #a9b3bf);
  /* The two random hues (0–254, as docsify) map onto one OKLCH hue arc,
     140°→390°, skipping brown/olive (≈30°–140°). Both modes use the same hue,
     so switching keeps the colour: pastel tint in light, muted tint in dark. */
  --hue-1: calc(140 + var(--h1) * 0.98);
  --hue-2: calc(140 + var(--h2) * 0.98);
  --wash-1: light-dark(oklch(0.90 0.07 var(--hue-1)), oklch(0.36 0.075 var(--hue-1)));
  --wash-2: light-dark(oklch(0.90 0.07 var(--hue-2)), oklch(0.36 0.075 var(--hue-2)));
  /* Aurora ribbons: the same hues plus their midpoint, a touch more vivid than the wash. */
  --hue-3: calc((var(--hue-1) + var(--hue-2)) / 2 + 30);
  --ribbon-1: light-dark(oklch(0.84 0.12 var(--hue-1)), oklch(0.46 0.12 var(--hue-1)));
  --ribbon-2: light-dark(oklch(0.84 0.12 var(--hue-2)), oklch(0.46 0.12 var(--hue-2)));
  --ribbon-3: light-dark(oklch(0.86 0.10 var(--hue-3)), oklch(0.44 0.10 var(--hue-3)));

  --sans: "Barlow", system-ui, sans-serif;
  --condensed: "Barlow Condensed", "Barlow", system-ui, sans-serif;
  --serif: "Source Serif 4", Georgia, serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --gutter: clamp(1.25rem, 5vw, 4rem);
  --wrap: 72rem;
  --measure: 42rem;

  /* Hues overwritten on every load by the inline script in <head>. */
  --h1: 200;
  --h2: 320;
  --wash-height: 34rem;
}
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

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

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

body {
  position: relative;
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 1.125rem/1.65 var(--serif);
  font-optical-sizing: auto;
}

img { max-width: 100%; height: auto; }

a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { text-decoration-thickness: 2px; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; border-radius: 2px; }

.skip {
  position: absolute; left: var(--gutter); top: -3rem;
  padding: 0.5rem 0.75rem; background: var(--ink); color: var(--paper);
  font-family: var(--sans); z-index: 10;
}
.skip:focus { top: 0.75rem; }

/* ---------- Cover: the random gradient ---------- */

/* The docsify gradient as an aurora: a static base wash with three soft ribbons
   drifting over it, all dissolving into the paper instead of ending at an edge. */
.aurora {
  position: absolute;
  inset: 0 0 auto;
  height: var(--wash-height);
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background: linear-gradient(to left bottom, var(--wash-1) 0%, var(--wash-2) 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 45%, transparent);
  mask-image: linear-gradient(to bottom, #000 45%, transparent);
}
body:has(.cover--home) { --wash-height: max(100vh, 52rem); }

.aurora span {
  position: absolute;
  left: -25%;
  width: 150%;
  height: 55%;
  border-radius: 50%;
  opacity: 0.85;
  will-change: transform;
  animation: aurora-1 5s ease-in-out infinite alternate;
}
.aurora span:nth-child(1) {
  top: -10%;
  background: radial-gradient(closest-side, var(--ribbon-1), transparent);
}
.aurora span:nth-child(2) {
  top: 15%;
  background: radial-gradient(closest-side, var(--ribbon-2), transparent);
  animation-name: aurora-2;
  animation-duration: 7s;
}
.aurora span:nth-child(3) {
  top: 35%;
  height: 40%;
  background: radial-gradient(closest-side, var(--ribbon-3), transparent);
  animation-name: aurora-3;
  animation-duration: 9s;
}
@keyframes aurora-1 {
  0%   { transform: translate(-12%, 0) rotate(-10deg) scale(1, 0.8); }
  50%  { transform: translate(6%, 12%) rotate(4deg) scale(1.15, 1.1); }
  100% { transform: translate(14%, -4%) rotate(12deg) scale(0.9, 0.9); }
}
@keyframes aurora-2 {
  0%   { transform: translate(14%, 6%) rotate(8deg) scale(1.1, 0.9); }
  50%  { transform: translate(-8%, -10%) rotate(-6deg) scale(0.9, 1.2); }
  100% { transform: translate(-16%, 8%) rotate(-14deg) scale(1.2, 0.8); }
}
@keyframes aurora-3 {
  0%   { transform: translate(0, 0) rotate(-4deg) scale(0.9, 1); }
  50%  { transform: translate(16%, -14%) rotate(10deg) scale(1.2, 0.8); }
  100% { transform: translate(-10%, 6%) rotate(-12deg) scale(1, 1.2); }
}
/* Off-screen: stop entirely; the script restarts it in phase when it returns. */
.aurora.is-paused span { animation: none; }

/* ---------- Page transitions (cross-document View Transitions) ----------
   The aurora, top bar, site name and current-page indicator persist across
   navigation; only the page content fades. */
@view-transition { navigation: auto; }
.aurora:not(.is-paused) { view-transition-name: aurora; }
.topbar { view-transition-name: topbar; }
.nav-indicator { view-transition-name: nav-indicator; }
/* The name morphs between the home hero and the top bar; exactly one element
   carries it at a time. */
.hero-name-en, .nav-home { view-transition-name: site-name; }
.topbar--home:not(.is-stuck) .nav-home,
.topbar--home.is-stuck ~ .cover .hero-name-en { view-transition-name: none; }
/* Cover and main get per-page names in the template, so the outgoing and
   incoming content animate separately instead of morphing into each other. */
.cover, main { view-transition-class: page; }

::view-transition-group(*) {
  animation-duration: 450ms;
  animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
}
::view-transition-old(aurora),
::view-transition-new(aurora) { height: 100%; object-fit: fill; }
::view-transition-old(*.page) { animation: page-out 160ms ease-in both; }
/* Name morph: show only the larger (crisp) snapshot while it scales, and swap
   to the other one at the very end, so the text never doubles up or blurs.
   The larger one is the old image unless the new page is the home page. */
::view-transition-old(site-name),
::view-transition-new(site-name) { height: 100%; object-fit: fill; mix-blend-mode: normal; }
::view-transition-old(site-name) { animation: name-hold-out 450ms both; }
::view-transition-new(site-name) { animation: name-hold-in 450ms both; }
:root:has(.cover--home)::view-transition-old(site-name) { animation: name-swap-out 450ms both; }
:root:has(.cover--home)::view-transition-new(site-name) { animation: name-swap-in 450ms both; }
@keyframes name-hold-out { 0%, 85% { opacity: 1; } 100% { opacity: 0; } }
@keyframes name-hold-in { 0%, 85% { opacity: 0; } 100% { opacity: 1; } }
@keyframes name-swap-out { 0% { opacity: 1; } 15%, 100% { opacity: 0; } }
@keyframes name-swap-in { 0% { opacity: 0; } 15%, 100% { opacity: 1; } }
::view-transition-new(*.page) { animation: page-in 340ms 120ms cubic-bezier(0.2, 0.7, 0.2, 1) both; }
@keyframes page-out { to { opacity: 0; } }
@keyframes page-in { from { opacity: 0; transform: translateY(8px); } }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

.cover { padding: 0 var(--gutter); }
.cover a, .topbar a { color: var(--ink); }

/* Sticky top bar: clear over the gradient, frosted once the page scrolls. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  padding: 0 var(--gutter);
  border-bottom: 1px solid transparent;
  transition: background-color 200ms ease, border-color 200ms ease;
}
.topbar.is-stuck {
  background: var(--bar-bg);
  -webkit-backdrop-filter: blur(14px) saturate(1.6);
  backdrop-filter: blur(14px) saturate(1.6);
  border-bottom-color: var(--bar-line);
}
/* On the home page the hero already shows the name; reveal it in the bar after scrolling. */
.topbar--home .nav-home { opacity: 0; transition: opacity 200ms ease; }
.topbar--home.is-stuck .nav-home { opacity: 1; }
.topbar--home:not(.is-stuck) .nav-home { visibility: hidden; }

.nav {
  width: 100%;
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 1rem 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.85rem 2rem;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 1rem;
}
.nav ul { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 1.5rem; margin: 0; padding: 0; list-style: none; }

/* Light/dark switch: shows the icon of the mode it switches to. */
.theme-switch {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  margin: -0.25rem 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink);
  cursor: pointer;
}
.theme-switch:hover { background: var(--bar-line); }
.theme-switch .icon { display: block; width: 1.25rem; height: 1.25rem; }
.theme-switch .to-light { display: none; }
:root[data-scheme="dark"] .theme-switch .to-light { display: contents; }
:root[data-scheme="dark"] .theme-switch .to-dark { display: none; }
.nav a { text-decoration: none; }
.nav a:hover:not([aria-current]), .nav summary:not(.is-current):hover, .nav-menu a[aria-current="page"] {
  text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 0.18em;
}
/* Current-page underline as its own element, so it can slide between items on navigation. */
.nav ul a, .nav summary { position: relative; }
.nav-indicator {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  background: currentColor;
}
.nav summary .nav-indicator { right: 0.7em; }

.nav-group { position: relative; }
.nav summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.3em;
}
.nav summary::-webkit-details-marker { display: none; }
.nav summary::after {
  content: "";
  width: 0.4em;
  height: 0.4em;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-0.15em) rotate(45deg);
  transition: transform 150ms ease;
}
.nav details[open] summary::after { transform: translateY(0.1em) rotate(-135deg); }
.nav .nav-menu {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: -0.75rem;
  z-index: 5;
  display: block;
  min-width: 12rem;
  padding: 0.4rem 0;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 6px;
  box-shadow: var(--shadow);
  animation: menu-in 140ms ease-out;
}
.nav-menu a { display: block; padding: 0.45rem 0.75rem; white-space: nowrap; }
.nav-menu a:hover { background: var(--code-bg); }
@keyframes menu-in { from { opacity: 0; transform: translateY(-4px); } }

@media (prefers-reduced-motion: reduce) {
  .aurora span { animation: none !important; }
  .nav .nav-menu { animation: none !important; }
  .hero-profiles .icon, .icon-img .icon { transition: none !important; }
  .nav summary::after { transition: none; }
  .topbar, .topbar--home .nav-home { transition: none; }
}
/* Same face as the hero name, so the two morph cleanly into each other. */
.nav-home {
  display: inline-block;
  font-family: var(--condensed);
  font-weight: 600;
  font-size: 1.35rem;
  line-height: 0.88;
  letter-spacing: -0.015em;
}
html { scroll-padding-top: 4.5rem; }

.cover--home {
  min-height: min(68vh, 44rem);
  display: flex;
  flex-direction: column;
}

.hero {
  max-width: var(--wrap);
  width: 100%;
  margin: auto auto 0;
  padding: 3rem 0 clamp(2.5rem, 6vw, 4.5rem);
}

.hero-avatar {
  display: block;
  width: 112px;
  height: 112px;
  border-radius: 50%;
  margin-bottom: 1.75rem;
}

.hero-name {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 0.3em;
  font-weight: 600;
  line-height: 0.88;
}
.hero-name-en {
  font-family: var(--condensed);
  font-size: clamp(3.75rem, 12vw, 8.5rem);
  letter-spacing: -0.015em;
}
.hero-name-zh {
  font-family: "Noto Serif SC", "Songti SC", serif;
  font-weight: 500;
  font-size: clamp(1.75rem, 4.5vw, 3rem);
  letter-spacing: 0.08em;
}

.hero-tagline {
  margin: 1.25rem 0 0;
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(1.2rem, 2.4vw, 1.5rem);
}

.hero-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 2.5rem;
  margin-top: 1.5rem;
  font-family: var(--sans);
  font-weight: 500;
}
.hero-links ul { display: flex; gap: 0.5rem 1.5rem; margin: 0; padding: 0; list-style: none; }
.hero-links .hero-profiles { gap: 1.25rem; }
.hero-profiles a { display: block; color: var(--ink); }
.hero-profiles .icon { display: block; width: 28px; height: 28px; transition: transform 120ms ease; }
.hero-profiles a:hover .icon { transform: translateY(-2px); }

/* [[Paper]] / [[Slides]] / [[Code]] links, rewritten at build time */
.pub-link {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  margin-right: 0.9em;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 0.95em;
}
.pub-link .icon { width: 1.1em; height: 1.1em; flex: none; }

.cover--page { padding-bottom: clamp(2rem, 5vw, 3.5rem); }
.page-title {
  max-width: var(--wrap);
  margin: 2rem auto 0;
  font-family: var(--condensed);
  font-weight: 600;
  font-size: clamp(2.75rem, 7vw, 4.5rem);
  line-height: 0.95;
}

/* ---------- Content ---------- */

main, .footer {
  margin-inline: max(var(--gutter), calc((100% - var(--wrap)) / 2));
}
main { padding: clamp(1.5rem, 4vw, 3rem) 0 4rem; }

.prose { max-width: var(--measure); }
.prose > :first-child { margin-top: 0; }

.prose h2, .prose h3 {
  font-family: var(--condensed);
  font-weight: 600;
  line-height: 1.1;
  scroll-margin-top: 1.5rem;
}
.prose h2 { font-size: 2rem; margin: 3.25rem 0 1.25rem; }
.prose h3 { font-size: 1.45rem; margin: 2.25rem 0 0.75rem; }
.prose p, .prose ul, .prose ol, .prose table, .prose pre { margin: 0 0 1.25rem; }

.prose--home ul { list-style: none; padding: 0; }
.prose--home li { margin-bottom: 1.5rem; }
.prose--home li > p { margin: 0; }
.prose--home li strong { font-weight: 600; }

.award {
  display: inline-block;
  padding: 0.05em 0.45em;
  border-radius: 4px;
  background: var(--award-bg);
  font-family: var(--sans);
  font-weight: 600;
  color: var(--red);
}

.michelin { display: inline-flex; gap: 0.15em; color: var(--red); vertical-align: -0.15em; }
.michelin .icon { width: 1.05em; height: 1.05em; }
.michelin-total { display: inline-flex; align-items: center; gap: 0.35em; color: var(--red); }
.michelin-total .icon { width: 1.05em; height: 1.05em; }
.prose tfoot td { border-top: 2px solid var(--ink); border-bottom: 0; font-weight: 600; }

.prose code {
  font-family: var(--mono);
  font-size: 0.85em;
  background: var(--code-bg);
  padding: 0.1em 0.3em;
  border-radius: 3px;
}
.prose pre {
  tab-size: 4;
  overflow-x: auto;
  padding: 1rem 1.25rem;
  background: var(--code-bg);
  border-radius: 6px;
  line-height: 1.5;
}
.prose pre code { padding: 0; background: none; font-size: 0.8rem; }

/* Hide the raw diagram source until mermaid has drawn it. */
pre.mermaid:not([data-processed]) { visibility: hidden; }
pre.mermaid {
  background: none;
  padding: 0;
  text-align: center;
  font-family: var(--sans);
}

/* Prism tokens, tuned to the palette */
.token.comment, .token.prolog { color: var(--slate); font-style: italic; }
.token.keyword, .token.directive, .token.macro .token.directive-hash { color: var(--link); font-weight: 600; }
.token.string, .token.char { color: var(--tok-string); }
.token.number, .token.boolean { color: var(--magenta); }
.token.function { color: var(--tok-function); }
.token.macro, .token.property { color: var(--tok-macro); }
.token.operator, .token.punctuation { color: var(--tok-punct); }

.prose table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--sans);
  font-size: 1rem;
  font-variant-numeric: tabular-nums;
}
.prose th {
  text-align: left;
  font-weight: 600;
  border-bottom: 2px solid var(--ink);
  padding: 0.5rem 0.75rem 0.5rem 0;
}
.prose td {
  border-bottom: 1px solid var(--rule);
  padding: 0.55rem 0.75rem 0.55rem 0;
  vertical-align: top;
}
@media (max-width: 40rem) {
  .prose table { display: block; overflow-x: auto; white-space: nowrap; }
}

.flight-banner { display: inline-block; }

/* ![Name](icon:name) in Markdown renders an inline SVG icon in the link colour. */
.prose a:has(> .icon-img) { display: inline-block; margin-right: 1.25rem; color: var(--link); }
.icon-img, .icon-img .icon { display: block; width: 28px; height: 28px; }
.icon-img .icon { transition: transform 120ms ease; }
.prose a:hover > .icon-img .icon { transform: translateY(-2px); }

/* ---------- Table of contents ---------- */

.toc { font-family: var(--sans); font-size: 0.95rem; margin-bottom: 2.5rem; }
.toc-title { font-weight: 600; margin: 0 0 0.5rem; }
.toc ol { margin: 0; padding-left: 1.25rem; color: var(--slate); }
.toc li { margin: 0.3rem 0; }
.toc a { color: var(--ink); text-decoration: none; }
.toc a:hover, .toc a[aria-current] { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 0.2em; }

@media (min-width: 64rem) {
  .page--toc {
    display: grid;
    grid-template-columns: minmax(0, var(--measure)) 14rem;
    justify-content: space-between;
    gap: 4rem;
  }
  .page--toc .toc {
    order: 2;
    position: sticky;
    top: 5.5rem;
    align-self: start;
    margin: 0;
  }
}

/* ---------- Footer ---------- */

.footer {
  padding: 1.5rem 0 2.5rem;
  border-top: 1px solid var(--rule);
  font-family: var(--sans);
  font-size: 0.95rem;
  color: var(--slate);
}
.footer p { margin: 0; }
.footer a { color: inherit; }

@media (max-width: 40rem) {
  body { font-size: 1.0625rem; }
  .hero-avatar { width: 88px; height: 88px; }
  .cover--home { min-height: 0; }
  .hero { padding-top: 2.5rem; }
}
