:root{
  --ink:#1A1F24;
  --ink-soft:#4A5560;
  --ink-faint:#8B959E;
  --paper:#F5F1E6;
  --paper-raised:#FBF8F0;
  --seal:#A6342A;
  --pine:#24486B;
  --navy:#2C4C77;
  --rule:#DCD5C4;
  --badge:#A6342A;
  --btn-alt:#2F5D50;

  --display:"Newsreader",Georgia,serif;
  --body:"IBM Plex Sans",system-ui,-apple-system,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,monospace;
  --tc:"Noto Serif TC",serif;
  --kr:"Noto Serif KR",serif;
  --jp:"Noto Serif JP",serif;

  --measure:64rem;
  --topbar-bg:rgba(245,241,230,.92);
}

/* ---------- dark palette ---------- */
/* Applied when the visitor picks dark, or when their system prefers it and
   they have not picked light. Only the colour tokens change. */
:root[data-theme="dark"]{
  --ink:#E4E7E2;
  --ink-soft:#A9B2B8;
  --ink-faint:#7B858C;
  --paper:#15181B;
  --paper-raised:#1D2126;
  --seal:#E8C46B;
  --pine:#A9C6DE;
  --navy:#8AABD6;
  --rule:#333A40;
  --badge:#E0B341;
  --btn-alt:#EDA3BC;
  --topbar-bg:rgba(21,24,27,.92);
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --ink:#E4E7E2;
    --ink-soft:#A9B2B8;
    --ink-faint:#7B858C;
    --paper:#15181B;
    --paper-raised:#1D2126;
    --seal:#E8C46B;
    --pine:#A9C6DE;
    --navy:#8AABD6;
    --rule:#333A40;
    --badge:#E0B341;
    --btn-alt:#EDA3BC;
    --topbar-bg:rgba(21,24,27,.92);
  }
}

*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important}
}

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

a{color:inherit;text-decoration:none}
a:focus-visible,button:focus-visible{
  outline:2px solid var(--seal);
  outline-offset:3px;
}

.wrap{
  max-width:var(--measure);
  margin:0 auto;
  padding:0 1.75rem;
}

/* ---------- top bar ---------- */
.topbar{
  position:sticky;top:0;z-index:50;
  background:var(--topbar-bg);
  backdrop-filter:blur(8px);
  border-bottom:1px solid var(--rule);
}
.topbar .wrap{
  display:flex;align-items:baseline;gap:2rem;
  padding-top:.9rem;padding-bottom:.9rem;
  flex-wrap:wrap;
}
.brand{
  font-family:var(--display);
  font-size:1.05rem;font-weight:500;
  letter-spacing:.01em;
  margin-right:auto;
}
/* Chinese name: Noto Serif TC, but the same size and colour as the Latin name */
.brand span{font-family:var(--tc);font-weight:500;margin-left:.35rem}
nav ul{display:flex;gap:1.4rem;list-style:none;flex-wrap:wrap}
nav a{
  font-family:var(--mono);
  font-size:.74rem;
  letter-spacing:.09em;
  text-transform:uppercase;
  color:var(--ink-soft);
  padding-bottom:.15rem;
  border-bottom:1px solid transparent;
  transition:color .2s,border-color .2s;
}
nav a:hover{color:var(--ink);border-bottom-color:var(--ink)}
nav a[aria-current="page"]{color:var(--ink);border-bottom-color:var(--ink)}

/* ---------- theme toggle ---------- */
.theme-toggle{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:1.9rem;height:1.9rem;
  color:var(--ink-faint);
  background:none;
  border:1px solid transparent;
  border-radius:50%;
  padding:0;
  cursor:pointer;
  align-self:center;
  transition:color .2s,border-color .2s;
}
.theme-toggle svg{width:1.05rem;height:1.05rem;display:block}
.theme-toggle:hover{color:var(--ink);border-color:var(--rule)}

/* ---------- home ---------- */
.hero{padding:5.5rem 0 4rem}
.eyebrow{
  font-family:var(--mono);
  font-size:.74rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--pine);
  margin-bottom:1.6rem;
}
h1{
  font-family:var(--display);
  font-weight:300;
  font-size:clamp(2.6rem,7vw,4.6rem);
  line-height:1.04;
  letter-spacing:-.02em;
  margin-bottom:.4rem;
}
h1 .zh{
  display:block;
  font-family:var(--tc);
  font-weight:400;
  font-size:.42em;
  color:var(--ink-faint);
  letter-spacing:.14em;
  margin-top:.7rem;
}
.tagline{
  font-family:var(--display);
  font-size:clamp(1.1rem,2.4vw,1.4rem);
  font-style:italic;
  color:var(--ink-soft);
  max-width:34em;
  margin-top:1.8rem;
}


