/* ---------------------------------------------------------------
   Henry Rhoden — portfolio
   Tokens taken from Figma "Landing Page" (file Q1vduz8LxBm9cCwowgm8Oy,
   frame 1:4 "Desktop - 1"). Anything not marked as from Figma was
   derived to extend that design language to the interior pages.
   --------------------------------------------------------------- */

:root {
  /* From Figma */
  --paper: #ffe992;          /* frame background */
  --ink: #000000;            /* wordmark + body text */

  /* Derived */
  --ink-soft: rgba(0, 0, 0, 0.62);
  --rule: rgba(0, 0, 0, 0.18);
  --accent-blue: #1a2f5c;        /* classic nautical navy */

  /* Footer stripe: navy bands with page-colour gaps, solid at the very
     bottom edge and dissolving upward. Both values feed one derived
     period, so changing either rescales the band. */
  --navy: #1a2f5c;
  --stripe-w: 7px;
  --stripe-gap: 7px;
  --stripe-period: calc(var(--stripe-gap) + var(--stripe-w));
  --stripe-solid: 12%;           /* solid run at the bottom before the fade */
  --stripe-drift: 1.6s;          /* one stripe period per cycle */

  --ground: var(--paper);              /* page colour, used to knock out chips */

  /* Bookmania is a licensed font and is not bundled here. Drop the
     web-font files into assets/fonts/ and add an @font-face block to
     match the Figma comp exactly; until then the serif fallbacks below
     carry the same bold-italic voice. */
  --display: "Bookmania", "Playfair Display", Georgia, "Times New Roman", serif;

  /* Navigation / UI voice: a monospace set small and uppercase, used for
     nav, dates, meta and buttons. Body copy stays Georgia. */
  --ui: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;

  --gutter: 4.93vw;          /* 71 / 1440 — the Figma left margin */
  --measure: 62ch;
}

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

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

body {
  margin: 0;
  min-height: 100vh;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--display);
  display: flex;
  flex-direction: column;
}

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

a {
  color: inherit;
}

/* --- Masthead ------------------------------------------------------
   The Figma frame has no navigation, so this bar is an addition. It
   reuses the comp's only real ingredients: black bold-italic serif on
   the yellow ground.
   ------------------------------------------------------------------ */

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
  position: relative;
  padding: 43px var(--gutter) 22px;
  background-color: var(--paper);
}


.wordmark {
  font-style: italic;
  font-weight: 700;
  line-height: 0.87;              /* 78px step at the 90px Figma size */
  letter-spacing: -0.01em;
  margin: 0;
  text-decoration: none;
  text-transform: uppercase;
  /* Bookmania's italic N overhangs its advance width by ~0.107em, so
     its ink sits outside the element box. This right padding pulls the
     box out past the glyph so the wipe animation's clip-path can never
     shave that corner. */
  padding: 0 0.14em 0 0;
  background: none;
  position: relative;
}



/* A hair of air between the R and H of RHODEN, which otherwise read
   as one joined shape at display size. */
.wordmark span.kern {
  display: inline;
  letter-spacing: 0.03em;
}

.wordmark span {
  display: block;
}

/* Landing wordmark: 90px in the 1440-wide comp. */
.wordmark--hero {
  font-size: clamp(3rem, 6.25vw, 5.625rem);
}

/* Interior pages keep the same mark, set smaller so the page content leads. */
.wordmark--compact {
  font-size: clamp(1.75rem, 2.4vw, 2.25rem);
  line-height: 0.95;
}

.nav {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.5rem, 2.6vw, 3rem);
  margin: 0;
  padding: 0;
  background: none;
  position: relative;
  list-style: none;
  font-family: var(--ui);
  font-style: normal;
  font-weight: 500;
  font-size: 1rem;
  text-transform: uppercase;
  /* Monospace is already wide, so it needs less tracking. */
  letter-spacing: 0.08em;
}

.nav a {
  text-decoration: none;
}

/* No underline on hover or on the current page — the menu stays as a
   clean row of words. Hover dims instead, so there is still feedback. */
.nav a:hover {
  opacity: 0.55;
}

/* Keyboard focus still needs to be visible; an outline sits around the
   word rather than drawing a line beneath it. */
