/* Layout adapted from pd-perry.github.io. A paper-gray ground, charcoal ink and a warm gray ladder
   between them; hairlines and whitespace instead of boxes and shadows. */
:root {
  color-scheme: light;
  /* Lightness carries hierarchy: the more important, the darker. */
  --paper: #F0EFEB;
  --ink: #1C1C1A;
  --ink-2: #4A4944;    /* body copy, 7.8:1 on paper */
  --muted: #6A6963;    /* secondary text, 4.8:1 */
  --subtle: #8F8E88;   /* non-essential captions */
  --faint: #C6C5BF;    /* underlines, outlines */
  --grid: #DEDDD6;     /* hairlines */
  --sans: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --ease: cubic-bezier(0.25, 1, 0.5, 1);  /* quartic out: fast in, fast stop, no bounce */
  --pop: cubic-bezier(0.2, 0.7, 0.3, 1.3); /* small overshoot, for the doodles only */
  --max-width: 880px;
  --year-width: 96px;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; overflow-x: clip; }

body {
  position: relative;  /* containing block for the background doodles */
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

a:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; border-radius: 2px; }

.wrap {
  position: relative;
  z-index: 1;  /* above the background doodles */
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 7rem 1.5rem 4rem;
}

/* Hero */
.hero {
  display: flex;
  gap: 3rem;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 5.5rem;
}

.hero-text {
  flex: 1 1 auto;
  min-width: 0;
}

.hero-text h1 {
  font-size: 44px;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.03em;
  margin: 0 0 0.9rem;
}

.tagline {
  margin: 0 0 1.75rem;
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.intro {
  max-width: 60ch;
  margin: 0 0 2rem;
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1.75;
  text-align: justify;
}
.intro a {
  color: var(--ink);
  font-weight: 500;
  text-decoration: underline;
  text-decoration-color: var(--faint);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  transition: text-decoration-color 0.3s var(--ease);
}
.intro a:hover { text-decoration-color: var(--ink); }

.links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

/* Capsule buttons that invert to ink on hover. */
.links a {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 0 16px;
  border: 1px solid var(--faint);
  border-radius: 999px;
  color: var(--ink);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.links a:hover { background: var(--ink); border-color: var(--ink); color: var(--paper); }

.hero-photo {
  flex: 0 0 auto;
}

.hero-photo img {
  display: block;
  width: 220px;
  height: 220px;
  object-fit: cover;
  border-radius: 20px;
  background: var(--grid);
}

/* Sections */
section { margin-bottom: 5rem; }

h2 {
  position: relative;
  margin: 0;
  padding-bottom: 0.9rem;
  font-size: 22px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.02em;
}
/* Ink rule under each section title; drawn left to right when the section is revealed. */
h2::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--ink);
  transform-origin: left center;
}

p { margin: 0 0 1rem; }

/* Education and publications: year column + details */
.entry-list {
  display: grid;
  gap: 24px;
  padding-top: 1.75rem;
}

.entry {
  display: grid;
  grid-template-columns: var(--year-width) minmax(0, 1fr);
  gap: 18px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--grid);
}
.entry:last-child { padding-bottom: 0; border-bottom: 0; }

.entry-year {
  padding-top: 2px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.entry-body {
  min-width: 0;
  text-align: justify;
}

.entry-body h3 {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.01em;
}
.entry-body h3 a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  transition: text-decoration-color 0.3s var(--ease);
}
.entry-body h3 a:hover { text-decoration-color: var(--ink); }

.entry-meta { color: var(--muted); font-size: 13.5px; line-height: 1.6; }
.entry-meta + .entry-meta { margin-top: 2px; }
.entry-meta strong { color: var(--ink); font-weight: 600; }