/* ---------- home: section index ---------- */
.pagelinks{
  margin-top:3.5rem;
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));
  gap:1.4rem 2.4rem;
}
.pagelink{
  border-top:1px solid var(--rule);
  padding-top:.9rem;
  display:block;
  transition:border-color .2s;
}
.pagelink:hover{border-top-color:var(--seal)}
.pagelink .label{
  font-family:var(--mono);
  font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-faint);
  display:block;margin-bottom:.45rem;
  transition:color .2s;
}
.pagelink:hover .label{color:var(--seal)}
.pagelink .blurb{font-size:.93rem;line-height:1.55;color:var(--ink-soft)}

/* ---------- page head (sub-pages) ---------- */
.page-head{
  padding:4rem 0 0;
}
.page-head .eyebrow{margin:.6rem 0 0}
.page-head h1{
  font-size:clamp(2.1rem,5vw,3.2rem);
  font-weight:400;
  margin-bottom:.35rem;
}
.page-head .count{
  font-family:var(--mono);
  font-size:.74rem;
  color:var(--ink-faint);
  letter-spacing:.08em;
}

/* ---------- sections ---------- */
section{padding:4.5rem 0;border-bottom:1px solid var(--rule)}
section:last-of-type{border-bottom:none}
section.flush{border-bottom:none}
section.first{padding-top:2.6rem}
.sec-head{
  display:flex;align-items:baseline;gap:1rem;
  margin-bottom:2.4rem;
}
.sec-head h2{
  font-family:var(--display);
  font-weight:400;
  font-size:clamp(1.7rem,3.4vw,2.2rem);
  letter-spacing:-.01em;
}
.sec-head .count{
  font-family:var(--mono);
  font-size:.74rem;
  color:var(--ink-faint);
  letter-spacing:.08em;
}
.prose p{
  max-width:50em;
  margin-bottom:1.15rem;
  font-size:1.02rem;
  text-align:justify;
  -webkit-hyphens:auto;
  hyphens:auto;
}
.prose p:last-child{margin-bottom:0}
.prose a{border-bottom:1px solid var(--rule);transition:color .2s,border-color .2s}
.prose a:hover{color:var(--seal);border-bottom-color:var(--seal)}
.prose a.reach{border-bottom:none}

.facts{
  margin-top:2.5rem;
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));
  gap:1.6rem 2.4rem;
}
.fact{border-top:1px solid var(--rule);padding-top:.9rem}
.fact dt{
  font-family:var(--mono);
  font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-faint);
  margin-bottom:.45rem;
}
.fact dd{font-size:.95rem;line-height:1.55}

/* ---------- inline citations ---------- */
.cite{
  font-family:var(--mono);
  font-size:.7em;
  vertical-align:.35em;
  margin-left:.15em;
  color:var(--pine);
  transition:color .2s;
}
a.cite:hover{color:var(--seal)}
span.cite{color:var(--ink-faint);cursor:default}
/* the sticky top bar would otherwise cover a linked entry */
.pub[id]{scroll-margin-top:5.5rem}
/* The mark holds, then fades on its own. Change --mark-time to adjust: the
   bar stays for roughly three quarters of it, then dissolves. */
.pub:target{
  --mark-time:6s;
  animation:cite-mark var(--mark-time) ease-out forwards;
}
@keyframes cite-mark{
  0%,72%{box-shadow:inset 3px 0 0 var(--seal);padding-left:1rem}
  100%{box-shadow:inset 3px 0 0 transparent;padding-left:0}
}