.nav a:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* --- Landing -------------------------------------------------------
   Figma geometry: image 984x651 at top=291 in a 1440x1096 frame, i.e.
   68.33% wide and horizontally centred, sitting 44px under the wordmark.
   ------------------------------------------------------------------ */

.hero {
  flex: 1;
  min-height: 100vh;               /* footer band starts below the fold */
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding-bottom: clamp(1.5rem, 5vh, 56px);
}

/* Diptych: Wyoming Horses and Collage 05, hung as a pair. They share one
   height rather than one width, so the horse simply reads bigger because
   it's the wider of the two at that height.

   The shared height is whatever makes the pair's combined width exactly
   fill the content measure — full-bleed across the gutters, no vertical
   cap. 2.137 is the sum of both images' width/height ratios
   (2500/1870 + 1042/1302), so dividing the available width by it solves
   for that height directly. On tall/narrow viewports this can run the
   pair past the fold; that's intentional now that more content is
   planned below, rather than shrinking the images to stay on screen. */
.hero__duo {
  --duo-gap: clamp(1.25rem, 3vw, 2rem);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: var(--duo-gap);
  height: calc((100vw - 2 * var(--gutter) - var(--duo-gap)) / 2.137);
  margin-top: 44px;
}

/* The images are wrapped in links through to their portfolio entries;
   the anchor takes over the flex-item sizing the image used to carry
   so the layout math above is unaffected either way. */
.hero__link {
  display: block;
  height: 100%;
  flex: none;
  line-height: 0;
}

.hero__image {
  height: 100%;
  width: auto;
  display: block;
}

/* --- Wordmark entrance ---------------------------------------------
   Option B, "angled wipe": the reveal edge is cut to match the italic
   slant, so the name reads as being laid down rather than sliding in.
   Runs once on load; nothing loops.

   The polygon deliberately extends past the box on every side. Italic
   caps overhang their advance width and the 0.87 line-height crops the
   line box, so a polygon stopping at 0%/100% would shave the edges of
   the letterforms — the same trap that clipped the N previously.
   ------------------------------------------------------------------ */

.wordmark {
  animation: wordmark-wipe 1.1s cubic-bezier(0.33, 0, 0.15, 1) both;
}

@keyframes wordmark-wipe {
  /* Only the leading edge moves; the trailing edge is parked off to the
     left. Travel is kept just wide enough to clear the letterforms, so
     the reveal reads across the whole duration instead of finishing in
     a burst halfway through. */
  from { clip-path: polygon(-40% -50%, -8% -50%, -32% 150%, -40% 150%); }
  to   { clip-path: polygon(-40% -50%, 130% -50%, 106% 150%, -40% 150%); }
}

/* The wordmark is the route home from every page, so it is a link
   everywhere — including the landing, where it points at itself. */
.wordmark a,
a.wordmark {
  color: inherit;
  text-decoration: none;
  display: block;
}

/* No hover or active state on the wordmark — it reads the same resting,
   hovered and clicked. */

.wordmark a:focus-visible,
a.wordmark:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .wordmark { animation: none; }
}


/* --- Footer band ----------------------------------------------------
   The two cities at the outer corners, separated from the page by a
   hairline rule.
   ------------------------------------------------------------------ */

.footer-band {
  position: relative;
  flex: 0 0 auto;
  /* Drops the whole band 69px, matching where the stripes were moved to
     in Figma. Applied here rather than per-page so the landing and the
     interior pages keep the same distance above the stripes. */
  margin-top: 69px;
  /* Tall band so the stripes climb well up the page; the chips sit low
     inside it, near the bottom edge. */
  padding: 175px 0 34px;
  /* Floor for the band if the labels ever go missing; every page now
     carries them, so the natural height (288px) wins in practice. */
  min-height: 259px;
}

/* Striped field: a navy-to-paper wash masked into bands, strongest at
   the bottom edge of the page and dissolving upward. A repeating
   gradient cannot vary colour on the other axis, hence the mask. */