.author-me {
  text-decoration: underline;
  text-decoration-color: var(--faint);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* Venue as a caption line, kept in each venue's own casing (NeurIPS, arXiv, CoRL, ...). */
.entry-venue {
  margin-top: 7px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* Experience: a horizontal timeline. Logos rest as ink silhouettes and develop into their own colors on hover. */
.timeline {
  --xp-gap: 24px;
  --seg: 300ms;  /* time for the line to run from one dot to the next */
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--xp-gap);
  margin: 0;
  padding: 1.75rem 0 0;
  list-style: none;
}

.xp { position: relative; }
.xp:nth-child(2) { --i: 1; }  /* position on the timeline, for the entrance sequence */
.xp:nth-child(3) { --i: 2; }
.xp:nth-child(4) { --i: 3; }
.xp:nth-child(5) { --i: 4; }

.xp-logo {
  display: flex;
  align-items: flex-end;
  height: 26px;
}
.xp-logo img {
  display: block;
  width: auto;
  max-width: 100%;
  filter: brightness(0);
  opacity: 0.82;
  transform-origin: left bottom;
  transition: filter 0.5s var(--ease), opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
/* Heights matched by cap height; the ByteDance box includes the y descender, so it drops to the shared baseline. */
.xp-logo--caltech img { height: 18px; }
.xp-logo--bytedance img { height: 23px; margin-bottom: -5px; }
.xp-logo--penn img { height: 20px; }

/* Rail: each item's dot plus the segment running on to the next one (dashed after the current one). */
.xp-rail {
  position: relative;
  display: block;
  height: 9px;
  margin: 14px 0 12px;
}
.xp-rail::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 0;
  right: calc(-1 * var(--xp-gap));
  border-top: 1px solid var(--faint);
  transform-origin: left center;
}
.xp:last-child .xp-rail::before { right: 0; border-top-style: dashed; }
.xp-rail::after {
  content: "";
  position: absolute;
  top: 1px;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--subtle);
  transition: transform 0.45s var(--pop), background-color 0.3s var(--ease);
}
.xp--now .xp-rail::after {  /* the current one: darkest, with a ring */
  background: var(--ink);
  box-shadow: 0 0 0 3px var(--paper), 0 0 0 4px var(--ink);
}

.xp-year {
  display: block;
  color: var(--muted);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.1em;
}
.xp-title {
  display: block;
  margin: 4px 0 2px;
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.01em;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  transition: text-decoration-color 0.3s var(--ease);
}
/* Stretched link: the whole item opens the lab's site, while the advisor link sits above it. */
.xp-title::after {
  content: "";
  position: absolute;
  inset: 0;
}
.xp-meta { display: block; color: var(--muted); font-size: 12.5px; line-height: 1.6; }
.xp-meta a {
  position: relative;
  z-index: 1;             /* above the stretched lab link */
  display: inline-block;  /* wraps as a unit on narrow screens: "Advisor:" / "Prof. Steven Low" */
  max-width: 100%;
}

/* Advisor links (education and experience): a quiet underline that darkens on hover. */
:is(.entry-meta, .xp-meta) a {
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-color: var(--faint);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color 0.3s var(--ease), text-decoration-color 0.3s var(--ease);
}
:is(.entry-meta, .xp-meta) a:hover { color: var(--ink); text-decoration-color: var(--ink); }

/* Hover or keyboard focus anywhere on the item: the logo grows a touch and develops into its own colors.
   The lab title underlines too, unless the pointer is on the advisor link (so it is clear which link you'd follow). */
.xp:is(:hover, :focus-within) .xp-logo img { filter: brightness(1); opacity: 1; transform: scale(1.06); }
.xp:is(:hover, :focus-within) .xp-rail::after { transform: scale(1.4); background-color: var(--ink); }
.xp:is(:hover, :focus-within):not(:has(.xp-meta a:is(:hover, :focus-visible))) .xp-title { text-decoration-color: var(--ink); }

/* Doodles (decorative). A faint field of margin drawings behind the page, one skater on the publications rule,
   and a sticker on the footer rule. The tilts and offsets are deliberate: loose, never lined up. */