/* ---------- publications ---------- */
.pub{
  display:grid;
  grid-template-columns:4rem 1fr;
  gap:1.5rem;
  padding:1.5rem 0;
  border-top:1px solid var(--rule);
}
.pub .yr{
  font-family:var(--mono);
  font-size:.8rem;
  color:var(--ink-faint);
  padding-top:.2rem;
}
.pub h3{
  font-family:var(--display);
  font-weight:400;
  font-size:1.18rem;
  line-height:1.35;
  margin-bottom:.5rem;
}
.pub .authors{font-size:.9rem;color:var(--ink-soft);margin-bottom:.35rem}
.pub .authors .me{color:var(--ink);font-weight:600}
.pub .venue{font-size:.9rem;color:var(--ink-soft);font-style:italic}
.pub .links{margin-top:.7rem;display:flex;gap:.6rem;flex-wrap:wrap}
.pub .links a{
  font-family:var(--mono);
  font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;
  border:1px solid var(--rule);
  padding:.22rem .6rem;
  color:var(--ink-soft);
  transition:border-color .2s,color .2s,background .2s;
}
.pub .links a:hover{border-color:var(--pine);color:var(--pine)}
.badge{
  display:inline-block;
  font-family:var(--mono);
  font-size:.65rem;letter-spacing:.08em;text-transform:uppercase;
  color:var(--badge);
  border:1px solid var(--badge);
  padding:.1rem .45rem;
  margin-left:.5rem;
  vertical-align:.15em;
}

/* ---------- service ---------- */
.svc-group{margin-bottom:3rem}
.svc-group:last-child{margin-bottom:0}
.svc-group h3{
  font-family:var(--mono);
  font-weight:500;
  font-size:.74rem;letter-spacing:.11em;text-transform:uppercase;
  color:var(--navy);
  margin-bottom:1.2rem;
  padding-bottom:.5rem;
  border-bottom:1px solid var(--rule);
}
.svc-group h3 .since{
  font-weight:400;
  color:var(--ink-faint);
  margin-left:.7rem;
  letter-spacing:.08em;
}
.svc-list{list-style:none}
.svc-list li{
  display:grid;
  grid-template-columns:5.5rem 1fr;
  gap:1.5rem;
  padding:.65rem 0;
  font-size:.96rem;
}
.svc-list.tight li{padding:.35rem 0}
.svc-list .yr{font-family:var(--mono);font-size:.8rem;color:var(--ink-faint)}
.svc-list .role{color:var(--ink-soft)}
.svc-list .role .org{color:var(--ink)}
.svc-list .role a{
  border-bottom:1px solid var(--rule);
  transition:color .2s,border-color .2s;
}
.svc-list .role a:hover{color:var(--seal);border-bottom-color:var(--seal)}

/* ---------- scrollable news ---------- */
/* Caps the News block at five entries; anything beyond that scrolls inside the
   box instead of lengthening the page. One row is the .96rem type at the 1.65
   body line-height plus the .35rem the tight list pads above and below. */
.news-box{
  --news-row:2.29rem;
  display:flex;
  align-items:stretch;
  gap:.7rem;
}
.news-scroll{
  flex:1;
  min-width:0;
  max-height:calc(var(--news-row) * 5);
  overflow-y:auto;
  overscroll-behavior:contain;
  /* the bottom edge fades out to signal there is more below */
  -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 2.2rem),transparent 100%);
  mask-image:linear-gradient(to bottom,#000 calc(100% - 2.2rem),transparent 100%);
}
/* no fade once the reader has reached the end of the list */
.news-scroll.at-end{-webkit-mask-image:none;mask-image:none}
.news-scroll:focus-visible{
  outline:1px solid var(--rule);
  outline-offset:.6rem;
}
/* The arrows carry the affordance, so the scrollbar itself is hidden. The box
   still scrolls by wheel, trackpad, touch and keyboard. */
.news-scroll{scrollbar-width:none}
.news-scroll::-webkit-scrollbar{display:none}
/* the mask would otherwise clip the last row's descenders */
.news-scroll .svc-list li:last-child{padding-bottom:1.6rem}

/* Up/down buttons, built by news.js so a reader without scripts still gets a
   plain scrollable list rather than two dead controls. */
.news-nav{
  flex:none;
  /* the flex display below would otherwise beat the hidden attribute */
  display:flex;
  flex-direction:column;
  /* the column stretches to the height of the list, so this parks one arrow
     against the rule under the heading and the other at the foot of the box */
  justify-content:space-between;
  align-items:center;
}
.news-nav[hidden]{display:none}
.news-nav button{
  display:flex;
  align-items:center;
  justify-content:center;
  width:1.5rem;
  height:1.5rem;
  background:none;
  border:0;
  color:var(--ink-faint);
  cursor:pointer;
  transition:color .2s,opacity .2s;
}
/* pulled up into the gap the heading leaves, so it sits closer to the rule */
.news-nav button:first-child{margin-top:-.75rem}
.news-nav button svg{width:.9rem;height:.9rem}
.news-nav button:hover:not(:disabled){color:var(--seal)}
.news-nav button:focus-visible{outline:1px solid var(--rule);outline-offset:2px}
.news-nav button:disabled{opacity:.25;cursor:default}

