/* ---------- Self-hosted fonts ----------
   Downloaded from Google Fonts and served from assets/fonts/ instead of
   fonts.googleapis.com/fonts.gstatic.com, which are blocked in mainland
   China — self-hosting means the fonts load everywhere, not just where
   Google's CDN is reachable. Each file is a variable font covering the
   whole weight range in one download (that's why one file serves 500-700
   for Quicksand, and 400-700 for Nunito). Latin-alphabet glyphs only —
   Chinese text on the site (e.g. artwork titles) was already falling back
   to the system's own CJK font, since neither of these families ships
   Chinese glyphs at all, so nothing changes there. */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url('../assets/fonts/quicksand.woff2') format('woff2');
}

@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../assets/fonts/nunito-normal.woff2') format('woff2');
}

@font-face {
  font-family: 'Nunito';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/nunito-italic.woff2') format('woff2');
}

/* ---------- Reset & base ---------- */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
}

body {
  font-family: 'Nunito', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  color: #4a3b32;
  background: #fdf6ee;
}

h1, h2 {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
}

a {
  color: inherit;
}

/* ---------- Site navigation ---------- */
.site-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 1.5rem;
  background: rgba(253, 246, 238, 0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(74, 46, 26, 0.08);
}

.nav-logo {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex-shrink: 0;
  text-decoration: none;
}

/* Spelled out beside the avatar so it is obvious that clicking it is the way
   back to the main page -- the picture alone was not telling anyone that. */
.nav-logo-label {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  font-size: 0.95rem;
  color: #4a3b32;
  /* Same padding and transparent border as .nav-link so switching the underline
     on and off never shifts the header. */
  padding: 0.4rem 0.2rem;
  border-bottom: 2px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.nav-logo:hover .nav-logo-label {
  color: #b5652e;
  border-color: #f0a35f;
}

/* On the homepage the underline tracks the section in view: Home while the
   hero is on screen, About once the bio/awards sections are (see main.js). */
.nav-logo.active .nav-logo-label {
  color: #b5652e;
  border-color: #f0a35f;
}

.nav-logo img {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  object-fit: cover;
  object-position: 12% center;
  display: block;
}

.nav-menu {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 1rem 1.5rem;
}

.nav-link {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  font-size: 0.95rem;
  text-decoration: none;
  color: #4a3b32;
  padding: 0.4rem 0.2rem;
  border-bottom: 2px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.nav-link:hover {
  color: #b5652e;
  border-color: #f0a35f;
}

.nav-link.active {
  color: #b5652e;
  border-color: #f0a35f;
}

/* ---------- Sub-nav (second row: siblings of the active top-level item) ---------- */
/* Centred, not tucked into the left corner: everything else on these pages is
   centred, so a left-aligned row read as page furniture and visitors were
   missing that the work is split into categories at all. */
.sub-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
}

.series-page .sub-nav,
.content-page .sub-nav {
  position: relative;
  margin-bottom: 2rem;
  padding-bottom: 1rem;
}

/* A pseudo-element rather than a border, so it can draw in from the left. */
.series-page .sub-nav::after,
.content-page .sub-nav::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: #eaddcc;
  transform-origin: left center;
}

/* Proper tabs rather than small grey links: Melanie found that visitors did
   not notice the work was split into categories at all. */
.sub-nav a {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  font-size: 1rem;
  color: #6d5647;
  text-decoration: none;
  padding: 0.45rem 1.15rem;
  border: 1.5px solid #eaddcc;
  border-radius: 999px;
  background: #fffaf3;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.sub-nav a:hover {
  color: #b5652e;
  border-color: #d9a876;
}

.sub-nav a:hover {
  color: #b5652e;
}

.sub-nav a.active {
  color: #fffaf3;
  background: #b5652e;
  border-color: #b5652e;
}

/* ---------- Series pages ---------- */
.series-page {
  min-height: 100vh;
  min-height: 100svh;
  max-width: 72rem;
  margin: 0 auto;
  padding: 6.5rem 1.5rem 3rem;
}

.series-header {
  text-align: center;
  max-width: 40rem;
  margin: 0 auto 2.75rem;
}

.series-header h1 {
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  margin-bottom: 0.75rem;
}

.series-header p {
  color: #5c4738;
  line-height: 1.6;
}

/* Year and materials for a body of work shown as one piece (the series pages
   and Mixed Media). These used to be per-image hover captions; Melanie asked
   for them here instead, because each of those pages is a single work. */
.work-meta {
  font-size: 0.9rem;
  font-weight: 600;
  color: #b5652e;
  margin-bottom: 0.9rem;
}

/* 18rem min keeps this at 3 tiles per row inside the 72rem container —
   bigger artwork than the 4-up it used to be, same overall grid width. */
.art-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 1.5rem;
}

/* Only 2 paintings: stack them and size each closer to the width of the
   screen instead of splitting it in half. */
.art-grid--paintings {
  grid-template-columns: 1fr;
  justify-items: center;
}

.art-grid--paintings .art-tile {
  /* Melanie asked for these to take up at least a third of the screen; the
     100% ceiling just stops them overflowing the 72rem container on very
     wide displays. */
  width: clamp(20rem, 44vw, 100%);
}

