@charset "UTF-8";

/* ==========================================================================
   Junwen Rao — résumé
   Editorial magazine layout. Cream surface, single blue hue, serif display.

   Structure
     1. Tokens          design system: colour, type scale, spacing, motion
     2. Reset & base
     3. Language switch pure-CSS zh/en visibility
     4. Layout          the editorial grid
     5. Chrome          skip link, top bar, progress rail, nav
     6. Hero
     7. Section shell   eyebrow numbers, rules, headings
     8. Charts          delta bars, meters, funnel, proportion bars, stat tiles
     9. Experience
    10. Education / skills / closing
    11. Reveal          scroll-triggered entrances
    12. Responsive
    13. Print          A4, chart fallbacks, animation off
   ========================================================================== */

/* ==========================================================================
   1. Tokens
   ========================================================================== */

:root {
  /* --- Surface & ink -----------------------------------------------------
     Contrast measured against the cream surface (#FFFDD0):
       blue   8.37:1   deep 12.65:1   ink 16.58:1   warm 5.48:1
     All clear WCAG AA for body text. `--rule` sits at 3.55:1, which is the
     UI-component threshold — it is used for borders, never for text. */
  --cream: #fffdd0;
  --cream-deep: #f7f3c0; /* recessed panels */
  --paper: #fffef0; /* raised cards, one step lighter than the page */
  --blue: #00489d;
  --deep: #002f66;
  --ink: #0b1b33;
  --warm: #6b6752; /* secondary text — 5.48:1 on cream */
  --rule: #7286a8; /* hairlines & borders — 3.55:1, not for text */
  --rule-soft: #c9d4e6; /* decorative only, never load-bearing */

  /* --- Chart ramp --------------------------------------------------------
     One hue, monotone lightness. Validated as an ordinal ramp: monotone L,
     adjacent ΔL ≥ 0.06, light end ≥ 2:1 on surface, hue spread 4°.
     Adjacent steps sit at only ~1.5–2:1 against each other, so series are
     NEVER distinguished by colour alone — every mark is directly labelled. */
  --c-1: #002f66; /* darkest — "after" / primary stage */
  --c-2: #00489d; /* primary */
  --c-3: #3d79bf; /* mid — "before" / secondary stage */
  --c-track: #8fb5db; /* meter track, 2.06:1 on cream */
  --c-wash: #dce7f4; /* area wash, decorative fill only */

  /* --- Type -------------------------------------------------------------- */
  --font-display: 'Newsreader', 'Noto Serif SC', 'Songti SC', 'SimSun', Georgia, serif;
  --font-body: 'Inter', 'Noto Sans SC', -apple-system, 'Segoe UI', 'Microsoft YaHei', sans-serif;

  /* Fluid scale, 1.25 ratio at the small end opening to ~1.33 for display.
     clamp() keeps every step readable from 320px to 1600px without steps. */
  --t-eyebrow: 0.6875rem; /* 11px — small-caps labels */
  --t-micro: 0.75rem; /* 12px — captions, sources */
  --t-small: 0.8125rem; /* 13px — chart labels */
  --t-body: clamp(0.9375rem, 0.9rem + 0.2vw, 1.0625rem);
  --t-lead: clamp(1.0625rem, 1rem + 0.4vw, 1.3125rem);
  --t-h3: clamp(1.125rem, 1.05rem + 0.4vw, 1.375rem);
  --t-h2: clamp(1.75rem, 1.4rem + 1.6vw, 3rem);
  --t-figure: clamp(2rem, 1.6rem + 2vw, 3.25rem);
  --t-display: clamp(3rem, 1.5rem + 7vw, 8.5rem);

  --lh-tight: 1.06;
  --lh-snug: 1.28;
  --lh-body: 1.72;

  /* --- Space ------------------------------------------------------------
     A 4px base, doubling. Section rhythm is fluid so long pages breathe on
     wide screens without leaving phones over-padded. */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;
  --s-9: 6rem;
  --s-section: clamp(4.5rem, 9vw, 9rem);
  --gutter: clamp(1.25rem, 5vw, 4.5rem);
  --measure: 68ch; /* body copy line length ceiling */

  /* --- Motion ----------------------------------------------------------- */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-fast: 180ms;
  --dur: 420ms;
  --dur-slow: 900ms;
  --dur-chart: 1100ms;

  --maxw: 78rem;
  --radius: 2px; /* editorial = near-square; 4px only on chart data-ends */
}

/* ==========================================================================
   2. Reset & base
   ========================================================================== */

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