.doodle-field {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.doodle-field img {
  position: absolute;
  top: var(--top);
  left: calc(50% + var(--x));
  width: var(--w);
  height: auto;
  opacity: var(--o, 0.055);
  transform: rotate(var(--r, 0deg));
  user-select: none;
}
/* Misc page: one doodle in each margin, 24px outside the column, as wide as the margin allows (up to 240px) while
   keeping 36px from the window's edge, so neither is ever cut off; hidden where the margins are too narrow for one. */
.doodle-field--margins img { --w: min(240px, calc(50% - 476px)); }
.doodle-field--margins .doodle-field-left { left: calc(50% - 440px - var(--w)); }
.doodle-field--margins .doodle-field-right { left: calc(50% + 440px); }
@media (max-width: 1250px) { .doodle-field--margins { display: none; } }

.doodle {
  display: block;
  width: auto;
  pointer-events: none;
  user-select: none;
}

h2:has(> .doodle) { padding-right: 88px; }  /* long titles wrap before they reach the skater */

/* The skater, its wheels on the rule, stopped at the rule's end. */
.doodle--skater {
  position: absolute;
  right: 0;
  bottom: 1px;
  height: 54px;
}

/* A sticker slapped across the footer rule; its paper-colored patch hides the rule behind it. */
.doodle--sticker {
  position: absolute;
  top: -26px;
  right: 20%;  /* slapped just past the credits, not squared to the edge */
  height: 46px;
  transform: rotate(-12deg);
}

/* The way into the misc page: a caption-sized link at the bottom right of the homepage photo, flush with its edge. */
.misc-link {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
  width: fit-content;
  margin: 12px 0 0 auto;
  color: var(--ink);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-decoration: none;
  text-transform: uppercase;
}
.misc-link-arrow { transition: transform 0.3s var(--ease); }
.misc-link:hover .misc-link-arrow { transform: translateX(3px); }

/* Misc page: the homepage's frame, with paintings in place of the academic sections. Its doodles are
   part of the page, each moving the way its drawing suggests. */
.wrap--misc { padding-top: 4.5rem; overflow-y: clip; }  /* a smiley that falls out of the page never lengthens it */

.back-link {
  display: flex;
  align-items: baseline;
  gap: 0.5em;
  width: fit-content;
  margin-bottom: 2.5rem;
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-decoration: none;
  text-transform: uppercase;
  transition: color 0.3s var(--ease);
}
.back-link-arrow { transition: transform 0.3s var(--ease); }
.back-link:hover { color: var(--ink); }
.back-link:hover .back-link-arrow { transform: translateX(-3px); }

/* Three smileys stacked as drawn (each face's box in the original drawing), standing at the right end of the title's
   rule. Each takes one knock; the page's script simulates what falls, and fallen faces stay down. */
.smiley-stack {
  position: absolute;
  right: 0;
  bottom: 1px;  /* standing on the rule, at its right end */
  width: 30px;
  aspect-ratio: 430 / 1311;
}
.smiley { --amp: 0deg; position: absolute; display: block; transform-origin: 50% 100%; }
.smiley img { display: block; width: 100%; height: auto; transform-origin: 50% 100%; user-select: none; -webkit-user-drag: none; }
.smiley--1 { --n: 2; top: 0; left: 0.7%; width: 99.3%; }
.smiley--2 { --n: 1; top: 34.48%; left: 0; width: 100%; }
.smiley--3 { --n: 0; top: 69.18%; left: 3.26%; width: 94.88%; }

/* Paintings: two prints side by side, black and white until first hovered (the script keeps the color). */
.paintings {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 40px;
  align-items: start;
  padding-top: 1.75rem;
}
.painting { margin: 0; }
.painting-frame {
  display: block;
  overflow: hidden;
  border-radius: 3px;
  background: var(--ink);  /* holds the space in the paintings' own dark while they load */
  cursor: zoom-in;
}
.painting-frame img {
  display: block;
  width: 100%;
  height: auto;
  filter: grayscale(1);
  transition: transform 0.8s var(--ease), filter 1.4s var(--ease);
}
.painting-frame:hover img { transform: scale(1.025); }
.painting-frame:is(:hover, :focus-visible, .is-colored) img { filter: none; }

.painting figcaption,
.viewer figcaption {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-top: 12px;
}
.painting-title { color: var(--ink); font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
.painting-year { color: var(--muted); font-size: 12px; font-weight: 500; font-variant-numeric: tabular-nums; }


/* Full-size viewer for a painting, on the page's own paper. */
.viewer {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink);
  cursor: zoom-out;
}
.viewer[open] { display: grid; place-items: center; }
.viewer::backdrop { background: var(--paper); }
.viewer-figure { width: fit-content; margin: 0; }
.viewer-img {
  display: block;
  width: auto;
  height: auto;
  max-width: calc(100vw - 3rem);
  max-height: calc(100vh - 8rem);
  max-height: calc(100dvh - 8rem);
  border-radius: 3px;
}
.viewer-close {
  position: fixed;
  top: 1.25rem;
  right: 1.25rem;
  min-height: 34px;
  padding: 0 16px;
  border: 1px solid var(--faint);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font: 500 13px var(--sans);
  cursor: pointer;
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.viewer-close:hover { background: var(--ink); border-color: var(--ink); color: var(--paper); }
html:has(.viewer[open]) { overflow: hidden; }

/* Footer */
footer {
  position: relative;
  padding-top: 1.5rem;
  border-top: 1px solid var(--grid);
  color: var(--subtle);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
footer p { margin: 0; }
footer a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--faint);
  text-underline-offset: 3px;
  transition: color 0.3s var(--ease), text-decoration-color 0.3s var(--ease);
}
footer a:hover { color: var(--ink); text-decoration-color: var(--ink); }
footer .credit-own { color: var(--muted); }  /* the doodles are the one part not adapted from anyone */
footer .visits-count, footer .visits-count:hover { color: inherit; text-decoration: none; }  /* a quiet link: reads as plain text */

/* Motion, besides hover feedback and the doodles, is kept to three things: publications (and, on the misc page,
   paintings) rise in one by one, the experience timeline runs to the right, and the skater rides along its rule.
   Everything else is simply there. The inline script sets .in (and a 90ms stagger, --delay) as each enters the
   viewport; the timeline and the skater play once per visit (.seen-timeline, .seen-skater: already in place).
   Screen only (printing shows everything), with JS support (.js), and only if the reader allows motion. */
@media screen and (prefers-reduced-motion: no-preference) {
  .js :is(#publications .entry, .painting) {
    transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
    transition-delay: var(--delay, 0ms);
  }
  .js :is(#publications .entry, .painting):not(.in) {
    opacity: 0;
    transform: translateY(10px);
  }

  /* Timeline: one line runs to the right at a steady pace, one segment per --seg; each experience appears (drifting
     the same way, never up) and its dot pops only when the line reaches it. The line itself is never faded, only
     drawn. The pop is an animation with backwards fill only, so it never overrides the hover transition afterwards. */
  .js .timeline .xp > :not(.xp-rail) {
    transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
    transition-delay: calc(var(--delay, 0ms) + var(--i, 0) * var(--seg));
  }
  .js:not(.seen-timeline) .timeline:not(.in) .xp > :not(.xp-rail) { opacity: 0; transform: translateX(-10px); }
  .js .timeline .xp-rail::before { transition: transform var(--seg) linear calc(var(--delay, 0ms) + var(--i, 0) * var(--seg)); }
  .js:not(.seen-timeline) .timeline:not(.in) .xp-rail::before { transform: scaleX(0); }
  .js:not(.seen-timeline) .timeline:not(.in) .xp-rail::after { transform: scale(0); }
  .js .timeline.in .xp-rail::after { animation: dot-pop 0.4s var(--pop) calc(var(--delay, 0ms) + var(--i, 0) * var(--seg)) backwards; }

  /* The skater rides in along its rule when the title comes into view: a push, a slowing glide, another push,
     and a coast to a stop at the rule's end. */
  .js:not(.seen-skater) h2:not(.in) > .doodle--skater { opacity: 0; }
  .js h2.in > .doodle--skater { animation: ride 2.3s calc(var(--delay, 0ms) + 250ms) both; }

  /* Misc smileys: they drop onto the rule and stack up, bottom one first, each squashing as it lands. The entrance
     runs on the outer element and the teeter on the image, so neither restarts the other. When a face is knocked
     off, the ones still standing teeter (the script sets .is-shaken and each face's --amp). */
  .js h2:not(.in) .smiley { opacity: 0; }
  .js h2.in .smiley { animation: drop-stack 0.8s calc(var(--delay, 0ms) + 300ms + var(--n) * 230ms) backwards; }
  .smiley-stack.is-shaken .smiley:not(.is-down) img { animation: teeter 0.9s ease-in-out; }

  .viewer[open] .viewer-figure { animation: viewer-in 0.45s var(--ease); }
  .viewer[open]::backdrop { animation: fade-in 0.3s var(--ease); }
}

@keyframes drop-stack {
  0% { opacity: 0; transform: translateY(-150px); animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45); }  /* falling */
  10% { opacity: 1; }
  55% { transform: translateY(0) scale(1.1, 0.84); animation-timing-function: cubic-bezier(0.25, 1, 0.5, 1); }  /* landing */
  75% { transform: translateY(-5px) scale(0.97, 1.04); }
  100% { opacity: 1; transform: none; }
}