/* A 4-piece series reads better as one full row than 3 + a lone straggler. */
.art-grid--four {
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

/* Blessing is laid out as an open book: cover down the left, the three
   interior spreads stacked in the middle, back cover down the right.
   align-items: center keeps the single-tile side columns vertically centred
   against the tall middle column instead of stretching them. */
.art-grid--book {
  grid-template-columns: repeat(3, 1fr);
  align-items: center;
  row-gap: 0.75rem;
}

/* Explicit placement for all five -- auto-placement would fill row by row. */
.art-grid--book .art-tile:nth-child(1) { grid-column: 1; grid-row: 1 / 4; }
.art-grid--book .art-tile:nth-child(2) { grid-column: 2; grid-row: 1; }
.art-grid--book .art-tile:nth-child(3) { grid-column: 2; grid-row: 2; }
.art-grid--book .art-tile:nth-child(4) { grid-column: 2; grid-row: 3; }
.art-grid--book .art-tile:nth-child(5) { grid-column: 3; grid-row: 1 / 4; }

/* The three interior spreads are all 2:1 files. In a square tile each one sat
   in a band of empty page, which read as a big gap between them -- a 2:1 tile
   removes that and stacks them tight. Half the usual watermark inset keeps the
   name at the same height relative to the (now shorter) tile. */
.art-grid--book .art-tile:nth-child(2),
.art-grid--book .art-tile:nth-child(3),
.art-grid--book .art-tile:nth-child(4) {
  aspect-ratio: 2 / 1;
}

.art-grid--book .art-tile:nth-child(2)::before,
.art-grid--book .art-tile:nth-child(3)::before,
.art-grid--book .art-tile:nth-child(4)::before {
  padding-bottom: 9%;
}

/* Cover and back cover smaller than a full column so they sit beside the
   spreads rather than looming over them. */
.art-grid--book .art-tile:nth-child(1),
.art-grid--book .art-tile:nth-child(5) {
  width: 72%;
  justify-self: center;
}

/* Individual series pages: one big piece per row, read top to bottom. */
.art-grid--stacked {
  grid-template-columns: 1fr;
  justify-items: center;
  gap: 2.5rem;
}

.art-grid--stacked .art-tile {
  width: 100%;
  max-width: 44rem;
}

/* Blessing's own page. Its three interior spreads are 2:1 files and its cover
   and back cover are square. Forcing every tile square letterboxed each spread
   inside a tall empty box, which pushed the watermark off the artwork and left
   a big gap between pieces -- matching each tile to its own image fixes both,
   so the page gap can come down too. */
.art-grid--book-page {
  gap: 1rem;
}

.art-grid--book-page .art-tile--wide {
  aspect-ratio: 2 / 1;
}

.art-grid--book-page .art-tile--wide::before {
  padding-bottom: 9%;
}

.art-grid--book-page .art-tile--cover {
  max-width: 26rem;
}

/* Fruit Skin: small square works, and Melanie asked for them to show smaller
   than the other series' stacked pages. */
.art-grid--compact .art-tile {
  max-width: 30rem;
}

/* Mixed Media: every photo is a different shape, including one 1:4 strip. Each
   tile keeps its own proportions at a shared row height so nothing is cropped
   or squashed, and rows centre themselves. */
.art-grid--mixed {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  grid-template-columns: none;
  gap: 1.25rem;
}

/* The long 1:4 strip runs down one side rather than sitting alone at the
   bottom, where it read as one endless image with empty space beside it. */
.art-grid--mixed .mixed-side {
  flex: 0 0 16rem;
  max-width: 100%;
}

.art-grid--mixed .mixed-side .art-tile {
  width: 100%;
  height: auto;
  aspect-ratio: auto;
}

.art-grid--mixed .mixed-side .art-tile > img {
  width: 100%;
  height: auto;
}

/* Everything else fills the other side, each piece at its own proportions and
   a shared height, rows centring themselves. */
.art-grid--mixed .mixed-main {
  flex: 1 1 30rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: 1.25rem;
}

.art-grid--mixed .mixed-main .art-tile {
  flex: 0 0 auto;
  height: 20rem;
  width: auto;
  aspect-ratio: auto;
}

.art-grid--mixed .mixed-main .art-tile img {
  height: 100%;
  width: auto;
}

.art-tile {
  position: relative;
  display: block;
  aspect-ratio: 1;
  overflow: hidden;
  cursor: pointer;
}

.art-tile img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  transition: filter 0.35s ease, transform 0.35s ease;
}

/* The "Melanie" watermark used to be drawn here as a `content: 'Melanie'`
   pseudo-element. It is burned into the image files now, because a CSS
   watermark is painted by the browser and never reaches the file -- anyone
   could right-click, Save Image As, and get a clean copy. This rule is kept
   only as the hook the hover/focus states still use; it draws nothing. */
.art-tile::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Deliberately hit-testable, as a second line of defence against the iOS
     long-press menu: with this sheet of nothing over the picture, the press
     lands on the <figure> rather than on the <img>, and Safari has no image
     to offer "Save Image" for. Clicks still reach the tile (and the <a> around
     a series preview), so tapping to reveal the caption is unaffected. */
  pointer-events: auto;
}

/* :hover is scoped to real mouse pointers only — on touch devices it can
   stick around after a tap in an unpredictable way, so touch relies solely
   on the JS-driven .active class below for consistent open/close behavior. */
@media (hover: hover) and (pointer: fine) {
  .art-tile:hover > img {
    filter: blur(7px) brightness(0.72);
    transform: scale(1.06);
  }

  .art-tile:hover .art-overlay {
    opacity: 1;
  }

  .art-tile:hover::before {
    opacity: 0;
  }
}

.art-tile:focus-visible > img,
.art-tile.active > img {
  filter: blur(7px) brightness(0.72);
  transform: scale(1.06);
}

.art-tile:focus-visible::before,
.art-tile.active::before {
  opacity: 0;
}

.art-tile:focus-visible {
  outline: 3px solid #f0a35f;
  outline-offset: 3px;
}

/* A tile with no caption has nothing to reveal, so it gets none of the reveal
   behaviour: no blur, no zoom, no pointer, and the watermark stays put. The
   class is added by main.js to every .art-tile that has no .art-overlay, which
   is all of the series pages and Mixed Media. */
.art-tile--plain {
  cursor: default;
}

/* Except on series.html, where the whole block is a link -- clicking a preview
   there does go somewhere, so it should still look clickable. */
a.series-block .art-tile--plain {
  cursor: pointer;
}

