/* ------------------------------------------------------------------ *
   Ellen D. Zhong — personal site

   Two grounds, one system: vitreous ice by default, paper on request.
   Deep teal carries structure, sienna marks anything you can touch.
 * ------------------------------------------------------------------ */

:root {
  /* ---- light (default) ---- */
  --paper-a:  #ffffff;
  --paper-b:  #fafbfb;
  --scrim:    255, 255, 255;

  --text:    #101417;
  --dim:     #4a5457;
  --faint:   #798385;
  --rule:    rgba(16, 42, 42, 0.14);
  --rule-hi: rgba(16, 42, 42, 0.34);

  /* --ice only ever lands on display type and graphics in this theme
     (surname, card edge, toggle, dots, focus ring), so it answers to the
     3:1 threshold rather than 4.5:1 — which buys real chroma on white. */
  --ice:   #009688;   /* 3.67:1 on white */
  --flame: #c2410c;   /* 5.18:1 — carries link text, so it stays above 4.5 */

  --tint-cool: rgba(0, 150, 136, 0.14);
  --tint-warm: rgba(194, 65, 12, 0.07);

  --panel:  rgba(255, 255, 255, 0.93);
  --card-wash:     linear-gradient(100deg,
                     rgba(0, 150, 136, 0.26), rgba(0, 150, 136, 0) 66%);
  --card-bg:       rgba(255, 255, 255, 0.62);
  --card-bg-hover: rgba(255, 255, 255, 0.82);
  --grain-opacity: 0.03;
  --photo-filter: none;

  /* one family throughout; hierarchy comes from weight, size and case.
     Each role still has its own token, so any of them can be split back out. */
  --face:    "Karla", "Helvetica Neue", Helvetica, sans-serif;
  --sans:    var(--face);
  --display: var(--face);
  --name:    var(--face);
  --mono:    var(--face);

  --gutter: clamp(1.4rem, 5vw, 4.5rem);
}

:root[data-theme="dark"] {
  --paper-a:  #05090b;
  --paper-b:  #04080a;
  --scrim:    5, 9, 11;

  --text:    #dae7e6;
  --dim:     #93a8ab;
  --faint:   #62777b;
  --rule:    rgba(146, 200, 200, 0.15);
  --rule-hi: rgba(146, 200, 200, 0.34);

  --ice:   #74e0d0;
  --flame: #ff9f57;

  --tint-cool: rgba(50, 148, 138, 0.16);
  --tint-warm: rgba(255, 133, 66, 0.075);

  --panel: rgba(6, 12, 15, 0.86);
  --card-wash:     linear-gradient(100deg,
                     rgba(116, 224, 208, 0.11), rgba(116, 224, 208, 0) 62%);
  --card-bg:       rgba(8, 15, 18, 0.55);
  --card-bg-hover: rgba(10, 19, 22, 0.72);
  --grain-opacity: 0.16;
  --photo-filter: saturate(0.9) brightness(0.92);
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* on the root so it stays behind the negative-z-index canvas */
  background:
    radial-gradient(1100px 720px at 78% 8%, var(--tint-cool), transparent 62%),
    radial-gradient(760px 620px at 6% 96%,  var(--tint-warm), transparent 66%),
    linear-gradient(var(--paper-a), var(--paper-b));
  background-attachment: fixed;
}

body {
  margin: 0;
  min-height: 100vh;
  color: var(--text);
  font-family: var(--sans);
  font-size: clamp(0.96rem, 0.32vw + 0.88rem, 1.03rem);
  font-weight: 400;
  line-height: 1.64;
  letter-spacing: 0;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ---------- layers ------------------------------------------------- */

#stage {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -3;
  pointer-events: none;
}

/* keeps the type crisp where the molecule drifts behind it */
.scrim {
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background: linear-gradient(100deg,
    rgba(var(--scrim), 0.94) 0%,
    rgba(var(--scrim), 0.9)  30%,
    rgba(var(--scrim), 0.45) 52%,
    rgba(var(--scrim), 0)    72%);
}

.grain {
  position: fixed;
  inset: -50%;
  z-index: -1;
  pointer-events: none;
  opacity: var(--grain-opacity);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: drift 900ms steps(3) infinite;
}
@keyframes drift {
  0%   { transform: translate(0, 0); }
  33%  { transform: translate(-7px, 4px); }
  66%  { transform: translate(5px, -6px); }
  100% { transform: translate(0, 0); }
}

/* ---------- shell -------------------------------------------------- */

.shell {
  position: relative;
  max-width: 1220px;
  margin: 0 auto;
  padding: clamp(3.5rem, 11vh, 7.5rem) var(--gutter) 4rem;
}