/* ---------- CV ---------- */
.cv-actions{display:flex;gap:.9rem;flex-wrap:wrap;margin-bottom:2.8rem}
.btn{
  font-family:var(--mono);
  font-size:.76rem;letter-spacing:.08em;text-transform:uppercase;
  padding:.7rem 1.3rem;
  border:1px solid var(--ink);
  transition:background .2s,color .2s;
}
.btn:hover{background:var(--ink);color:var(--paper)}
.btn.alt{border-color:var(--rule);color:var(--ink-soft)}
.btn.alt:hover{background:var(--btn-alt);border-color:var(--btn-alt);color:var(--paper)}

.pdf-viewer{
  display:block;
  width:100%;
  height:min(85vh,60rem);
  border:1px solid var(--rule);
  background:var(--paper-raised);
}
.pdf-fallback{
  display:flex;
  align-items:center;
  justify-content:center;
  height:100%;
  padding:2rem;
  text-align:center;
  font-size:.95rem;
  color:var(--ink-soft);
}
.pdf-fallback a{border-bottom:1px solid var(--rule)}
.pdf-fallback a:hover{color:var(--seal);border-bottom-color:var(--seal)}
@media (max-width:800px){
  .pdf-viewer{height:70vh}
}


.skills{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));gap:1.8rem 2.4rem}
.skill{border-top:1px solid var(--rule);padding-top:.9rem}
.skill h4{
  font-family:var(--mono);
  font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-faint);
  margin-bottom:.6rem;
}
.skill p{font-size:.93rem;line-height:1.6}

/* ---------- footer ---------- */
footer{padding:1.6rem 0 2rem;border-top:1px solid var(--rule)}
.contact{display:flex;gap:1.6rem;flex-wrap:wrap;margin-bottom:1.6rem}
.contact a{
  font-family:var(--mono);
  font-size:.8rem;
  border-bottom:1px solid var(--rule);
  padding-bottom:.15rem;
  transition:border-color .2s,color .2s;
}
.contact a:hover{color:var(--seal);border-bottom-color:var(--seal)}
a.reach{display:inline-flex;align-items:center;gap:.45rem}
a.reach svg{
  width:1.05rem;height:1.05rem;flex:none;
  transform-origin:50% 60%;
  /* one nudge shortly after the page settles, so the icon is noticed */
  animation:envelope-shake .55s ease-in-out 1.4s 2;
}
a.reach:hover svg,a.reach:focus-visible svg{animation:envelope-shake-hover .5s ease-in-out}
@keyframes envelope-shake{
  0%,100%{transform:rotate(0)}
  15%{transform:rotate(-9deg)}
  35%{transform:rotate(7deg)}
  55%{transform:rotate(-5deg)}
  75%{transform:rotate(3deg)}
}
/* same motion under a second name, so hovering replays it */
@keyframes envelope-shake-hover{
  0%,100%{transform:rotate(0)}
  15%{transform:rotate(-9deg)}
  35%{transform:rotate(7deg)}
  55%{transform:rotate(-5deg)}
  75%{transform:rotate(3deg)}
}

.colophon{
  display:flex;
  justify-content:space-between;
  gap:.6rem 2rem;
  flex-wrap:wrap;
  font-size:.8rem;
  color:var(--ink-faint);
  font-family:var(--mono);
}


/* ---------- phones ---------- */
@media (max-width:520px){
  .wrap{padding:0 1.25rem}
  /* justified text needs room; on a narrow column it opens word gaps */
  .prose p{text-align:left;-webkit-hyphens:manual;hyphens:manual}
  .topbar .wrap{gap:.9rem 1.2rem}
  nav ul{gap:1rem}
  .page-head{padding-top:2.2rem}
  section{padding:2.6rem 0}
  .cv-actions{gap:.6rem;margin-bottom:2rem}
  .colophon{gap:.35rem}
  /* long organisation names should break rather than push the page sideways */
  .svc-list .role,.pub h3{overflow-wrap:anywhere}
  /* entries wrap onto two lines at this width, so the row allowance grows to
     keep the same five entries in view */
  .news-box{--news-row:3.9rem;gap:.5rem}
}