@keyframes teeter {  /* --amp: how far each face rocks; the top one most, the bottom one not at all */
  20% { transform: rotate(calc(var(--amp) * -1)); }
  45% { transform: rotate(calc(var(--amp) * 0.75)); }
  68% { transform: rotate(calc(var(--amp) * -0.35)); }
  85% { transform: rotate(calc(var(--amp) * 0.12)); }
}

@keyframes viewer-in {
  from { opacity: 0; transform: scale(0.985); }
}

@keyframes fade-in {
  from { opacity: 0; }
}

@keyframes dot-pop {
  from { transform: scale(0); }
  to { transform: scale(1); }
}

@keyframes ride {  /* --ride: where it starts, left of its stop. The curves meet at the same speed, so it never jerks. */
  0% { opacity: 0; transform: translateX(var(--ride, -160px)); animation-timing-function: cubic-bezier(0.25, 0.6, 0.55, 0.85); }  /* push off, slowing */
  12% { opacity: 1; }
  38% { transform: translateX(calc(var(--ride, -160px) * 0.45)); animation-timing-function: linear; }  /* glide */
  56% { transform: translateX(calc(var(--ride, -160px) * 0.36)); animation-timing-function: cubic-bezier(0.3, 0.18, 0.25, 1); }  /* push again, coast to a stop */
  100% { opacity: 1; transform: none; }
}