.art-tile--plain img,
.art-tile--plain:hover img,
.art-tile--plain:focus-visible img,
.art-tile--plain.active img {
  filter: none;
  transform: none;
}

.art-tile--plain:hover::before,
.art-tile--plain:focus-visible::before,
.art-tile--plain.active::before {
  opacity: 1;
}

.art-overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1.5rem;
  color: #fffaf3;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}

.art-tile:focus-visible .art-overlay,
.art-tile.active .art-overlay {
  opacity: 1;
}

.art-overlay h3 {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-size: 1.15rem;
  margin-bottom: 0.5rem;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
}

.art-overlay p {
  font-size: 0.9rem;
  line-height: 1.5;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}

.series-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: 3.5rem;
  padding-top: 2rem;
  border-top: 1px solid #eaddcc;
}

.pager-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.4rem;
  border-radius: 999px;
  text-decoration: none;
  font-weight: 600;
  font-size: 0.95rem;
  transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}

.pager-prev,
.pager-next {
  border: 1.5px solid #d9a876;
  color: #b5652e;
}

.pager-prev:hover,
.pager-next:hover {
  background: #b5652e;
  color: #fffaf3;
  transform: translateY(-2px);
}

.pager-all {
  background: #b5652e;
  color: #fffaf3;
}

.pager-all:hover {
  background: #9c5326;
  transform: translateY(-2px);
}

.pager-arrow {
  font-size: 1.1rem;
  line-height: 1;
}

/* ---------- Content pages (Illustration hub, Paintings, Curatorial Work) ---------- */
.content-page {
  min-height: 100vh;
  min-height: 100svh;
  max-width: 46rem;
  margin: 0 auto;
  padding: 6.5rem 1.5rem 4rem;
}

.project-list {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

.project-block {
  padding-bottom: 1.75rem;
  border-bottom: 1px solid #eaddcc;
}

.project-block:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.project-block h2 {
  font-size: 1.2rem;
  margin-bottom: 0.3rem;
}

.project-role {
  font-size: 0.9rem;
  font-weight: 600;
  color: #b5652e;
  margin-bottom: 0.6rem;
}

.project-block p {
  line-height: 1.7;
  color: #5c4738;
}


/* Exhibition documentation, NOT Melanie's own artwork: deliberately no
   "Melanie" watermark and no hover caption, because these photos show other
   artists' work in the shows she organised. Keep it that way. */
/* One uniform grid: every photo, poster included, the same size. Melanie tried
   a poster-left / photos-right split and asked to go back to this.
   auto-fill, not auto-fit: with auto-fit the Sotheby's block (only two photos)
   stretched them to double everyone else's size, which is exactly what she
   asked to get away from. */
/* Flex, not grid: grid aligns every row to the same columns, so a short last
   row sits under the left of the row above. Wrapping flex items with
   justify-content: center centre each row on its own. */
.project-gallery {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
  margin-top: 1.5rem;
}

/* Five photos would otherwise wrap as 4 + a lone one. Capping the row at three
   tiles gives 3 + 2, which Melanie asked for. Add this to any gallery whose
   last row would hold a single tile. */
.project-gallery--3up {
  max-width: calc(16.5rem * 3 + 1rem * 2);
  margin-left: auto;
  margin-right: auto;
}

.project-shot {
  margin: 0;
  width: 16.5rem;
  aspect-ratio: 1;
  overflow: hidden;
}

.project-shot img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.hub-links {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 2rem;
  justify-content: center;
}

.contact-list {
  list-style: none;
  max-width: 26rem;
  margin: 2.5rem auto 0;
}

.contact-list li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 1.1rem 0;
  border-bottom: 1px solid #eaddcc;
}

.contact-list li:last-child {
  border-bottom: none;
}

.contact-label {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  color: #8a6f5c;
}

/* Orange and underlined from the start, not only on hover: an art director
   reviewing the page should see at a glance that these are links, not text
   to be copied out by hand. */
.contact-list a {
  font-weight: 600;
  color: #b5652e;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: rgba(181, 101, 46, 0.5);
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}

.contact-list a:hover {
  color: #8f4c1f;
  text-decoration-color: currentColor;
}

/* Each series on series.html is one big link into its own page — the whole
   block is clickable, and the tint + name colour + arrow on hover are there
   so it reads as clickable rather than looking like a static gallery. */
.series-block {
  display: block;
  margin-top: 3rem;
  padding: 3rem 1.25rem 1.75rem;
  border-top: 1px solid #eaddcc;
  color: inherit;
  text-decoration: none;
  transition: background 0.25s ease;
}

.series-block:first-of-type {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

a.series-block:hover,
a.series-block:focus-visible {
  background: rgba(240, 163, 95, 0.09);
}

a.series-block:focus-visible {
  outline: 3px solid #f0a35f;
  outline-offset: 2px;
}

.series-block-header {
  text-align: center;
  max-width: 40rem;
  margin: 0 auto 2rem;
}

.series-block-header h2 {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-size: clamp(1.5rem, 3.5vw, 2rem);
  margin-bottom: 0.75rem;
  transition: color 0.25s ease;
}

a.series-block:hover .series-block-header h2,
a.series-block:focus-visible .series-block-header h2 {
  color: #b5652e;
}

.series-block-header p {
  color: #5c4738;
  line-height: 1.6;
}

.series-block-cue {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  font-size: 0.9rem;
  color: #b5652e;
  transition: gap 0.25s ease;
}

a.series-block:hover .series-block-cue,
a.series-block:focus-visible .series-block-cue {
  gap: 0.85rem;
  text-decoration: underline;
}

/* Preview tiles inside a series block aren't individually interactive — the
   block itself is the link — so suppress the per-tile blur/caption reveal
   and keep the watermark showing. */
.series-block .art-tile:hover > img,
.series-block .art-tile:focus-visible > img {
  filter: none;
  transform: none;
}

.series-block .art-tile:hover::before,
.series-block .art-tile:focus-visible::before {
  opacity: 1;
}

.series-card-title {
  display: block;
  margin-top: 0.6rem;
  text-align: center;
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  color: #4a3b32;
}

.coming-soon {
  text-align: center;
  padding: 3rem 1.5rem;
  border: 1px dashed #d9a876;
  border-radius: 1.25rem;
  color: #8a6f5c;
  font-style: italic;
}

/* ---------- Pending grid: real captions/metadata, image + title not in yet ---------- */
.pending-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 1.5rem;
}

