/* James Zhu — portfolio
   One theme: deep pine ground, warm gold accent, light-weight geometric type.
   The portrait is pre-graded against --ground (see tools/portrait.py), so if
   --ground changes the portrait has to be regenerated to match. */

/* Jost is served from this repo rather than fonts.googleapis.com, which is
   unreachable from mainland China — a render-blocking stylesheet on a host
   that never answers leaves the page blank until the request times out, and
   half the audience for this site is in Beijing. Both files are the variable
   font, so one face per subset covers the 200-500 range the design uses.
   SIL Open Font License 1.1; see assets/fonts/OFL.txt. */
@font-face {
  font-family: Jost;
  font-style: normal;
  font-weight: 200 500;
  font-display: swap;
  src: url("../assets/fonts/jost-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: Jost;
  font-style: normal;
  font-weight: 200 500;
  font-display: swap;
  src: url("../assets/fonts/jost-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --ground: #13251E;
  --ground-2: #0F1E19;
  --bar: #0C1915;
  --ink: #EFF1EC;
  --dim: #8FA096;
  --muted: #A9B4AC;
  --accent: #D9B36C;
  --accent-soft: rgba(217, 179, 108, 0.16);
  --line: rgba(255, 255, 255, 0.09);
  --maxw: 1240px;
  --pad: clamp(24px, 5vw, 60px);
  color-scheme: dark;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
  color: var(--ink);
  font-family: Jost, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  font-weight: 300;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

main { flex: 1 0 auto; }

a { color: inherit; text-decoration: none; }

img { max-width: 100%; }

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--pad);
}

/* Nav ------------------------------------------------------------------ */
.topbar {
  background: var(--bar);
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.topbar__inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--pad);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.topbar nav {
  display: flex;
  gap: clamp(18px, 2.6vw, 34px);
  overflow-x: auto;
  scrollbar-width: none;
}

.topbar nav::-webkit-scrollbar { display: none; }

.topbar nav a {
  display: block;
  padding: 15px 0;
  color: var(--dim);
  font-size: 0.74rem;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  font-weight: 400;
  white-space: nowrap;
  flex: 0 0 auto;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 0.25s ease;
}

.topbar nav a:hover { color: var(--ink); }
.topbar nav a.is-active { color: var(--ink); border-color: var(--accent); }

.topbar__lang {
  color: var(--dim);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 14px;
  white-space: nowrap;
  flex: 0 0 auto;
  transition: color 0.25s ease, border-color 0.25s ease;
}

.topbar__lang:hover { color: var(--ink); border-color: var(--accent); }

/* Hero (home) ---------------------------------------------------------- */
.hero {
  position: relative;      /* the desk pet walks along this box's floor */
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  align-items: center;
  min-height: 600px;
}

.hero__text { padding: 70px 0 80px; }

/* a clear strip along the bottom of the hero for the desk pet to walk on,
   so he never stands on the entry links */
.hero { padding-bottom: 96px; }

.hero h1 {
  margin: 0;
  font-weight: 200;
  font-size: clamp(3.4rem, 8vw, 6.6rem);
  line-height: 1;
  letter-spacing: 0.045em;
}

.hero h1 span { display: block; color: var(--accent); }

.hero__cn {
  margin: 22px 0 0;
  font-size: 1.02rem;
  letter-spacing: 0.55em;
  color: var(--dim);
}

.hero__role {
  margin: 30px 0 0;
  font-size: clamp(1.1rem, 2.2vw, 1.3rem);
  font-weight: 200;
  line-height: 1.55;
  color: var(--muted);
}


.hero__meta {
  margin: 34px 0 0;
  font-size: 0.74rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--dim);
}

.hero__links {
  margin-top: 42px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 26px;
}

.hero__links a {
  color: var(--dim);
  font-size: 0.88rem;
  transition: color 0.25s ease;
}

.hero__links a i { font-style: normal; display: inline-block; transition: transform 0.25s ease; }
.hero__links a:hover { color: var(--ink); }
.hero__links a:hover i { transform: translateX(4px); }

.hero__photo {
  position: relative;
  align-self: end;
  justify-self: center;
  width: 100%;
  max-width: 520px;
}

.hero__photo::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 14%;
  transform: translateX(-50%);
  width: 86%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent-soft), transparent 68%);
  filter: blur(26px);
}