.shell > * { max-width: 39rem; }

/* ---------- type primitives ---------------------------------------- */

/* the serif carries every label; the mono survives only in the readout,
   where instrument type actually belongs */
.label, time, .links, .contact-list dt, .lab-kicker {
  font-family: var(--display);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: 0.005em;
  text-transform: none;
}

h1 {
  margin: 0;
  font-family: var(--name);
  font-weight: 800;
  font-size: clamp(2.2rem, 5.3vw, 3.7rem);
  line-height: 1.08;
  letter-spacing: 0.004em;
  color: var(--text);
}
h1 em {
  font-style: normal;
  color: var(--ice);
}

.tagline {
  margin: 1.1rem 0 0;
  max-width: 31rem;
  font-size: 1.16em;
  line-height: 1.4;
  font-weight: 400;
  color: var(--dim);
  letter-spacing: -0.004em;
  text-wrap: balance;
}

.label {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  color: var(--text);
  margin: 0 0 1.5rem;
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: -0.012em;
}
.label .num {
  color: var(--flame);
  font-weight: 500;
  font-size: 0.82em;
}
.label::after {
  content: "";
  flex: 1;
  align-self: center;
  height: 1px;
  background: linear-gradient(90deg, var(--rule), transparent);
}

/* ---------- links -------------------------------------------------- */

a {
  color: inherit;
  text-decoration: none;
  background-image: linear-gradient(var(--flame), var(--flame));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 1px;
  transition: color 220ms ease, background-size 220ms ease;
}
a:hover, a:focus-visible {
  color: var(--flame);
  background-size: 100% 2px;
}
:focus-visible { outline: 1px dashed var(--ice); outline-offset: 4px; }

/* ---------- theme toggle ------------------------------------------- */

#theme-toggle {
  --spin: 0deg;
  --pop: 1;
  position: fixed;
  top: clamp(1rem, 2.6vw, 1.9rem);
  right: clamp(1rem, 2.6vw, 1.9rem);
  z-index: 6;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  padding: 0;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 50%;
  cursor: pointer;
  backdrop-filter: blur(7px);
  transition: border-color 260ms ease, box-shadow 260ms ease;
}
#theme-toggle:hover {
  --pop: 1.12;
  border-color: var(--rule-hi);
  box-shadow: 0 0 0 4px var(--tint-cool);
}

#theme-toggle svg {
  width: 1.35rem;
  height: 1.35rem;
  display: block;
}
#theme-toggle .mol {
  transform: rotate(var(--spin)) scale(var(--pop));
  transform-origin: 12px 12px;
  transform-box: view-box;
  transition: transform 620ms cubic-bezier(.2, .8, .3, 1);
}
#theme-toggle line {
  stroke: var(--rule-hi);
  stroke-width: 1.15;
  stroke-linecap: round;
  transition: stroke 400ms ease;
}
#theme-toggle circle {
  fill: var(--paper-a);
  stroke: var(--ice);
  stroke-width: 1.4;
  transition: fill 460ms ease, stroke 400ms ease;
}
#theme-toggle:hover line { stroke: var(--flame); }

/* the structure fills in with ink when the lights go out */
:root[data-theme="dark"] #theme-toggle { --spin: 60deg; }
:root[data-theme="dark"] #theme-toggle line { stroke: var(--dim); }
:root[data-theme="dark"] #theme-toggle circle { fill: var(--ice); }
:root[data-theme="dark"] #theme-toggle .core { fill: var(--flame); stroke: var(--flame); }

/* ---------- portrait ----------------------------------------------- */

/* ---------- hero ---------------------------------------------------- */

.hero {
  /* the hero keeps the prose measure, so its right edge lines up with the
     text below it and the molecule gets the whole margin to itself */
  --photo: 13.5rem;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--photo);
  gap: 0 clamp(1.2rem, 3vw, 2.2rem);
  align-items: start;
}

/* matching the photo's height and pushing the links to the bottom gives the
   two columns a shared top and bottom edge, so the photo sits in a
   composition instead of floating in the margin */
.hero-text {
  min-height: var(--photo);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.hero .links { grid-column: 1 / -1; }


/* ---------- portrait ----------------------------------------------- */

.portrait {
  width: 100%;
  margin: 0;
}
.portrait img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  object-position: 50% 22%;
  border: 1px solid var(--rule);
  border-radius: 2px;
  filter: var(--photo-filter);
  transition: filter 400ms ease, border-color 400ms ease;
}
.portrait:hover img { border-color: var(--rule-hi); filter: none; }
/* ---------- about -------------------------------------------------- */