.footer-band::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: linear-gradient(
    to top,
    var(--navy) 0%,
    var(--navy) var(--stripe-solid),
    var(--paper) 100%
  );
  -webkit-mask-image: repeating-linear-gradient(
    to bottom,
    transparent 0 var(--stripe-gap),
    #000 var(--stripe-gap) var(--stripe-period)
  );
  mask-image: repeating-linear-gradient(
    to bottom,
    transparent 0 var(--stripe-gap),
    #000 var(--stripe-gap) var(--stripe-period)
  );
  -webkit-mask-position: 0 0;
  mask-position: 0 0;
  /* Only the band pattern travels; the navy-to-paper wash stays fixed.
     One full period per cycle, so the loop has no seam. */
  animation: stripe-drift var(--stripe-drift) linear infinite;
}

@keyframes stripe-drift {
  to {
    -webkit-mask-position: 0 calc(-1 * var(--stripe-period));
    mask-position: 0 calc(-1 * var(--stripe-period));
  }
}

@media (prefers-reduced-motion: reduce) {
  .footer-band::before { animation: none; }
}


.footer-band__inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--gutter);
}

.footer-band__place {
  /* Same display face as the wordmark and page titles. No chip behind
     it — a white ring around the letterforms holds them off the stripes
     instead, so the pattern runs unbroken behind the text. */
  font-family: var(--display);
  font-style: italic;
  font-weight: 700;
  font-size: clamp(2.5rem, 4vw, 3.5rem);
  letter-spacing: 0.01em;
  text-transform: uppercase;
  background: none;
  padding: 0;
  text-shadow:
    -4px -4px 0 #ffffff,
    0px -4px 0 #ffffff,
    4px -4px 0 #ffffff,
    -4px 0px 0 #ffffff,
    4px 0px 0 #ffffff,
    -4px 4px 0 #ffffff,
    0px 4px 0 #ffffff,
    4px 4px 0 #ffffff,
    -3px -4px 0 #ffffff,
    3px -4px 0 #ffffff,
    -4px -3px 0 #ffffff,
    4px -3px 0 #ffffff,
    -4px 3px 0 #ffffff,
    4px 3px 0 #ffffff,
    -3px 4px 0 #ffffff,
    3px 4px 0 #ffffff;
}


/* --- Lightbox (portfolio) -------------------------------------------
   Clicking a piece opens it at full size over a dark backdrop. The
   dialog carries no content until JS fills it, so with JS off the
   thumbnails stay ordinary images and nothing is lost.

   "Rise up": the dialog starts translated down and transparent, and JS
   adds .is-open a frame later so the transition actually runs (a
   transition can't fire between two states set in the same tick). The
   hidden state carries the transition too, since closing removes
   .is-open and needs to animate back down before JS calls dlg.close().
   ------------------------------------------------------------------ */

.lightbox {
  width: 100%;
  height: 100%;
  max-width: 100vw;
  max-height: 100vh;
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  overflow: hidden;
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.3s ease-out, transform 0.3s ease-out;
}
.lightbox.is-open {
  opacity: 1;
  transform: translateY(0);
}

.lightbox::backdrop {
  background: rgba(0, 0, 0, 0.9);
  opacity: 0;
  transition: opacity 0.3s ease-out;
}
.lightbox.is-open::backdrop {
  opacity: 1;
}