.hero__photo img { position: relative; display: block; width: 100%; height: auto; }

/* Page header (sub-pages) ---------------------------------------------- */
.page-head {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(56px, 9vw, 104px) var(--pad) 0;
}

.page-head h1 {
  margin: 0;
  font-weight: 200;
  font-size: clamp(2.2rem, 5.4vw, 3.6rem);
  letter-spacing: 0.04em;
  line-height: 1.1;
}

.page-head p {
  margin: 16px 0 0;
  color: var(--dim);
  font-size: 1.02rem;
  max-width: 56ch;
}

.section {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(38px, 5vw, 56px) var(--pad) clamp(70px, 9vw, 110px);
}

/* Stat strip + roster -------------------------------------------------- */
.strip {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--ground-2);
}

.strip__inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 34px var(--pad);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 30px;
}

.stat b {
  display: block;
  font-size: clamp(1.9rem, 3.4vw, 2.4rem);
  font-weight: 200;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.stat:first-child b { color: var(--accent); }

.stat span {
  display: block;
  margin-top: 9px;
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
}

.roster {
  padding: 30px 0;
  border-bottom: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 6px 24px;
  align-items: baseline;
}

.roster em {
  font-style: normal;
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dim);
  margin-right: 12px;
}

.roster b { font-weight: 400; font-size: 1rem; letter-spacing: 0.04em; }

/* About ---------------------------------------------------------------- */
.about {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: clamp(36px, 5vw, 64px);
}

.about__text {
  margin: 0;
  font-size: 1.06rem;
  color: var(--muted);
}

.about__facts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.about__facts li {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}

.about__facts span {
  color: var(--dim);
  font-size: 0.74rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  padding-top: 3px;
}

.about__facts strong { font-weight: 400; text-align: right; }

/* Skills --------------------------------------------------------------- */
.groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: clamp(30px, 4vw, 48px);
}

.group h2 {
  margin: 0 0 18px;
  font-size: 0.74rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--accent);
}

.tags { display: flex; flex-wrap: wrap; gap: 9px; }

.tag {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 7px 16px;
  font-size: 0.88rem;
  transition: border-color 0.25s ease, color 0.25s ease;
}

.tag:hover { border-color: var(--accent); }

/* Work ----------------------------------------------------------------- */
.cases { display: flex; flex-direction: column; }

.case {
  display: grid;
  grid-template-columns: 1fr 1.6fr;
  gap: clamp(24px, 4vw, 56px);
  padding: clamp(28px, 4vw, 40px) 0;
  border-bottom: 1px solid var(--line);
}

.case:first-child { border-top: 1px solid var(--line); }

.case h2 {
  margin: 0;
  font-size: clamp(1.3rem, 2.4vw, 1.6rem);
  font-weight: 300;
  letter-spacing: 0.02em;
}

.case__who {
  margin-top: 8px;
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}

.case p { margin: 0; color: var(--muted); font-size: 1rem; }

.case__num {
  margin-top: 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 28px;
}

.case__num div b {
  display: block;
  font-size: 1.5rem;
  font-weight: 200;
  line-height: 1.1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.case__num div span {
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
}

/* Experience ----------------------------------------------------------- */
.timeline { display: flex; flex-direction: column; }

.job {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: clamp(20px, 3vw, 36px);
  padding: 28px 0;
  border-bottom: 1px solid var(--line);
}

.job:first-child { border-top: 1px solid var(--line); }

.job__side { padding-top: 8px; }

.job time {
  display: block;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

/* Company marks keep their own colours: flattening them to one silhouette
   turned the Sparks crest — a filled disc — into a featureless white dot, and
   a stacked wordmark into a smudge. They are held slightly back at rest so the
   rail stays quiet, and come forward on hover. Backgrounds are matted out by
   tools/logos.py; a dark mark is inverted there rather than in CSS, so this
   rule stays the same for every logo. */
.job__logo {
  display: block;
  width: auto;
  height: auto;
  max-height: 38px;
  max-width: 140px;
  object-fit: contain;
  object-position: left center;
  margin-bottom: 14px;
  opacity: 0.82;
  transition: opacity 0.28s ease;
}

.job:hover .job__logo { opacity: 1; }

.job h2 { margin: 0; font-size: 1.34rem; font-weight: 300; letter-spacing: 0.02em; }

.job__org {
  margin-top: 3px;
  font-size: 0.74rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--accent);
}

.job p { margin: 12px 0 0; color: var(--muted); font-size: 0.98rem; max-width: 66ch; }

.honors { margin-top: clamp(44px, 6vw, 68px); }

.honors h2 {
  margin: 0 0 18px;
  font-size: 0.74rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--accent);
}