.pending-tile {
  text-align: center;
}

.pending-image {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed #d9a876;
  border-radius: 1.25rem;
  color: #8a6f5c;
  font-style: italic;
  margin-bottom: 0.6rem;
}

.pending-title {
  font-weight: 600;
  font-style: italic;
  color: #8a6f5c;
  margin-bottom: 0.2rem;
}

.pending-meta {
  font-size: 0.85rem;
  color: #8a6f5c;
}

/* ---------- Scroll container ---------- */
.scroller {
  height: 100vh;
  height: 100svh;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* legacy Edge/IE */
}

.scroller::-webkit-scrollbar {
  display: none; /* Chrome, Safari, modern Edge */
}

.scroller.no-snap {
  scroll-snap-type: none;
}

.section {
  position: relative;
  height: 100vh;
  height: 100svh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

/* ---------- Section 1: hero ---------- */
.hero-section {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.hero-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The source is already the top half of "Swam Across Your Wall"; anchoring to
     the top means any further cropping on wide screens takes from the bottom,
     never from the two faces. */
  /* The crop is the faces and both of the girl's hands, with a little room
     under them, starting from the very top of the painting so there is some
     room above their heads. Biased slightly downwards so that on a short
     window it is the top that goes rather than her hands. */
  object-position: center 60%;
  /* Sharp and unaltered -- no blur, no denoising pass, no scale-up. The
     graininess that earlier rounds kept trying to blur away was the old
     source file being too small for a full-screen background: blurring it
     only clumped the speckle into blotches. Melanie supplied a bigger
     original instead, so the texture is simply resolved now. */
  /* No scale-up: hero.jpg is already cropped to exactly the framing Melanie
     wants, so enlarging it here would only eat into the edges again. */
  /* Back to the softness the cover originally had. This is safe now in a way
     it never was before: the speckled grain is out of hero.jpg itself, so the
     blur has clean input and simply softens, instead of clumping the noise
     into blotches the way every earlier round did. The scale-up hides the
     transparent edge a blur leaves; `object-position: center 60%` above keeps
     the girl's hands when a short window has to crop something. */
  filter: blur(5px);
  transform: scale(1.06);
}

/* The dark wash that used to sit here was only ever there to hold white text
   over the painting, and it dimmed the artwork badly -- Melanie's original is
   bright and she noticed. The type is dark brown now, so nothing needs to be
   darkened. Kept in the markup, and kept as a rule, so there is one obvious
   place to put a veil back if some future cover needs one. */
.hero-overlay {
  position: absolute;
  inset: 0;
  /* The original full-height wash, which is where Melanie came back to after
     trying it without: no scrim at all (the cover is nearly white in the
     middle, so white type vanished) and a pair of local gradients behind the
     text only. This one darkens the whole cover, which is the trade she
     accepted to get the white type reading everywhere, including the contact
     row along the bottom. */
  background: linear-gradient(
    to bottom,
    rgba(74, 46, 26, 0.15) 0%,
    rgba(74, 46, 26, 0.35) 55%,
    rgba(74, 46, 26, 0.55) 100%
  );
}

.hero-content {
  position: relative;
  z-index: 1;
  text-align: center;
  color: #fffaf3;
  padding: 2rem;
  max-width: 40rem;
}


.hero-name {
  font-size: clamp(2.9rem, 7.5vw, 5rem);
  font-weight: 700;
  margin: 0.35rem 0 1.1rem;
  text-shadow: 0 1px 2px rgba(74, 46, 26, 0.45), 0 3px 16px rgba(74, 46, 26, 0.6);
}


.hero-rule {
  width: clamp(5.5rem, 6vw, 7rem);
  height: 3px;
  margin: 0 auto 1.4rem;
  border: none;
  background: #f6c99a;
  border-radius: 2px;
}

.hero-tagline {
  font-size: clamp(1.3rem, 1.9vw, 1.8rem);
  font-style: italic;
  margin-bottom: 1.9rem;
  text-shadow: 0 1px 2px rgba(74, 46, 26, 0.45), 0 2px 10px rgba(74, 46, 26, 0.55);
}

/* The obvious way into the site. The bare chevron that used to sit at the
   bottom was too easy to miss, so this says where it goes. */
/* Quiet by default, so it never competes with her name: a barely-there warm
   tint, a thin light outline and white text. It fills with cream on hover.
   No backdrop blur and no drop shadow -- both were what made the earlier
   version read as a grey smudge sitting on top of the artwork. */
.hero-enter {
  display: inline-block;
  margin-top: 0.6rem;
  padding: 0.8rem 2.6rem;
  border: 1.5px solid rgba(255, 250, 243, 0.6);
  border-radius: 999px;
  background: rgba(74, 46, 26, 0.34);
  color: #fffaf3;
  text-decoration: none;
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  font-size: 1.05rem;
  letter-spacing: 0.04em;
  text-shadow: 0 1px 3px rgba(74, 46, 26, 0.5);
  transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease, transform 0.25s ease;
}

.hero-enter:hover,
.hero-enter:focus-visible {
  background: #fffaf3;
  border-color: #fffaf3;
  color: #4a3b32;
  text-shadow: none;
  transform: translateY(-2px);
}

.hero-scroll-hint {
  display: block;
  width: 1.5rem;
  margin: 1.5rem auto 0;
  animation: nudge 2.2s ease-in-out infinite;
}

.hero-scroll-hint .chevron {
  display: block;
  margin: 0 auto;
  opacity: 0.9;
  filter: drop-shadow(0 1px 3px rgba(74, 46, 26, 0.6));
}

@keyframes nudge {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(4px); }
}

