/* ==========================================================================
   Joyce Yue Cui - portfolio
   Design tokens live here. Change a value once, it changes everywhere.
   ========================================================================== */

:root {
  /* Palette: white / gray / black only. No accent color, so the work is the
     only source of color on the page. */
  --paper:      #ffffff;
  --surface:    #f7f7f7;
  --hairline:   #e4e4e4;
  --muted:      #8c8c8c;
  --ink:        #1a1a1a;
  --ink-strong: #000000;

  /* Type */
  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --size-caption: 0.8125rem;  /* 13px */
  --size-body:    1rem;
  --size-lead:    1.125rem;
  --size-title:   1.5rem;
  --size-display: clamp(2rem, 6vw, 3.5rem);

  /* Spacing, on an 8px scale */
  --s1: 0.5rem;
  --s2: 1rem;
  --s3: 1.5rem;
  --s4: 2rem;
  --s6: 3rem;
  --s8: 4rem;
  --s12: 6rem;

  /* Layout */
  --measure: 34rem;      /* comfortable reading width */
  --page-max: 78rem;
  --gutter: clamp(1.25rem, 5vw, 4rem);

  /* Motion: one speed for the whole site */
  --t: 180ms ease;
}

/* --------------------------------------------------------------- reset ---- */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--size-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img { display: block; max-width: 100%; height: auto; }

h1, h2, h3 { font-weight: 500; line-height: 1.2; margin: 0; }

p { margin: 0 0 var(--s2); }

a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--ink-strong); }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

:focus-visible {
  outline: 1px solid var(--ink-strong);
  outline-offset: 3px;
}

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

.skip-link {
  position: absolute;
  left: var(--s2);
  top: -3rem;
  z-index: 100;
  padding: var(--s1) var(--s2);
  background: var(--paper);
  border: 1px solid var(--ink);
  transition: top var(--t);
}
.skip-link:focus { top: var(--s2); }

/* -------------------------------------------------------------- layout ---- */

.wrap {
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--s12); }
.section + .section { border-top: 1px solid var(--hairline); }
.section--tint { background: var(--surface); }

.section__head { margin-bottom: var(--s6); }

.eyebrow {
  font-size: var(--size-caption);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 var(--s1);
}

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

/* -------------------------------------------------------------- header ---- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s2) var(--s4);
  padding: var(--s3) var(--gutter);
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--hairline);
}

.site-name {
  font-size: var(--size-body);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-strong);
}

.site-nav {
  display: flex;
  gap: var(--s3);
  margin-left: auto;
  font-size: var(--size-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.site-nav a { color: var(--muted); transition: color var(--t); }
/* "true" is set by the section observer on the home page; "page" is written
   into the markup of about.html and contact.html, which run no script. */
.site-nav a:hover,
.site-nav a[aria-current="true"],
.site-nav a[aria-current="page"] { color: var(--ink-strong); }

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

.hero { padding-block: var(--s12) var(--s8); }

.hero__name {
  font-size: var(--size-display);
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--ink-strong);
  margin-bottom: var(--s3);
}

.hero__line {
  font-size: var(--size-lead);
  color: var(--muted);
  max-width: var(--measure);
  margin: 0;
}

/* --------------------------------------------------------------- works ---- */

/* Rows, not columns: the grid reads left to right and then down, the order
   the works are listed in. Multi-column masonry packed them more tightly but
   ran down each column first, so the page disagreed with the list. Works keep
   their true proportions, which leaves the foot of a row uneven. */
.works-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s6) var(--s4);
  align-items: start;
  margin: 0;
  padding: 0;
  list-style: none;
}

.work__button {
  display: block;
  width: 100%;
  text-align: left;
}

.work__frame {
  background: var(--surface);
  overflow: hidden;
}

.work__frame--pending {
  display: grid;
  place-items: center;
  border: 1px solid var(--hairline);
}
.work__frame--pending::after {
  content: "Image pending";
  font-size: var(--size-caption);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #c4c4c4;
}