.bio { margin-top: clamp(2.8rem, 8vh, 4.5rem); }

.prose p { margin: 0 0 1.05em; }
.prose p:last-of-type { margin-bottom: 0; }

/* a plain list inside the prose, not a section of its own */
.engagements {
  list-style: none;
  margin: 0.35rem 0 0;
  padding-left: 1.15rem;
  color: var(--dim);
}
.engagements li {
  position: relative;
  padding-left: 1.05rem;
  margin-bottom: 0.3em;
}
.engagements li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.66em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ice);
  opacity: 0.85;
}
.engagements a { color: var(--text); }

/* ---------- lab callout -------------------------------------------- */

.lab-callout { margin: clamp(3.2rem, 8vh, 5.5rem) 0 0; }

.lab-card {
  display: block;
  padding: 1.6rem 1.8rem;
  border: 1px solid var(--rule);
  border-left: 2px solid var(--ice);
  /* translucent enough to let the ground through, tinted enough to lift */
  background-image: var(--card-wash);
  background-color: var(--card-bg);
  /* the global `a` rule paints link underlines via background-size: 100% 1px,
     which would squash this wash into a hairline at the card's bottom edge */
  background-size: 100% 100%;
  background-position: 0 0;
  transition: border-color 280ms ease, background-color 280ms ease,
              transform 280ms cubic-bezier(.2,.8,.3,1);
}
.lab-card:hover {
  border-color: var(--rule-hi);
  border-left-color: var(--flame);
  background-color: var(--card-bg-hover);
  transform: translateX(5px);
  color: inherit;
}
.lab-kicker {
  display: block;
  font-style: italic;
  font-weight: 400;
  color: var(--faint);
  margin-bottom: 0.35rem;
}
.lab-name {
  display: block;
  font-size: 1.6rem;
  font-weight: 600;
  letter-spacing: -0.018em;
  line-height: 1.1;
  color: var(--text);
}
.lab-name .arrow {
  display: inline-block;
  color: var(--ice);
  transition: transform 280ms cubic-bezier(.2,.8,.3,1), color 280ms ease;
}
.lab-card:hover .arrow { transform: translateX(9px); color: var(--flame); }
.lab-blurb {
  display: block;
  margin-top: 0.5rem;
  color: var(--dim);
  font-size: 0.94em;
  max-width: 30rem;
}

/* ---------- news --------------------------------------------------- */

.news { margin-top: clamp(3.6rem, 11vh, 7rem); }
.news .label { margin-bottom: 0.25rem; }

/* The scroller wraps the list rather than being it, so the timeline's rule
   still spans the full run of entries instead of just the visible window.
   The trailing padding gives the fade empty space to land on at the end. */
.news-scroll {
  max-height: 24rem;
  overflow-y: auto;
  padding: 1.4rem 1rem 2.6rem 0;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 1.4rem,
                        #000 calc(100% - 2.6rem), transparent);
          mask-image: linear-gradient(to bottom, transparent, #000 1.4rem,
                        #000 calc(100% - 2.6rem), transparent);
  scrollbar-width: thin;
  scrollbar-color: var(--rule-hi) transparent;
}
.news-scroll::-webkit-scrollbar { width: 4px; }
.news-scroll::-webkit-scrollbar-track { background: transparent; }
.news-scroll::-webkit-scrollbar-thumb {
  background: var(--rule-hi);
  border-radius: 2px;
}

/* the spine is carried by the items themselves rather than one absolute rule,
   so a single entry can break it where the record actually skips */
.timeline {
  list-style: none;
  margin: 0;
  /* room for the dots, which straddle the spine and would otherwise be
     clipped by the scroll container's padding box */
  padding: 0 0 0 8px;
}
.timeline li {
  position: relative;
  padding: 0 0 1.6rem 1.6rem;
  border-left: 1px solid var(--rule);
}
.timeline li:last-child { padding-bottom: 0; }
.timeline li::before {
  content: "";
  position: absolute;
  left: -5px;
  top: 0.5rem;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--paper-a);
  border: 1px solid var(--rule-hi);
  transition: background 260ms ease, border-color 260ms ease, box-shadow 260ms ease;
}
.timeline li:first-child::before {
  background: var(--ice);
  border-color: var(--ice);
  box-shadow: 0 0 0 3px var(--tint-cool);
}
.timeline li:hover::before {
  background: var(--flame);
  border-color: var(--flame);
  box-shadow: 0 0 0 4px var(--tint-warm);
}
/* an explicit break in the run, so the jump from 2024 back to 2022 reads as
   omitted history rather than as an evenly spaced interval */