.honors ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }

.honors li { position: relative; padding-left: 20px; color: var(--muted); font-size: 0.97rem; }

.honors li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 12px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}

/* Desk pet (home page) -------------------------------------------------- */
.pet {
  position: absolute;
  left: 0;
  bottom: 12px;
  z-index: 40;
  width: 76px;
  height: 57px;
  cursor: pointer;
  will-change: transform;
  -webkit-tap-highlight-color: transparent;
}

.pet__sprite {
  display: block;
  width: 100%;
  height: 100%;
  transition: transform 0.2s ease;
}

.pet__sprite svg {
  display: block;
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45));
}

/* the ground shadow keeps him standing on the page rather than floating */
.pet::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 1px;
  transform: translateX(-50%);
  width: 44px;
  height: 7px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.38);
  filter: blur(4px);
}

/* Legs, tail and ear only move while he is actually going somewhere. */
.pet-leg-a, .pet-leg-b, .pet-tail, .pet-ear { transform-box: fill-box; }
.pet-leg-a, .pet-leg-b { transform-origin: 50% 6%; }
.pet-tail { transform-origin: 88% 92%; }
.pet-ear { transform-origin: 55% 8%; }

.pet.is-walking .pet-leg-a { animation: pet-step-a 0.52s ease-in-out infinite; }
.pet.is-walking .pet-leg-b { animation: pet-step-b 0.52s ease-in-out infinite; }
.pet.is-walking .pet-ear { animation: pet-flop 0.52s ease-in-out infinite; }
.pet-tail { animation: pet-wag 0.75s ease-in-out infinite; }
.pet.is-walking .pet-tail { animation-duration: 0.42s; }

@keyframes pet-step-a { 0%, 100% { transform: rotate(14deg); } 50% { transform: rotate(-14deg); } }
@keyframes pet-step-b { 0%, 100% { transform: rotate(-14deg); } 50% { transform: rotate(14deg); } }
@keyframes pet-wag { 0%, 100% { transform: rotate(-16deg); } 50% { transform: rotate(16deg); } }
@keyframes pet-flop { 0%, 100% { transform: rotate(-6deg); } 50% { transform: rotate(7deg); } }

.pet__say {
  position: absolute;
  bottom: 62px;
  left: 50%;
  transform: translate(-50%, 6px);
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--accent);
  color: #16281F;
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}

.pet__say.is-on { opacity: 1; transform: translate(-50%, 0); }

/* Reduce motion: Milo stays on the page, he just holds still. js/pet.js stops
   walking him to match, so nothing here is left animating a stationary dog. */
@media (prefers-reduced-motion: reduce) {
  .pet-leg-a, .pet-leg-b, .pet-tail, .pet-ear { animation: none; }
  .pet__sprite { transition: none; }
  .pet__say { transition: opacity 0.22s ease; transform: translate(-50%, 0); }
}

/* 404 ------------------------------------------------------------------ */
/* Scoped under .page-head: these are <p> elements inside it, and the bare
   class (0,1,0) loses to `.page-head p` (0,1,1) on size, colour and margin. */
.page-head .notfound__code {
  margin: 0 0 12px;
  font-size: clamp(3.4rem, 9vw, 6rem);
  font-weight: 200;
  line-height: 1;
  color: var(--accent);
  letter-spacing: 0.04em;
}

.page-head .notfound__en { margin-top: 10px; color: var(--dim); font-size: 0.94rem; }

.page-head .notfound__back { margin-top: 34px; display: flex; flex-wrap: wrap; gap: 26px; }

.notfound__back a { color: var(--muted); transition: color 0.25s ease; }
.notfound__back a:hover { color: var(--ink); }
.notfound__back i { font-style: normal; display: inline-block; transition: transform 0.25s ease; }
.notfound__back a:hover i { transform: translateX(4px); }

/* Milo ----------------------------------------------------------------- */
.milo__intro {
  margin: 0 0 clamp(34px, 5vw, 52px);
  max-width: 62ch;
  font-size: 1.06rem;
  color: var(--muted);
}