html {
  /* Lenis drives scrolling; native smooth-scroll would fight it. Anchor jumps
     are handled in JS. Without Lenis (no-JS), this stays a normal document. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  font-weight: 400;
  /* Optical sizing is pinned in the subset; opt into kerning + ligatures. */
  font-kerning: normal;
  font-variant-ligatures: common-ligatures;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* Lenis: the library transforms a wrapper, so the page must not also scroll. */
html.lenis,
html.lenis body {
  height: auto;
}
.lenis.lenis-smooth {
  scroll-behavior: auto !important;
}
.lenis.lenis-stopped {
  overflow: hidden;
}

h1,
h2,
h3,
h4,
p,
figure,
blockquote,
ul,
ol,
dl,
dd {
  margin: 0;
}

ul,
ol {
  padding: 0;
  list-style: none;
}

a {
  color: var(--blue);
  text-decoration: none;
  text-underline-offset: 0.22em;
  text-decoration-thickness: from-font;
}

a:hover {
  text-decoration: underline;
}

/* A single visible focus treatment for every interactive element. */
:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: var(--radius);
}

img,
svg {
  display: block;
  max-width: 100%;
}

button {
  font: inherit;
  color: inherit;
  margin: 0;
  cursor: pointer;
}

::selection {
  background: var(--blue);
  color: var(--cream);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   3. Language switch
   --------------------------------------------------------------------------
   Both languages are present in the HTML. `<html lang>` decides which set is
   displayed, so switching is a single attribute write: no reflow of the whole
   document, no re-render, no flash, and the page still works with JS disabled
   (Chinese shows by default). Search engines index both.
   ========================================================================== */

[data-lang] {
  display: none;
}

html[lang^='zh'] [data-lang='zh'],
html[lang^='en'] [data-lang='en'] {
  display: revert;
}

/* Inline variants keep their display type inside running text. */
html[lang^='zh'] span[data-lang='zh'],
html[lang^='en'] span[data-lang='en'] {
  display: inline;
}

/* Chinese sets slightly tighter and heavier than Latin at the same size —
   Noto Sans SC has a larger x-height, so it is stepped down a touch. */
html[lang^='zh'] body {
  font-size: calc(var(--t-body) * 0.97);
  line-height: 1.85;
}

/* ==========================================================================
   4. Layout
   ========================================================================== */

.shell {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The editorial grid: a narrow label rail, then the content column.
   Collapses to a single column below 900px. */
.grid {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: start;
}

.measure {
  max-width: var(--measure);
}

/* ==========================================================================
   5. Chrome
   ========================================================================== */

.skip-link {
  position: absolute;
  top: var(--s-3);
  left: var(--s-3);
  z-index: 100;
  padding: var(--s-2) var(--s-4);
  background: var(--blue);
  color: var(--cream);
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  transform: translateY(-200%);
  transition: transform var(--dur-fast) var(--ease-out);
}

.skip-link:focus {
  transform: translateY(0);
  text-decoration: none;
}

/* --- Top bar ------------------------------------------------------------
   Stays out of the way: transparent over the hero, then picks up a cream
   backdrop and a hairline once the page scrolls (`.is-stuck` from JS). */
.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--gutter);
  background: transparent;
  border-bottom: 1px solid transparent;
  transition:
    background var(--dur) var(--ease-out),
    border-color var(--dur) var(--ease-out),
    backdrop-filter var(--dur) var(--ease-out);
}

.topbar.is-stuck {
  background: color-mix(in srgb, var(--cream) 88%, transparent);
  backdrop-filter: blur(12px) saturate(1.4);
  border-bottom-color: var(--rule-soft);
}

.topbar__mark {
  font-family: var(--font-display);
  font-size: 1rem;
  letter-spacing: 0.02em;
  color: var(--deep);
  white-space: nowrap;
}

.topbar__actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

/* --- Language toggle ---------------------------------------------------
   A two-state segmented control. The thumb is a single translated pill, so
   the movement reads as one object sliding rather than two boxes swapping. */
.langtoggle {
  position: relative;
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: color-mix(in srgb, var(--cream) 70%, transparent);
}

.langtoggle__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(50% - 2px);
  height: calc(100% - 4px);
  border-radius: 999px;
  background: var(--blue);
  transition: transform var(--dur) var(--ease-out);
  pointer-events: none;
}

html[lang^='en'] .langtoggle__thumb {
  transform: translateX(100%);
}