.timeline li.elision {
  min-height: 2.4rem;
  padding-bottom: 1.4rem;
  border-left-color: var(--ice);
  border-left-style: dashed;
}
.timeline li.elision::before { display: none; }

.timeline time {
  display: block;
  color: var(--faint);
  font-size: 0.92rem;
  font-style: italic;
  margin-bottom: 0.15rem;
  transition: color 260ms ease;
}
.timeline li:hover time { color: var(--flame); }
.timeline p {
  margin: 0;
  font-size: 0.96em;
  color: var(--dim);
}
.timeline p a { color: var(--text); }

/* ---------- footer ------------------------------------------------- */

/* ---------- profile links (hero) ------------------------------------ */

.links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding-top: 1.15rem;
  font-size: 0.95rem;
}
.links a {
  padding: 0.28rem 0.85rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--dim);
  background-image: none;          /* not an underlined link */
  background-color: transparent;
  transition: color 200ms ease, border-color 200ms ease, background-color 200ms ease;
}
.links a:hover, .links a:focus-visible {
  color: var(--flame);
  border-color: var(--flame);
  background-color: var(--tint-warm);
}

/* ---------- contact ------------------------------------------------- */

.contact { margin-top: clamp(3.6rem, 11vh, 7rem); }

.contact-list {
  display: grid;
  grid-template-columns: 6rem 1fr;
  gap: 0.85rem 1.6rem;
  margin: 0;
}
.contact-list dt {
  color: var(--faint);
  font-size: 0.95rem;
  padding-top: 0.1em;
}
.contact-list dd {
  margin: 0;
  color: var(--dim);
  font-size: 0.97em;
  line-height: 1.5;
}
.contact-list .at { color: var(--rule-hi); }

/* ---------- hud ---------------------------------------------------- */

.hud {
  position: fixed;
  right: clamp(1rem, 3vw, 2.2rem);
  bottom: clamp(1rem, 3vw, 2.2rem);
  z-index: 5;
  width: 15rem;
  padding: 0.85rem 0.95rem 0.8rem;
  border: 1px solid var(--rule);
  background: var(--panel);
  backdrop-filter: blur(7px);
  font-family: var(--mono);
  font-size: 0.57rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--faint);
  opacity: 0.5;
  transition: opacity 300ms ease, border-color 300ms ease;
}
.hud:hover, .hud:focus-within { opacity: 1; border-color: var(--rule-hi); }

.hud-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.3rem;
}
.hud-val {
  color: var(--ice);
  font-variant-numeric: tabular-nums;
  text-transform: none;
  letter-spacing: 0.06em;
}

#hud-scrub {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 14px;
  margin: 0.5rem 0 0.35rem;
  background: transparent;
  cursor: ew-resize;
}
#hud-scrub::-webkit-slider-runnable-track { height: 1px; background: var(--rule-hi); }
#hud-scrub::-moz-range-track { height: 1px; background: var(--rule-hi); }
#hud-scrub::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 3px;
  height: 13px;
  margin-top: -6px;
  border: 0;
  border-radius: 0;
  background: var(--flame);
}
#hud-scrub::-moz-range-thumb {
  width: 3px;
  height: 13px;
  border: 0;
  border-radius: 0;
  background: var(--flame);
}

#hud-next {
  width: 100%;
  padding: 0.42rem 0;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--dim);
  font: inherit;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 200ms ease, border-color 200ms ease;
}
#hud-next:hover { color: var(--flame); border-color: var(--flame); }

/* ---------- entrance ----------------------------------------------- */

/* hidden inside the keyframe, not on the element, so content still shows
   if the animation never runs */
.reveal {
  animation: rise 900ms cubic-bezier(.2, .75, .25, 1) both;
  animation-delay: var(--d, 0ms);
}
@keyframes rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- responsive --------------------------------------------- */

@media (max-width: 900px) {
  .scrim {
    background: linear-gradient(180deg,
      rgba(var(--scrim), 0.82) 0%,
      rgba(var(--scrim), 0.9) 40%,
      rgba(var(--scrim), 0.95) 100%);
  }
  .shell > * { max-width: none; }
  .hero { grid-template-columns: 1fr; gap: 0; }
  .hero-text { min-height: 0; }
  .portrait { width: 260px; max-width: 100%; margin-top: clamp(2rem, 6vh, 3rem); }
  .hero .links { padding-top: clamp(1.6rem, 4.5vh, 2.2rem); }
  .hud { width: 11.5rem; opacity: 0.95; }
  .news-scroll { max-height: 21rem; }
  .hud .hud-row:first-child { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .grain { animation: none; }
  .reveal { opacity: 1; transform: none; animation: none; }
  * { transition-duration: 1ms !important; }
}