.lightbox__inner {
  box-sizing: border-box;
  height: 100%;
  padding: clamp(1rem, 4vw, 3rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
}

.lightbox__stage {
  position: relative;
  width: 100%;
  /* Leave room for the caption under the tallest possible image. */
  height: calc(100vh - 9rem);
  overflow: hidden;
}

.lightbox__img {
  position: absolute;
  inset: 0;
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
/* The off-screen layer used to swap in the next or previous piece. */
.lightbox__img.is-back {
  visibility: hidden;
}

.lightbox__caption {
  margin: 0;
  font-family: var(--ui);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #ffffff;
  text-align: center;
}

.lightbox__close {
  position: absolute;
  top: clamp(0.75rem, 2vw, 1.5rem);
  right: clamp(0.75rem, 2vw, 1.5rem);
  width: 44px;
  height: 44px;
  font-size: 1.5rem;
  line-height: 1;
  color: #ffffff;
  background: none;
  border: none;
  cursor: pointer;
  z-index: 1;
}
.lightbox__close:focus-visible { outline: 2px solid #ffffff; outline-offset: 2px; }

.lightbox__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.35);
  background: rgba(0, 0, 0, 0.25);
  color: #ffffff;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  z-index: 1;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.lightbox__arrow:hover { background: rgba(255, 255, 255, 0.14); border-color: rgba(255, 255, 255, 0.7); }
.lightbox__arrow:focus-visible { outline: 2px solid #ffffff; outline-offset: 2px; }
.lightbox__arrow--prev { left: clamp(0.5rem, 2.5vw, 2rem); }
.lightbox__arrow--next { right: clamp(0.5rem, 2.5vw, 2rem); }

@media (prefers-reduced-motion: reduce) {
  .lightbox,
  .lightbox::backdrop,
  .lightbox__img {
    transition: none;
  }
}



/* Square looping clip inside a text page. Source is 540x540, so 1:1
   with cover keeps it honest at any width. */
.loop-media {
  width: min(400px, 100%);
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
  margin: 0.75rem 0 0;
  background: rgba(0, 0, 0, 0.06);
}

/* --- Interior pages ------------------------------------------------ */

.page {
  flex: 1;
  min-height: 100vh;               /* footer band starts below the fold */
  /* Top padding is fixed rather than responsive: together with the
     masthead's own 22px bottom padding it holds the wordmark-to-title
     gap at a flat 32px, so the title reads as part of the masthead
     block rather than the start of a separate section. Bottom keeps
     its generous, responsive value so the page still breathes at the
     foot. */
  padding: 10px var(--gutter) clamp(3rem, 8vw, 6rem);
}

.page__title {
  font-style: italic;
  font-weight: 700;
  text-transform: uppercase;
  /* Same size as the compact wordmark in the masthead above, so the
     page title and the name read as one typographic level. */
  font-size: clamp(1.75rem, 2.4vw, 2.25rem);
  line-height: 1.05;
  letter-spacing: -0.01em;
  padding-top: 0.04em;
  /* Generous above and below: the title separates from the masthead and
     from the first section rather than crowding either. */
  margin: 0 0 1.2em;
}

.page__intro,
.page p,
.page li {
  font-family: Georgia, "Times New Roman", serif;
  font-style: normal;
  font-weight: 400;
  font-size: clamp(1rem, 1.15vw, 1.125rem);
  line-height: 1.6;
  max-width: var(--measure);
}

.page__intro {
  font-size: clamp(1.125rem, 1.5vw, 1.375rem);
  line-height: 1.5;
}

.section {
  margin-top: clamp(2rem, 4vw, 3.25rem);
  padding-top: clamp(1.25rem, 2.5vw, 2rem);
  border-top: 1px solid var(--rule);
}

.section__title {
  font-style: italic;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: clamp(1.125rem, 1.5vw, 1.375rem);
  margin: 0 0 1rem;
}

/* Portfolio filter bar. Hidden in the markup and revealed by JS, so
   with scripting off the wall simply shows everything rather than
   offering buttons that cannot do anything. */
.work-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: clamp(1.5rem, 3vw, 2.5rem) 0 0;
}
.work-filter__btn {
  font-family: var(--ui);
  font-weight: 500;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink);
  background: none;
  border: 1.5px solid var(--ink);
  padding: 9px 16px;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease;
}
.work-filter__btn:hover,
.work-filter__btn[aria-pressed="true"] { background: var(--ink); color: var(--ground); }
.work-filter__btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.work-filter__n { opacity: 0.55; margin-left: 7px; }

/* Sort bar — same voice as the filter row, lighter weight since it's a
   secondary control. Unpressed on both buttons means "shuffled", the
   state every reload starts in; there's no third button back to it. */
.work-sort {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0.6rem 0 0;
}
.work-sort__label {
  font-family: var(--ui);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-right: 2px;
}
.work-sort__btn {
  font-family: var(--ui);
  font-weight: 500;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: none;
  border: 1px solid var(--rule);
  padding: 6px 12px;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.work-sort__btn:hover,
.work-sort__btn[aria-pressed="true"] { background: var(--ink); color: var(--ground); border-color: var(--ink); }
.work-sort__btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* One wall for every discipline, filtered in place. Masonry columns
   rather than rows: the work runs from 4:5 collages to wide campaign
   frames, and a row grid would leave a band of empty paper under every
   short piece. Reading order runs down a column, then across. */
.work-grid {
  columns: 280px 3;
  column-gap: clamp(1.5rem, 3vw, 2.5rem);
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
  /* Explicit, not relied-on-as-default: this is what keeps every column's
     first card flush with the row above, in every category. */
  column-fill: balance;
}

.work {
  /* A piece must not be split from its caption across a column break.
     flow-root keeps the card's own margins from collapsing out of it. */
  display: flow-root;
  -webkit-column-break-inside: avoid;
  break-inside: avoid;
  margin: 0 0 clamp(1.5rem, 3vw, 2.5rem);
}
/* flow-root would otherwise beat the hidden attribute the filter sets. */
.work[hidden] { display: none; }

/* Which section a piece belongs to, so the mixed wall still reads. */
.work__tag {
  display: block;
  margin-top: 3px;
  font-family: var(--ui);
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.5;
}

.work__thumb {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--rule);
  cursor: zoom-in;
}

/* Artwork keeps its own proportions — cropping a piece to a landscape
   box would cut the composition. Each <img> carries width/height
   attributes so the browser reserves the right space before load. */
.work__thumb--art {
  aspect-ratio: auto;
  height: auto;
}

.work__title {
  font-style: italic;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 1.125rem;
  margin: 0.75rem 0 0.35rem;
}

.work p {
  margin: 0;
  font-size: 1rem;
}

/* Contact list */
.contact-list {
  list-style: none;
  padding: 0;
  margin: clamp(1.5rem, 3vw, 2.5rem) 0 0;
}

.contact-list li {
  margin-bottom: 1rem;
}

.contact-list a {
  font-style: italic;
  font-weight: 700;
  font-family: var(--display);
  font-size: clamp(1.25rem, 2vw, 1.75rem);
  text-decoration: none;
  border-bottom: 2px solid var(--rule);
}

.contact-list a:hover,
.contact-list a:focus-visible {
  border-bottom-color: var(--ink);
}

/* --- CV (resume) ----------------------------------------------------
   Section headings span the full width with their entries below, and the
   entries themselves run in two columns. An earlier version put the
   heading in a side rail, which left dead space beside short sections.
   Spacing is deliberately tight throughout.
   ------------------------------------------------------------------ */

/* Full content width — the page gutter is the only constraint. */
.cv-doc { max-width: none; }

/* No rules between sections — the display-italic label carries the
   separation on its own, and dropping them tightens the page. */
.cv-section {
  padding: 0 0 36px;
}
.cv-section:last-child { padding-bottom: 0; }

.cv-section__label {
  font-family: var(--display);
  font-style: italic;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 1.15rem;
  line-height: 1.1;
  letter-spacing: 0.03em;
  margin: 0 0 7px;
}

/* Entries side by side; one column once there is no room. */
.cv-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 40px;
  align-items: start;
}
.cv-grid--single { grid-template-columns: minmax(0, 1fr); }