.langtoggle__btn {
  position: relative;
  z-index: 1;
  min-width: 3.25rem;
  /* 24px minimum pointer target (WCAG 2.2 AA). */
  min-height: 24px;
  padding: 0.3rem 0.65rem;
  border: 0;
  border-radius: 999px;
  background: none;
  font-size: var(--t-eyebrow);
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--deep);
  transition: color var(--dur) var(--ease-out);
}

.langtoggle__btn[aria-pressed='true'] {
  color: var(--cream);
}

/* --- Ghost button ------------------------------------------------------ */
.btn-ghost {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.42rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: none;
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  color: var(--deep);
  white-space: nowrap;
  transition:
    background var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.btn-ghost:hover {
  background: var(--blue);
  border-color: var(--blue);
  color: var(--cream);
  text-decoration: none;
}

/* --- Scroll progress --------------------------------------------------- */
.progress {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  height: 2px;
  background: none;
  pointer-events: none;
}

.progress__fill {
  height: 100%;
  width: 100%;
  background: var(--blue);
  transform: scaleX(0);
  transform-origin: 0 50%;
  /* JS writes --p; the transition smooths sub-frame jitter on fast wheels. */
  transition: transform 90ms linear;
}

/* --- Section nav rail (desktop only) ----------------------------------- */
.railnav {
  position: fixed;
  top: 50%;
  right: clamp(0.75rem, 2vw, 1.75rem);
  z-index: 40;
  transform: translateY(-50%);
  display: none;
  flex-direction: column;
  gap: var(--s-3);
}

.railnav__item {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-3);
  border: 0;
  background: none;
  padding: 2px;
  color: var(--warm);
  font-size: var(--t-eyebrow);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.railnav__label {
  opacity: 0;
  transform: translateX(6px);
  transition:
    opacity var(--dur) var(--ease-out),
    transform var(--dur) var(--ease-out);
}

.railnav__item:hover .railnav__label,
.railnav__item:focus-visible .railnav__label,
.railnav__item[aria-current='true'] .railnav__label {
  opacity: 1;
  transform: translateX(0);
}

.railnav__dot {
  width: 7px;
  height: 7px;
  flex: none;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: none;
  transition:
    background var(--dur) var(--ease-out),
    border-color var(--dur) var(--ease-out),
    transform var(--dur) var(--ease-out);
}

.railnav__item[aria-current='true'] {
  color: var(--blue);
}

.railnav__item[aria-current='true'] .railnav__dot {
  background: var(--blue);
  border-color: var(--blue);
  transform: scale(1.35);
}

@media (min-width: 1180px) {
  .railnav {
    display: flex;
  }
}

/* ==========================================================================
   6. Hero
   ========================================================================== */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: clamp(7rem, 14vh, 11rem) var(--s-8);
  overflow: hidden;
}

/* A faint rule grid, drawn as a gradient so it costs no DOM. Purely
   decorative and low-contrast; it never carries information. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--rule-soft) 55%, transparent) 0 1px,
    transparent 1px
  );
  background-size: 25% 100%;
  opacity: 0;
  animation: hero-grid var(--dur-slow) var(--ease-out) 200ms forwards;
  pointer-events: none;
}

@keyframes hero-grid {
  to {
    opacity: 0.5;
  }
}

.hero__inner {
  position: relative;
  z-index: 1;
}

.hero__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: clamp(var(--s-4), 3vh, var(--s-6));
  font-size: var(--t-eyebrow);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue);
}

.hero__eyebrow::after {
  content: '';
  height: 1px;
  flex: 1 1 auto;
  max-width: 7rem;
  background: var(--rule);
  transform: scaleX(0);
  transform-origin: 0 50%;
  animation: rule-in var(--dur-slow) var(--ease-out) 500ms forwards;
}

@keyframes rule-in {
  to {
    transform: scaleX(1);
  }
}

/* --- Display name -----------------------------------------------------
   Two lines of large serif. Each line masks its own reveal, so the letters
   rise out of the line above rather than fading in place. */
.hero__name {
  font-family: var(--font-display);
  font-size: var(--t-display);
  font-weight: 400;
  line-height: var(--lh-tight);
  letter-spacing: -0.028em;
  color: var(--deep);
  margin-bottom: clamp(var(--s-4), 4vh, var(--s-6));
}

/* Chinese display type wants no negative tracking and a hair more leading. */
html[lang^='zh'] .hero__name {
  letter-spacing: 0.01em;
  line-height: 1.1;
}

.hero__line {
  display: block;
  overflow: hidden;
}