/* Contact details sit quietly along the bottom of the cover. As big buttons in
   the middle they were too tempting -- people clicked straight off to Instagram
   instead of looking at the work. */
.hero-links {
  position: absolute;
  bottom: 1.5rem;
  /* left/right rather than left:50% + translate: with only `left` set, an
     absolutely positioned box may only use the space from there to the right
     edge, which on a phone was half the screen and wrapped the row. */
  left: 0;
  right: 0;
  z-index: 1;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.6rem;
  justify-content: center;
  margin: 0;
  padding: 0 1.25rem;
  max-width: 100%;
}

.hero-links a {
  display: inline-block;
  color: rgba(255, 250, 243, 0.85);
  text-decoration: none;
  font-size: 1rem;
  font-weight: 500;
  text-shadow: 0 1px 2px rgba(74, 46, 26, 0.55), 0 2px 8px rgba(74, 46, 26, 0.5);
  transition: color 0.25s ease;
}

.hero-links a:hover {
  color: #fffaf3;
  text-decoration: underline;
}


.chevron {
  width: 0.85rem;
  height: 0.85rem;
  border-right: 2.5px solid #fffaf3;
  border-bottom: 2.5px solid #fffaf3;
  transform: rotate(45deg);
}

@keyframes bob-up {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50% { transform: translateX(-50%) translateY(-8px); }
}

@keyframes bob {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50% { transform: translateX(-50%) translateY(8px); }
}

/* ---------- Sections 2 & 3: split layout ---------- */
.split-section {
  position: relative;
  display: flex;
}

.split-section.reverse {
  flex-direction: row-reverse;
}

.split-scroll-cue {
  position: absolute;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: rgba(181, 101, 46, 0.85);
  border: none;
  cursor: pointer;
  animation: bob 2.2s ease-in-out infinite;
}

.split-scroll-cue .chevron {
  border-color: #fffaf3;
}

/* At the bottom of the panel the arrow flips up, so it is clear the list has
   ended and the button now takes you back. */
.split-scroll-cue.at-end {
  animation-name: bob-up;
}

.split-scroll-cue.at-end .chevron {
  transform: rotate(225deg) translate(-2px, -2px);
}

.split-image {
  flex: 1 1 45%;
  height: 100%;
  overflow: hidden;
}

.split-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Both photos are of Melanie with her head in the upper third. Anchoring to
     the very top still cut her face off on phones, because a short panel then
     keeps only what is above the face. 22% puts the crop window just above the
     beret, so the whole head stays in frame at any panel height. */
  object-position: center 18%;
  display: block;
}

.split-text {
  flex: 1 1 55%;
  height: 100%;
  overflow-y: auto;
  display: flex;
  align-items: flex-start;
  background: #fdf6ee;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* legacy Edge/IE */
}

.split-text::-webkit-scrollbar {
  display: none; /* Chrome, Safari, modern Edge */
}

.split-text-inner {
  margin: auto 0;
  padding: clamp(2rem, 5vw, 5rem);
  max-width: 42rem;
}

.split-text h2 {
  font-size: 1.4rem;
  color: #b5652e;
  margin: 1.75rem 0 0.6rem;
}

.split-text h2:first-child {
  margin-top: 0;
}

.split-text p {
  line-height: 1.7;
  margin-bottom: 0.75rem;
  font-size: 1.02rem;
}

.split-text .lede {
  font-size: 1.12rem;
  font-weight: 600;
  color: #5c4738;
}

.split-text .closing {
  margin-top: 1.5rem;
  font-style: italic;
  color: #8a6f5c;
  border-top: 1px solid #eaddcc;
  padding-top: 1.25rem;
}

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 1.75rem;
}

.cta-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem;
  border-radius: 999px;
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  font-size: 0.95rem;
  text-decoration: none;
  transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}

.cta-primary {
  background: #b5652e;
  color: #fffaf3;
}

.cta-primary:hover {
  background: #9c5326;
  transform: translateY(-2px);
}

.cta-secondary {
  border: 1.5px solid #d9a876;
  color: #b5652e;
}

.cta-secondary:hover {
  background: #b5652e;
  color: #fffaf3;
  transform: translateY(-2px);
}

.cta-arrow {
  font-size: 1.05rem;
  line-height: 1;
}

/* The Works button's arrow points right and the page now really does move
   right: the current page steps out to the left, the next one arrives from
   the right. Only that button does it -- see main.js. */
body {
  overflow-x: hidden;
}

/* A full-width slide, not a nudge and a fade: it should read the way the
   homepage's section scrolling does, so the easing approximates the
   easeInOutQuad used by animateScrollTo. The fixed header stays put, so the
   content slides underneath it exactly as it does when you scroll down. */
@keyframes page-in {
  from { transform: translateX(100%); }
  to { transform: none; }
}

html.is-entering main,
html.is-entering .scroller {
  animation: page-in 0.42s cubic-bezier(0.45, 0, 0.55, 1) both;
}

/* The rule under the category tabs draws itself in afterwards, rather than
   being there the moment the page lands. */