.cv { margin: 0; }

/* Two entries stacked inside one grid cell keep the same rhythm as the
   grid itself, rather than a one-off margin. */
.cv-stack {
  display: flex;
  flex-direction: column;
  gap: 26px;
}

.cv__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0 1rem;
  flex-wrap: nowrap;              /* keeps every date on the first line */
}

.cv__org {
  display: flex;
  align-items: center;
  flex-wrap: wrap;                /* the place drops instead of the date */
  gap: 0.2rem 0.45rem;
  min-width: 0;
  flex: 1 1 auto;
  margin: 0;
  font-family: var(--display);
  font-style: italic;
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.2;
}

.cv__icon {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border-radius: 4px;
}
.cv__icon { object-fit: contain; }


.cv__org a,
.cv__note a { text-decoration: none; border-bottom: 1.5px solid var(--rule); }
.cv__org a:hover,
.cv__note a:hover { border-bottom-color: var(--ink); }
.cv__org a:focus-visible,
.cv__note a:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.cv-doc .cv__place {
  font-family: Georgia, "Times New Roman", serif;
  font-style: normal;
  /* Same size as the org name it sits beside, so neither reads as
     the secondary element on the line. */
  font-size: 1rem;
  color: var(--ink);
}

.cv__date {
  font-family: var(--ui);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  flex: 0 0 auto;
  margin-left: auto;
}