.work__img {
  width: 100%;
  transition: opacity var(--t);
}
.work__button:hover .work__img { opacity: 0.82; }

/* Title, size, medium and year, each on its own line. */
.work__meta {
  margin-top: var(--s2);
  font-size: var(--size-caption);
  line-height: 1.7;
  color: var(--ink);
}

.work__meta > span { display: block; }

/* ---------------------------------------------------------- sketchbook ---- */

/* The cover is a single thing to open, held to about the width of a works
   column so it sits in the same rhythm. The pages it opens onto are not. */
.sketchbook__entry {
  max-width: 22rem;
}

.sketchbook__stack {
  position: relative;
  display: block;
}

/* Two hairline edges peeking out behind the cover, so the entry reads as a
   book of pages rather than one more work. */
.sketchbook__stack::before,
.sketchbook__stack::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--surface);
  border: 1px solid var(--hairline);
  transition: transform var(--t);
}
.sketchbook__stack::before { transform: translate(4px, 4px); }
.sketchbook__stack::after  { transform: translate(8px, 8px); }

.sketchbook__cover:hover .sketchbook__stack::before { transform: translate(6px, 6px); }
.sketchbook__cover:hover .sketchbook__stack::after  { transform: translate(12px, 12px); }

/* The cover sits above its own stack. A negative z-index would put the stack
   behind the page background instead of behind the cover. */
.sketchbook__cover .work__frame {
  position: relative;
  z-index: 1;
}

/* A button may only hold phrasing content, so these are built from spans
   rather than the grid's divs. Without this they stay inline, aspect-ratio
   is ignored, and the frame shrinks away from the stack behind it. */
.sketchbook__cover .work__frame,
.sketchbook__cover .work__meta,
.sketchbook__page-button .work__frame,
.sketchbook__page-button .work__meta { display: block; }

/* All the pages at once: two spreads to a row, since a spread is already
   two leaves wide and any narrower makes the writing on them unreadable. */
/* An author display declaration beats the browser's own rule for [hidden],
   so without this the pages stay on screen while the cover reports them
   closed. */
.sketchbook__pages[hidden] { display: none; }

.sketchbook__pages {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s4);
  margin: var(--s6) 0 0;
  padding: 0;
  list-style: none;
}

/* Spreads differ by a few pixels in proportion, so the captions would sit at
   slightly different heights. Pushing each one to the bottom of its cell
   lines them up across the row. */
.sketchbook__page { display: flex; }

.sketchbook__page-button {
  display: flex;
  flex-direction: column;
  width: 100%;
}
.sketchbook__page-button .work__frame { flex: 0 0 auto; }
.sketchbook__page-button .work__meta { margin-top: auto; padding-top: var(--s1); }

.sketchbook__page-meta { color: var(--muted); }

@media (max-width: 52rem) {
  .sketchbook__pages { grid-template-columns: 1fr; }
}

/* Room for the stack to show without touching the next section. */
.sketchbook__cover .work__meta { margin-top: var(--s2); }

@media (prefers-reduced-motion: reduce) {
  .sketchbook__stack::before,
  .sketchbook__stack::after { transition: none; }
}

/* ------------------------------------------------------------ lightbox ---- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background: rgba(255, 255, 255, 0.98);
  padding: var(--s3) var(--gutter);
}
.lightbox[hidden] { display: none; }

.lightbox__bar {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-size: var(--size-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.lightbox__count { margin-right: auto; font-variant-numeric: tabular-nums; }

.lightbox__bar button { transition: color var(--t); }

/* The cross is drawn rather than set in type, so it stays square and sharp at
   any size. display: grid centres it and drops the line box an inline SVG
   would otherwise sit on. */
.lightbox__close {
  display: grid;
  place-items: center;
  padding: var(--s1);
  margin: calc(var(--s1) * -1);   /* a larger target, same visual position */
}
.lightbox__bar button:hover { color: var(--ink-strong); }
.lightbox__bar button[disabled] { opacity: 0.3; cursor: default; }