@keyframes rule-in {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

html.is-entering .series-page .sub-nav::after,
html.is-entering .content-page .sub-nav::after {
  animation: rule-in 0.9s cubic-bezier(0.45, 0, 0.55, 1) 0.3s both;
}

@media (prefers-reduced-motion: reduce) {
  html.is-entering main,
  html.is-entering .scroller,
  html.is-entering .series-page .sub-nav::after,
  html.is-entering .content-page .sub-nav::after {
    animation: none;
  }
}

.award-list {
  list-style: none;
  margin-bottom: 0.5rem;
}

.award-list li {
  position: relative;
  line-height: 1.6;
  padding-left: 1.3rem;
  margin-bottom: 0.7rem;
  font-size: 0.98rem;
}

.award-list li::before {
  content: "\2726";
  position: absolute;
  left: 0;
  top: 0.05rem;
  color: #f0a35f;
  font-size: 0.8rem;
}

.award-list li.award-group::before {
  content: "";
}

.award-sublist {
  list-style: none;
  margin: 0.5rem 0 0.6rem;
  padding-left: 0.3rem;
}

.award-sublist li {
  position: relative;
  line-height: 1.6;
  padding-left: 1.1rem;
  margin-bottom: 0.4rem;
  font-size: 0.95rem;
}

.award-sublist li::before {
  content: "\2013";
  position: absolute;
  left: 0;
  top: 0;
  color: #f0a35f;
}

.award-note {
  font-size: 0.85rem;
  font-style: italic;
  color: #8a6f5c;
  margin: 0 0 0.7rem;
}

/* ---------- Dot navigation ---------- */
.dot-nav {
  position: fixed;
  right: 1.5rem;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.dot {
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 50%;
  background: rgba(74, 46, 26, 0.25);
  border: 1.5px solid rgba(74, 46, 26, 0.35);
  transition: background 0.25s ease, transform 0.25s ease;
}

.dot:hover {
  transform: scale(1.2);
}

.dot.active {
  background: #b5652e;
  border-color: #b5652e;
  transform: scale(1.25);
}

/* ---------- Floating chat button + contact modal ---------- */
body.chat-modal-open {
  overflow: hidden;
}

body.chat-modal-open .scroller {
  overflow: hidden;
}

.chat-fab {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 30;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: #b5652e;
  color: #fffaf3;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 10px 24px rgba(74, 46, 26, 0.28);
  transition: background 0.2s ease, transform 0.2s ease;
}

.chat-fab:hover {
  background: #9c5326;
  transform: translateY(-3px);
}

.chat-fab svg {
  width: 1.6rem;
  height: 1.6rem;
}

.chat-modal[hidden] {
  display: none;
}

.chat-modal {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: 1.5rem;
}

.chat-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(74, 46, 26, 0.35);
}

.chat-modal-panel {
  position: relative;
  width: 100%;
  max-width: 24rem;
  max-height: calc(100vh - 3rem);
  overflow-y: auto;
  background: #fffaf3;
  border-radius: 1.25rem;
  padding: 1.75rem;
  box-shadow: 0 20px 50px rgba(74, 46, 26, 0.3);
  animation: chat-pop 0.25s ease;
}

@keyframes chat-pop {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.chat-modal-close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: #8a6f5c;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

.chat-modal-close:hover {
  background: #f5e8d8;
  color: #b5652e;
}

.chat-modal-panel h2 {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-size: 1.35rem;
  margin-bottom: 0.4rem;
  color: #4a3b32;
}

.chat-modal-panel > p {
  font-size: 0.92rem;
  color: #5c4738;
  margin-bottom: 1.25rem;
}

.chat-form {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.chat-form-honeypot {
  display: none;
}

.chat-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.chat-field label {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  font-size: 0.85rem;
  color: #4a3b32;
}

.chat-field input,
.chat-field textarea {
  width: 100%;
  padding: 0.6rem 0.8rem;
  border: 1.5px solid #eaddcc;
  border-radius: 0.7rem;
  font-family: 'Nunito', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-size: 0.95rem;
  color: #4a3b32;
  background: #fff;
  resize: vertical;
}

.chat-field input:focus,
.chat-field textarea:focus {
  outline: none;
  border-color: #d9a876;
}

.chat-form-submit {
  margin-top: 0.3rem;
  padding: 0.75rem 1.5rem;
  border-radius: 999px;
  border: none;
  background: #b5652e;
  color: #fffaf3;
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}

.chat-form-submit:hover:not(:disabled) {
  background: #9c5326;
  transform: translateY(-2px);
}

.chat-form-submit:disabled {
  opacity: 0.7;
  cursor: not-allowed;
}

.chat-form-status {
  margin-top: 0.6rem;
  font-size: 0.88rem;
  min-height: 1.2em;
}

.chat-form-status.success {
  color: #4d7c3f;
}

.chat-form-status.error {
  color: #b5482e;
}

/* ---------- Responsive ---------- */
@media (max-width: 860px) {
  /* Too narrow for a 3-up book spread: stack it like every other grid. The
     nth-child selectors have to be repeated here -- the desktop placements use
     them too, and a plainer selector loses to them, which collapsed the cover
     to zero width on phones. */
  .art-grid--book {
    grid-template-columns: 1fr;
  }

  .art-grid--book .art-tile:nth-child(1),
  .art-grid--book .art-tile:nth-child(2),
  .art-grid--book .art-tile:nth-child(3),
  .art-grid--book .art-tile:nth-child(4),
  .art-grid--book .art-tile:nth-child(5) {
    grid-column: auto;
    grid-row: auto;
    width: 100%;
  }

  .split-section,
  .split-section.reverse {
    flex-direction: column;
  }

  /* The header is fixed and sits over the top of this panel, which hid
     Melanie's face. Pad the photo down past it and give the panel a little
     more height to make up for the padding. */
  /* Phones: the homepage becomes one ordinary long page. On desktop each
     section is pinned to the screen and its text scrolls inside it; on a
     phone that left the photo stuck at the top while the text slid under it,
     and most of the bio was never visible. Melanie asked for it to scroll
     like one long image instead. */
  .scroller {
    scroll-snap-type: none;
  }

  .section.split-section {
    height: auto;
  }

  .split-text {
    height: auto;
    overflow-y: visible;
  }

  /* Both only make sense for the pinned, screen-by-screen layout. */
  .dot-nav,
  .split-scroll-cue {
    display: none;
  }

  /* Roughly square photo (Melanie asked for "类似于正方形的一个长方形"). */
  .split-image {
    flex: 0 0 auto;
    height: min(90vw, 26rem);
  }

  /* Land in-page jumps (and arriving from another page via #illustrator)
     below the fixed header instead of underneath it. */
  .section {
    scroll-margin-top: 4.5rem;
  }

  .split-text {
    flex: 1 1 auto;
    min-height: 0;
  }

  .split-text-inner {
    padding: 1.75rem 1.5rem 2.5rem;
  }

  .dot-nav {
    right: 0.85rem;
  }


  /* Melanie wanted the bigger hero on desktop only, so below this width
     everything goes back to its original size. */
  .hero-name {
    font-size: clamp(2.25rem, 6vw, 3.75rem);
    margin: 0.35rem 0 1rem;
  }

  .hero-rule {
    width: 4.5rem;
    margin: 0 auto 1.25rem;
  }

  .hero-tagline {
    font-size: 1.1rem;
    margin-bottom: 1.75rem;
  }

  .hero-enter {
    font-size: 1rem;
    padding: 0.7rem 2.2rem;
  }

  .hero-scroll-hint {
    margin-top: 1.2rem;
  }

  .hero-links {
    gap: 0.2rem 1.1rem;
    /* Clear the floating chat button, which is fixed to the bottom-right of
       the viewport. With four labels the row is wide enough to run under it,
       so the row lifts above it rather than being pushed off-centre. */
    bottom: 5.5rem;
  }

  .hero-links a {
    font-size: 0.92rem;
  }

  /* A phone is not wide enough for three labels plus handles on one row, and
     Melanie wants them on one row, so the handles drop away here. The links
     still go to the same places, and the Contact page has the full details. */
  .hero-handle {
    display: none;
  }
}

@media (max-width: 480px) {
  /* Two rows of nav links at this width, so the header is taller. */
  .section {
    scroll-margin-top: 6rem;
  }

  /* Phone: one tile per row, filling the column. */
  .project-shot {
    width: 100%;
  }

  /* Phone: mixed-media tiles size by width, one per row. */
  .art-grid--mixed .mixed-side,
  .art-grid--mixed .mixed-main .art-tile {
    flex-basis: 100%;
    height: auto;
    width: 100%;
  }

  .art-grid--mixed .mixed-main .art-tile img {
    width: 100%;
    height: auto;
  }

  .split-text h2 {
    font-size: 1.2rem;
  }

  .site-nav {
    padding: 0.5rem 1rem;
  }

  .nav-logo img {
    width: 2.25rem;
    height: 2.25rem;
  }

  .nav-menu {
    gap: 0.9rem;
  }

  .nav-link,
  .nav-logo-label {
    font-size: 0.85rem;
  }

  .nav-logo {
    gap: 0.4rem;
  }

  .series-page,
  .content-page {
    padding-top: 7.5rem;
  }

  .pager-btn {
    padding: 0.6rem 1.1rem;
    font-size: 0.85rem;
  }

  .chat-fab {
    right: 1rem;
    bottom: 1rem;
    width: 3.25rem;
    height: 3.25rem;
  }

  .chat-modal {
    padding: 0;
    align-items: flex-end;
  }

  .chat-modal-panel {
    max-width: none;
    width: 100%;
    max-height: 85vh;
    border-radius: 1.25rem 1.25rem 0 0;
  }
}

/* ---------- Language switch ----------
   Every translatable string is a pair of spans, data-lang="en" and
   data-lang="zh"; only the one matching <html lang> is shown. English is the
   default in the HTML, Chinese is opt-in and remembered per browser (see the
   inline script in each page's <head>, and main.js). Artwork titles are
   deliberately NOT paired -- Melanie keeps them in English in both versions,
   except the ones she already has Chinese names for. */
html:not([lang|="zh"]) [data-lang="zh"],
html[lang|="zh"] [data-lang="en"] {
  display: none !important;
}

.lang-switch {
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-weight: 600;
  font-size: 0.85rem;
  color: #b5652e;
  background: transparent;
  border: 1.5px solid #d9a876;
  border-radius: 999px;
  padding: 0.3rem 0.8rem;
  line-height: 1.2;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.lang-switch:hover {
  background: #b5652e;
  color: #fffaf3;
}

/* Chinese reads better with a little more line height than the Latin fonts. */
html[lang|="zh"] body {
  line-height: 1.75;
}

/* ---------- Saving and copying ---------- */
/* None of this actually prevents anything, and Melanie has been told so: a
   browser has to download an image in order to show it, and a screenshot
   always works. This is friction for the casual right-click. The real
   protection is the watermark burned into the image files plus the
   deliberately low pixel sizes, both of which survive being saved. */

/* iOS answers a long press with its own callout sheet ("Save Image", "Copy"),
   and that sheet is not the context menu -- preventDefault on `contextmenu`
   does nothing to it. `-webkit-touch-callout` is the switch for it, and it is
   inherited, so it is set on the root rather than only on `img`: setting it on
   the image alone was not enough on Melanie's phone, because for an image
   inside a link it is the link that owns the callout. Suppressing it also lets
   a long press fall through to the tap handler, so it reveals the caption the
   same way a tap does. */
html,
body,
a,
img,
.art-tile {
  -webkit-touch-callout: none;
}

img {
  -webkit-user-drag: none;
  user-drag: none;
}

body {
  -webkit-user-select: none;
  user-select: none;
}

/* Her contact details stay selectable on purpose -- locking those would stop
   people copying her email or phone, which is the opposite of what the site is
   for. Form fields have to stay selectable or they cannot be typed in and
   corrected. */
.contact-list,
.contact-list *,
.hero-links,
.hero-links *,
input,
textarea {
  -webkit-user-select: text;
  user-select: text;
}

/* ---------- Media page ---------- */
/* The tiles are the full 9:16 frame, black bands and all. The bands were
   cropped in CSS for a round, and Melanie asked for them back ("全保留"):
   they are not empty on every clip -- several carry her burnt-in RedNote
   captions, and cropping cut the text in half. The bands also differ from
   clip to clip (measured across five frames each, from none at all to 13%
   top and bottom), so no single crop suited them. If this comes up again,
   crop per clip or not at all. */
.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1.25rem;
}

.video-tile {
  position: relative;
  margin: 0;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  background: #000;
}

.video-tile video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Sound is off until the visitor asks for it -- browsers only allow a video to
   start on its own when it is muted, so autoplay and sound cannot both be the
   default. This is the way to turn it on. */
.video-sound {
  position: absolute;
  right: 0.6rem;
  bottom: 0.6rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.75rem 0.35rem 0.6rem;
  border: none;
  border-radius: 999px;
  background: rgba(74, 46, 26, 0.55);
  color: #fffaf3;
  font-family: 'Quicksand', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  opacity: 0.85;
  transition: background 0.2s ease, opacity 0.2s ease;
}

.video-sound:hover,
.video-sound:focus-visible {
  background: rgba(74, 46, 26, 0.8);
  opacity: 1;
}

.video-sound-icon {
  width: 0.85rem;
  height: 0.85rem;
  flex: none;
  background: currentColor;
  /* A speaker outline, drawn as a mask so it takes the button's colour. */
  -webkit-mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 9v6h4l5 4V5L8 9H4z'/%3E%3C/svg%3E");
  mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 9v6h4l5 4V5L8 9H4z'/%3E%3C/svg%3E");
}

.video-sound[aria-pressed="true"] {
  background: #b5652e;
  opacity: 1;
}

.video-more {
  margin-top: 2.5rem;
  text-align: center;
}

.video-more a {
  color: #b5652e;
  font-weight: 600;
  text-decoration: none;
}

.video-more a:hover {
  text-decoration: underline;
}

/* Media is a section with categories under it, so the section's own name and
   description sit ABOVE the category tabs and each category's heading sits
   below them -- Melanie: "放在细分分类的上面". h1 is the section, h2 the
   category, h3 an individual article. */
.section-intro {
  text-align: center;
  max-width: 40rem;
  margin: 0 auto 1.75rem;
}

.section-intro h1 {
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  margin-bottom: 0.75rem;
}

.series-header h2 {
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  margin-bottom: 0.6rem;
}

.article-card h3 {
  font-size: 1.05rem;
  line-height: 1.45;
  margin: 0 0 0.45rem;
}

/* ---------- Article cards (WeChat) ---------- */
/* WeChat articles cannot be embedded: the article's own CSP header carries
   `frame-ancestors` listing only Tencent's domains, so a browser refuses to
   render it in an iframe anywhere else. Verified on Melanie's own article.
   So each one is a card instead -- its real cover image (downloaded and
   hosted here; the cover is not referer-protected), title, account and date
   -- that opens the article in WeChat. */
/* auto-fill with a max track, not auto-fit with 1fr: with a single article
   `auto-fit` collapsed the empty tracks and stretched that one card across
   the whole page. */
.article-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 24rem));
  justify-content: center;
  gap: 1.5rem;
}