/* Tabular figures reserve the final width so the row cannot jitter as
   the digits change during the count. */
.cv__count {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.cv-doc .cv__role {
  font-family: var(--display);
  font-style: italic;
  font-weight: 700;
  font-size: 0.875rem;
  margin: 5px 0 0;
}

.cv__points {
  margin: 5px 0 0;
  padding-left: 0;
  list-style: none;
}
.cv-doc .cv__points li {
  font-family: Georgia, "Times New Roman", serif;
  font-size: 0.875rem;
  line-height: 1.32;
  margin-bottom: 0.4rem;
}
/* No trailing margin on the last bullet, so the gap to the next section
   is measured from the text itself and is identical everywhere. */
.cv-doc .cv__points li:last-child { margin-bottom: 0; }

.cv-doc .cv__note,
.cv-doc .cv__plain {
  font-family: Georgia, "Times New Roman", serif;
  font-size: 0.875rem;
  line-height: 1.32;
  margin: 5px 0 0;
  color: var(--ink);
}
.cv__plain { margin: 0 0 0.3rem; }
.cv-doc .cv__plain:last-child { margin-bottom: 0; }

@media (max-width: 760px) {
  .cv-grid { grid-template-columns: minmax(0, 1fr); gap: 14px 0; }
  .cv-section { padding: 0 0 28px; }
}


/* --- Section reveal (resume) ----------------------------------------
   Sections rise into place as they enter view, label first then each
   column.

   Transitions, not keyframes, on purpose: a transition lands on the
   final computed style the moment the class flips, so if the animation
   is never actually painted the content still ends up visible. With
   `animation: forwards` a section that failed to animate would stay
   stranded at opacity 0.

   The hidden state applies only under .reveal-on, which JS adds — so no
   JS, no IntersectionObserver, or reduced motion all render normally.
   ------------------------------------------------------------------ */

/* The hidden state carries NO transition: .reveal-on is applied by
   script, and if the transition lived here the content would visibly
   fade OUT on load before fading back in. The transition belongs to the
   revealed state only. */
.reveal-on .cv-section__label,
.reveal-on .cv-grid > * {
  opacity: 0;
  transform: translateY(14px);
}

.reveal-on .cv-section.is-visible .cv-section__label,
.reveal-on .cv-section.is-visible .cv-grid > * {
  opacity: 1;
  transform: none;
  transition: opacity 0.55s cubic-bezier(0.2, 0.75, 0.2, 1),
              transform 0.55s cubic-bezier(0.2, 0.75, 0.2, 1);
}

/* Label leads; columns follow left to right. */
.reveal-on .cv-section.is-visible .cv-grid > *:nth-child(1) { transition-delay: 0.09s; }
.reveal-on .cv-section.is-visible .cv-grid > *:nth-child(2) { transition-delay: 0.18s; }

@media (prefers-reduced-motion: reduce) {
  .reveal-on .cv-section__label,
  .reveal-on .cv-grid > * {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* --- Landing bottom --------------------------------------------------
   Matches Landing1 in Figma: the striped band sits 187px below the hero
   image, and the page ends at the bottom of the band.
   ------------------------------------------------------------------ */

body.landing .hero {
  padding-bottom: 187px;
}



/* --- Footer -------------------------------------------------------- */

.footer {
  padding: 1.5rem var(--gutter) 2rem;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

/* --- Small screens -------------------------------------------------- */

@media (max-width: 700px) {
  :root {
    --gutter: 1.5rem;
  }

  .masthead {
    padding-top: 1.75rem;
  }

  /* Too narrow to share a height and still read — stack instead, horse
     leading at full width with the collage centred underneath. */
  .hero__duo {
    flex-direction: column;
    align-items: center;
    height: auto;
  }

  .hero__link {
    height: auto;
    width: 100%;
  }

  .hero__link--collage {
    width: 62%;
  }

  .hero__image {
    height: auto;
    width: 100%;
  }
}

@media (prefers-reduced-motion: no-preference) {
  a {
    transition: border-color 120ms ease;
  }

  .hero__link {
    transition: opacity 150ms ease;
  }
}

.hero__link:hover,
.hero__link:focus-visible {
  opacity: 0.85;
}