.lightbox__stage {
  position: relative;   /* the prev / next buttons hang off this */
  display: grid;
  place-items: center;
  min-height: 0;
  padding-block: var(--s3);
  overflow: hidden;   /* keeps a zoomed work inside the stage */
}

/* Prev and next sit at the edges of the stage, level with the middle of the
   work, so turning a page needs no trip back to the bar. They carry the
   overlay's own background, to stay legible where they cross a dark work. */
.lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
  display: grid;
  place-items: center;
  padding: var(--s2) var(--s1);
  color: var(--muted);
  background: var(--paper);
  transition: color var(--t), opacity var(--t);
}
.lightbox__nav--prev { left: 0; }
.lightbox__nav--next { right: 0; }

.lightbox__nav:hover { color: var(--ink-strong); }
.lightbox__nav[disabled] { opacity: 0.25; cursor: default; }

/* A percentage max-height would resolve against an auto-height row and be
   ignored, letting tall works overflow the stage. Measure off the viewport
   instead: bar + padding + the four-line caption come to roughly 15rem. */
.lightbox__img {
  max-width: 100%;
  max-height: calc(100vh - 15rem);
  max-height: calc(100dvh - 15rem);
  object-fit: contain;
  cursor: zoom-in;
  transition: transform var(--t);
}

/* While zoomed the transform follows the pointer, so it must not animate. */
.lightbox__img--zoomed {
  cursor: zoom-out;
  transition: none;
  touch-action: none;
}

.lightbox__caption {
  font-size: var(--size-caption);
  line-height: 1.7;
  text-align: center;
  color: var(--ink);
}
.lightbox__caption > span { display: block; }

/* --------------------------------------------------------------- about ---- */

.about {
  display: grid;
  grid-template-columns: minmax(0, 18rem) minmax(0, var(--measure));
  gap: var(--s6);
}

.portrait { background: var(--surface); }

.placeholder-note {
  margin-top: var(--s4);
  padding: var(--s2) var(--s3);
  border-left: 2px solid var(--hairline);
  font-size: var(--size-caption);
  color: var(--muted);
}

/* ------------------------------------------------------------------ cv ---- */

.cv { display: grid; gap: var(--s6); }

.cv__group h3 {
  font-size: var(--size-caption);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--s2);
}

.cv__list { margin: 0; padding: 0; list-style: none; }

.cv__list li {
  display: grid;
  /* Wide enough for an interrupted span such as "2022-23, 2025-" on one
     line; the entries themselves are short, so the room is there. */
  grid-template-columns: 8rem 1fr;
  gap: var(--s2);
  padding-block: var(--s1);
  border-top: 1px solid var(--hairline);
  font-size: var(--size-caption);
}
.cv__list li:last-child { border-bottom: 1px solid var(--hairline); }

.cv__year { color: var(--muted); font-variant-numeric: tabular-nums; }

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

.contact__email {
  font-size: var(--size-title);
  color: var(--ink-strong);
  border-bottom: 1px solid var(--hairline);
  transition: border-color var(--t);
}
.contact__email:hover { border-color: var(--ink-strong); }

.contact__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s4);
  font-size: var(--size-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.contact__links a { color: var(--muted); transition: color var(--t); }
.contact__links a:hover { color: var(--ink-strong); }

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

.site-footer {
  padding: var(--s6) var(--gutter);
  border-top: 1px solid var(--hairline);
  font-size: var(--size-caption);
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
}

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

@media (max-width: 60rem) {
  .about { grid-template-columns: 1fr; gap: var(--s4); }
  .portrait { max-width: 18rem; }
}

@media (max-width: 36rem) {
  :root { --s12: 4rem; }
  .works-grid { grid-template-columns: 1fr; }
  .site-nav { gap: var(--s2); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

@media print {
  .site-header, .lightbox { display: none; }
  .section { padding-block: var(--s4); border: 0; }
}