.article-card {
  display: block;
  text-decoration: none;
  color: inherit;
  background: #fffaf3;
  border: 1.5px solid #eaddcc;
  overflow: hidden;
  transition: border-color 0.25s ease, transform 0.25s ease;
}

.article-card:hover,
.article-card:focus-visible {
  border-color: #d9a876;
  transform: translateY(-3px);
}

.article-card img {
  width: 100%;
  aspect-ratio: 900 / 382;
  object-fit: cover;
  display: block;
}

.article-card-text {
  padding: 1.1rem 1.25rem 1.35rem;
}

.article-meta {
  font-size: 0.85rem;
  color: #8a6f5c;
  margin: 0 0 0.8rem;
}

.article-cue {
  font-size: 0.9rem;
  font-weight: 600;
  color: #b5652e;
}


/* ---------- Hover hint on outgoing links ----------
   Same reason the contact details are underlined: people do not always
   realise a line of text will take them somewhere. Only links that leave
   the page carry the hint -- putting it on the nav too would be noise,
   since those links already look and behave like navigation. `main.js`
   adds the classes; the wording is a data-lang pair like every other
   string on the site. */
.has-tip {
  position: relative;
}

.has-tip::after {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 0.5rem);
  z-index: 80;
  transform: translate(-50%, 4px);
  padding: 0.35rem 0.65rem;
  border-radius: 0.55rem;
  background: rgba(74, 46, 26, 0.94);
  color: #fffaf3;
  font-family: 'Nunito', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.01em;
  white-space: nowrap;
  text-shadow: none;
  text-decoration: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.has-tip:hover::after,
.has-tip:focus-visible::after {
  opacity: 1;
  transform: translate(-50%, 0);
}

html:not([lang|="zh"]) .tip-ext::after { content: 'Opens in a new tab ↗'; }
html[lang|="zh"] .tip-ext::after { content: '点击在新标签页打开 ↗'; }
html:not([lang|="zh"]) .tip-mail::after { content: 'Opens your mail app ↗'; }
html[lang|="zh"] .tip-mail::after { content: '点击给我写邮件 ↗'; }
html:not([lang|="zh"]) .tip-tel::after { content: 'Tap to call ↗'; }
html[lang|="zh"] .tip-tel::after { content: '点击拨号 ↗'; }

/* A long press on a phone should not leave the bubble stranded on screen,
   and there is no cursor there to explain anything to. */
@media (hover: none) {
  .has-tip::after { display: none; }
}