.hero__line > span {
  display: block;
  transform: translateY(105%);
  animation: line-rise 1s var(--ease-out) forwards;
}

.hero__line:nth-child(2) > span {
  animation-delay: 120ms;
}

@keyframes line-rise {
  to {
    transform: translateY(0);
  }
}

.hero__role {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  color: var(--blue);
  letter-spacing: 0.01em;
}

.hero__role span + span::before {
  content: ' · ';
  color: var(--rule);
}

/* This now carries a full bio paragraph rather than a one-line pitch, so it
   takes the standard prose measure at body size instead of the punchier
   --t-lead treatment a single sentence could carry. */
.hero__tagline {
  max-width: var(--measure);
  margin-top: var(--s-5);
  font-size: clamp(0.9375rem, 0.9rem + 0.2vw, 1.0625rem);
  line-height: 1.7;
  color: var(--ink);
}

html[lang^='zh'] .hero__tagline {
  line-height: 1.9;
}

.hero__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  margin-top: clamp(var(--s-5), 5vh, var(--s-7));
  font-size: var(--t-micro);
  letter-spacing: 0.03em;
  color: var(--warm);
}

.hero__facts li {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

.hero__facts li::before {
  content: '';
  width: 4px;
  height: 4px;
  flex: none;
  background: var(--blue);
  border-radius: 50%;
}

.hero__contact {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
  margin-top: var(--s-5);
  font-size: var(--t-small);
}

.hero__contact a {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--deep);
  border-bottom: 1px solid var(--rule-soft);
  padding-bottom: 1px;
  transition: border-color var(--dur-fast) var(--ease-out);
}

.hero__contact a:hover {
  border-bottom-color: var(--blue);
  text-decoration: none;
}

/* --- Scroll hint ------------------------------------------------------- */
.hero__hint {
  position: absolute;
  left: var(--gutter);
  bottom: var(--s-6);
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-eyebrow);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm);
  opacity: 0;
  animation: fade-in var(--dur-slow) var(--ease-out) 1.1s forwards;
}

@keyframes fade-in {
  to {
    opacity: 1;
  }
}

.hero__hint-line {
  position: relative;
  width: 3rem;
  height: 1px;
  background: var(--rule-soft);
  overflow: hidden;
}

.hero__hint-line::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--blue);
  transform: translateX(-100%);
  animation: hint-sweep 2.4s var(--ease-in-out) infinite;
}

@keyframes hint-sweep {
  0% {
    transform: translateX(-100%);
  }
  55%,
  100% {
    transform: translateX(100%);
  }
}

/* Fades the whole hero out as it leaves, if the browser supports it. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero__inner {
      animation: hero-part linear both;
      animation-timeline: view();
      animation-range: exit 10% exit 90%;
    }
    @keyframes hero-part {
      to {
        opacity: 0;
        transform: translateY(-3rem);
      }
    }
  }
}

/* ==========================================================================
   7. Section shell
   ========================================================================== */

.section {
  position: relative;
  padding-block: var(--s-section);
  /* Anchor jumps clear the fixed top bar. */
  scroll-margin-top: 5rem;
}

.section + .section {
  border-top: 1px solid var(--rule-soft);
}

.section--tint {
  background: var(--cream-deep);
}

.section__head {
  margin-bottom: clamp(var(--s-6), 6vw, var(--s-8));
}

.section__index {
  display: block;
  font-family: var(--font-body);
  font-size: var(--t-eyebrow);
  font-weight: 500;
  letter-spacing: 0.2em;
  color: var(--blue);
  margin-bottom: var(--s-3);
  font-variant-numeric: tabular-nums;
}

.section__title {
  font-family: var(--font-display);
  font-size: var(--t-h2);
  font-weight: 400;
  line-height: var(--lh-snug);
  letter-spacing: -0.02em;
  color: var(--deep);
  text-wrap: balance;
}

html[lang^='zh'] .section__title {
  letter-spacing: 0.02em;
}

.section__lead {
  max-width: 52ch;
  margin-top: var(--s-4);
  font-size: var(--t-lead);
  line-height: 1.6;
  color: var(--blue);
  font-family: var(--font-display);
}

html[lang^='zh'] .section__lead {
  line-height: 1.85;
}

/* The label rail that sits left of content on wide screens. */
.rail {
  font-size: var(--t-eyebrow);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm);
  padding-top: 0.4em;
}

.prose p + p {
  margin-top: var(--s-4);
}

.prose {
  color: var(--ink);
}