/* Responsive */
@media (max-width: 640px) {
  .wrap { padding-top: 4rem; }
  .hero { flex-direction: column-reverse; align-items: flex-start; gap: 2rem; margin-bottom: 4rem; }
  .hero-text h1 { font-size: 36px; }
  .hero-photo img { width: 140px; height: 140px; border-radius: 16px; }
  .wrap--misc { padding-top: 3rem; }
  .smiley-stack { width: 22px; }
  section { margin-bottom: 4rem; }
  .doodle--skater { --ride: -40px; }  /* a short roll-in */
  .doodle-field img {
    top: var(--top-sm, var(--top));
    left: calc(50% + var(--x-sm, var(--x)));
    width: var(--w-sm, var(--w));
    opacity: calc(var(--o, 0.055) * 0.75);  /* fainter where it sits behind text */
  }
  .doodle-field .sm-hide { display: none; }
  /* Justified text in a narrow column needs hyphenation to avoid wide gaps; author names stay unbroken. */
  .intro, .entry-body h3 { hyphens: auto; }
}

@media (max-width: 560px) {
  .entry { grid-template-columns: 1fr; gap: 6px; }
  .paintings { grid-template-columns: 1fr; gap: 2.5rem; }
  .timeline { --xp-gap: 14px; }
  .xp-logo { height: 20px; }
  .xp-logo--caltech img { height: 13px; }
  .xp-logo--bytedance img { height: 16.5px; margin-bottom: -3.5px; }
  .xp-logo--penn img { height: 14px; }
  .xp-title { font-size: 12.5px; }
  .xp-meta { font-size: 11.5px; }
}

@media print {
  .doodle-field, .viewer { display: none; }
}