/* Editorial grid: the first shot runs 2x2 as the feature, the rest fill in
   beside it. A "wide" shot spans two columns to close the gap left when the
   remaining count would otherwise strand an empty cell. */
.shots {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(16px, 2.2vw, 26px);
  /* size each figure to its own content; only the feature is stretched to fill
     its two rows, otherwise every image gets a definite height, overriding
     aspect-ratio and cropping the subject out of frame */
  align-items: start;
}

.shot { margin: 0; display: grid; grid-template-rows: 1fr auto; }

.shot img {
  display: block;
  width: 100%;
  height: auto;            /* the tag carries height="800"; without this the
                              attribute wins and cover crops the subject out */
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 3px;
  filter: saturate(0.92) brightness(0.96);
  transition: filter 0.35s ease, transform 0.35s ease;
}

.shot--feature { grid-column: span 2; grid-row: span 2; align-self: stretch; }
.shot--feature img { aspect-ratio: auto; height: 100%; }

.shot--wide { grid-column: span 2; }
.shot--wide img { aspect-ratio: 3 / 2; }

.shot:hover img { filter: none; transform: translateY(-4px); }

.shot figcaption {
  margin-top: 14px;
  font-size: 0.9rem;
  color: var(--dim);
}

.shot figcaption b {
  display: block;
  font-weight: 400;
  font-size: 0.74rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 5px;
}

/* Contact -------------------------------------------------------------- */
.contact { display: flex; flex-direction: column; align-items: flex-start; gap: 22px; }

.contact__email {
  font-size: clamp(1.4rem, 4vw, 2.2rem);
  font-weight: 200;
  letter-spacing: 0.01em;
  color: var(--accent);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s ease;
}

.contact__email:hover { border-color: var(--accent); }

.contact__links { display: flex; gap: 26px; }

.contact__links a {
  color: var(--dim);
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  border-bottom: 1px solid transparent;
  transition: color 0.25s ease, border-color 0.25s ease;
}

.contact__links a:hover { color: var(--ink); border-color: var(--accent); }

/* Next-page link ------------------------------------------------------- */
.next {
  margin-top: clamp(48px, 7vw, 76px);
  padding-top: 28px;
  border-top: 1px solid var(--line);
}

.next a {
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
  transition: color 0.25s ease;
}

.next a i { font-style: normal; display: inline-block; transition: transform 0.25s ease; }
.next a:hover { color: var(--accent); }
.next a:hover i { transform: translateX(5px); }

/* Footer --------------------------------------------------------------- */
.footer {
  border-top: 1px solid var(--line);
  padding: 30px var(--pad);
  text-align: center;
  color: var(--dim);
  font-size: 0.78rem;
  letter-spacing: 0.1em;
}

/* Reveal --------------------------------------------------------------- */
.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}

.reveal.is-visible { opacity: 1; transform: none; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

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

/* Responsive ----------------------------------------------------------- */
@media (max-width: 900px) {
  .hero { grid-template-columns: 1fr; min-height: 0; text-align: center; }
  .hero__photo { order: -1; max-width: 320px; margin-top: 26px; }
  .hero__text { padding: 26px 0 60px; order: 1; }

  /* Milo walks under the portrait instead of at the foot of the hero. Stacked
     on a phone the hero runs taller than the screen, so the bottom strip put
     him a third of a screen below the fold — a desk pet nobody scrolls to is
     no desk pet. As a grid item between the photo and the text he lands on
     the first screen, and the reserved floor strip is no longer needed. */
  .hero { padding-bottom: 0; }
  .pet {
    position: relative;
    left: auto;
    bottom: auto;
    order: 0;
    justify-self: start;
    margin: 8px 0 0;
  }
  .hero__links { justify-content: center; }
  .hero__role { margin-left: auto; margin-right: auto; }
  .strip__inner { grid-template-columns: repeat(2, 1fr); gap: 26px; }
  .about { grid-template-columns: 1fr; }
  .about__facts strong { text-align: left; }
  .case, .job { grid-template-columns: 1fr; gap: 6px; }
  .shots { grid-template-columns: repeat(2, 1fr); }
  .shot--feature { grid-column: span 2; grid-row: auto; }
  .shot--feature img { aspect-ratio: 4 / 3; height: auto; }
  .job time { padding-top: 0; }
  .case__who { margin-bottom: 10px; }
}
