/* Gabriela Olivera — portfolio site
   Design values (colours, spacing, type scale, components) reused verbatim
   from the Terroir case study repo — same identity across both, see
   CLAUDE.md. New component rules below the "Portfolio-specific" marker are
   additive only; nothing above it is changed from that source. */

@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 500 900;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/archivo-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 500 900;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/archivo-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/source-sans-3-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/source-sans-3-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Bricolage Grotesque is a variable font on two axes: wght (weight) and
   opsz (optical size). font-weight: 600 800 preserves the exact range
   requested by the Google Fonts URL this replaced
   (wght@12..96,600..800) — the only weight the three --font-display
   rules actually use is 800 (assets/styles.css: .work__title,
   .how-i-work__title, .site-footer__title). The opsz axis has no
   font-face descriptor of its own; browsers drive it from rendered
   font-size automatically via font-optical-sizing: auto (the default),
   and the 12–96 range requested from Google is baked into these files'
   fvar table, so that continues to work unchanged, self-hosted. Not
   collapsed to a static instance: verified with fonttools that both
   axes are still present as real ranges, not single pinned values.
   Subsetted the same as Archivo/Source Sans 3 (latin + latin-ext) —
   see CLAUDE.md, "Every third-party asset is either self-hosted or
   justified". */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 600 800;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/bricolage-grotesque-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 600 800;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/bricolage-grotesque-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- Tokens ---------- */
:root {
  --bg: #E8E8E3;
  --bg-dark: #101B2E;
  --bg-dark-highlight: #16233A;
  --bg-section: #DEDDD5;
  --bg-card: #FAF8F3;

  --text: #201C17;
  --text-body: #33302A;
  --text-muted: #4A443C;
  --text-subtle: #5C564A;
  --text-on-dark: #E7EEF6;
  --text-on-dark-body: #C3D2E3;
  --text-on-dark-highlight: #D7E4F2;

  --accent-label: #12274A;
  --accent: #1A4A82;
  --accent-soft: #3E6491;
  --accent-highlight: #2E6FB0;
  --accent-muted: #8A8478;
  --neon-blue: #4FC3FF;
  --bg-card-alt: #E1E8EF;

  --border: #C9C6BD;
  --border-dark: #3A352C;

  --font-heading: 'Archivo', sans-serif;
  --font-body: 'Source Sans 3', sans-serif;
  --font-display: 'Bricolage Grotesque', 'Archivo', sans-serif;
}

/* ---------- Base ---------- */
* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  line-height: 1.5;
}
a { color: var(--accent-label); text-decoration: underline; text-underline-offset: 2px; }
a:hover { color: var(--accent); }
a:focus-visible,
button:focus-visible {
  outline: 2.5px solid var(--accent-highlight);
  outline-offset: 2px;
}
::selection { background: var(--accent-highlight); color: var(--bg-card); }

.wrap { max-width: 1240px; margin: 0 auto; padding-left: 32px; padding-right: 32px; }

.skip-link {
  position: absolute;
  top: -100px;
  left: 12px;
  z-index: 100;
  background: var(--text);
  color: var(--bg-card);
  padding: 12px 20px;
  border-radius: 2px;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 15px;
  text-decoration: none;
  transition: top 150ms ease;
}
.skip-link:focus { top: 12px; }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.eyebrow {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-label);
  margin-bottom: 16px;
}

.button {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 15px;
  padding: 14px 28px;
  border-radius: 999px;
  text-decoration: none;
  display: inline-block;
  transition: transform 150ms ease, box-shadow 150ms ease;
}
.button--primary { background: var(--accent); color: var(--bg-card); }
.button--primary:hover { box-shadow: 0 4px 14px rgba(31, 78, 95, 0.28); transform: translateY(-1px); }
.button--primary:hover, .button--primary:focus-visible, .button--primary:active { color: var(--bg-card); }
.button--secondary {
  background: transparent;
  color: var(--accent);
  border: 1.5px solid var(--accent);
  padding: 12.5px 26px;
}
.button--secondary:hover { background: rgba(31, 78, 95, 0.06); }
.button--secondary:hover, .button--secondary:focus-visible, .button--secondary:active { color: var(--accent); }

/* ---------- Header / nav ---------- */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  max-width: 1240px;
  margin: 0 auto;
  padding: 32px 32px 0;
}
.site-header__brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: inherit; }
.site-header__logo { width: 28px; height: 28px; border-radius: 6px; flex: none; }
.site-header__brand-text { display: flex; flex-direction: column; gap: 1px; }
.site-header__subtitle { font-size: 12px; color: var(--text-subtle); font-weight: 500; }
.site-header__mark {
  font-family: var(--font-heading);
  font-weight: 900;
  font-size: 20px;
  letter-spacing: -0.02em;
}
.site-header__nav {
  display: flex;
  gap: 28px;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 15px;
}
.site-header__nav a {
  position: relative;
  color: var(--text);
  text-decoration: none;
  padding-bottom: 3px;
}
.site-header__nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 100%;
  bottom: 0;
  height: 2px;
  background: var(--accent);
  transition: right 200ms ease;
}
.site-header__nav a:hover { text-decoration: none; color: var(--accent); }
.site-header__nav a:hover::after { right: 0; }
.site-header__nav a[aria-current="true"] { color: var(--accent); text-decoration: none; }
.site-header__nav a[aria-current="true"]::after { right: 0; }

/* ---------- Hero ---------- */
.hero { max-width: 1240px; margin: 0 auto; padding: 80px 32px 80px; }
.hero__title {
  font-family: var(--font-heading);
  /* 800, not 900: the only real usage (index.html's h1, which overrides
     font-family to --font-display inline) renders in Bricolage
     Grotesque, whose native max weight is 800 — CSS nearest-weight
     matching has always silently clamped a declared 900 down to 800
     here. Stating 800 makes the declaration match what actually
     renders, rather than a value that's never really applied. See
     CLAUDE.md, "The figure-staleness chain" — the same failure mode as
     every stale copied number, just in a CSS property instead of text. */
  font-weight: 800;
  font-size: clamp(48px, 7vw, 96px);
  line-height: 0.95;
  letter-spacing: -0.03em;
  margin: 0 0 20px;
}
.hero__lede {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: clamp(20px, 2.2vw, 26px);
  line-height: 1.4;
  max-width: 620px;
  margin: 0 0 20px;
  color: var(--text-body);
  text-wrap: pretty;
}
.hero__byline { font-size: 16px; color: var(--text-muted); max-width: 560px; margin: 0 0 36px; }
.hero__actions { display: flex; flex-wrap: wrap; gap: 14px; }

/* ---------- Portfolio-specific: process strip ---------- */
.process-strip__track {
  max-width: 1240px;
  margin: 0 auto;
  padding: 22px 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  font-family: ui-monospace, 'SF Mono', 'Cascadia Mono', 'Menlo', 'Consolas', monospace;
  font-size: 12.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.process-strip__step { color: var(--accent-label); white-space: nowrap; transition: color 150ms ease; }
.process-strip__step:hover { color: var(--accent); }
.process-strip__arrow { color: var(--border-dark); opacity: 0.45; }
.process-strip__step--final { color: var(--accent); font-weight: 800; }
.process-strip__step--final:hover { color: var(--accent-label); }

/* ---------- Portfolio-specific: hero photo grid ---------- */
.hero__grid { display: grid; grid-template-columns: 1.3fr 0.7fr; gap: 56px; align-items: center; }
.hero__photo-frame {
  width: 100%;
  max-width: 340px;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg-card);
  margin-left: auto;
}
.hero__photo-frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 860px) {
  .hero__grid { grid-template-columns: minmax(0, 1fr); gap: 32px; }
  .hero__photo-frame { max-width: 220px; margin: 0 auto; }
}

/* ---------- Portfolio-specific: work section (full-bleed diagonal bands) ---------- */
.project-band {
  width: 100vw;
  position: relative;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
  color: var(--text-on-dark);
}
/* Band colour and card image/body order both derive purely from position
   among sibling .project-band elements — never from a modifier class — so
   pasting in another project block just works, in either rhythm. See
   CLAUDE.md, "Adding a project". */
#work .project-band:nth-of-type(odd) { background: var(--accent); clip-path: polygon(0 36px, 100% 0, 100% 100%, 0 100%); padding-top: 36px; }
#work .project-band:nth-of-type(even) { background: var(--bg-dark); clip-path: polygon(0 0, 100% 36px, 100% 100%, 0 100%); padding-top: 36px; }
.project-band__inner { max-width: 1240px; margin: 0 auto; padding: 64px 32px 80px; }
.work__title--on-dark { color: #FFFFFF; }
.work__title {
  position: relative;
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(42px, 4.6vw, 60px);
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin: 64px 0 56px;
  text-wrap: pretty;
}
.work__title::after {
  content: "";
  position: absolute;
  left: 2px;
  bottom: -16px;
  height: 4px;
  width: 64px;
  background: var(--accent);
  border-radius: 2px;
}
html.motion-ready [data-reveal] .work__title::after {
  width: 0;
  transition: width 650ms ease 250ms;
}
html.motion-ready [data-reveal].is-visible .work__title::after { width: 64px; }
.work__title--on-dark::after { background: var(--neon-blue); }
.work__title:first-of-type { margin-top: 0; }
.project-card { color: inherit; }
.project-card {
  background: transparent;
  border: none;
  padding: 0;
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: 40px;
  align-items: stretch;
  margin-bottom: 0;
}
#work .project-band:nth-of-type(even) .project-card__media { order: 2; }
#work .project-band:nth-of-type(even) .project-card__body { order: 1; }
/* aspect-ratio (both real screenshots are ~1.99:1) replaces height: 100%
   as the source of the box's height: height: 100% took its height from
   .project-card's align-items: stretch — i.e. from the text column's
   height, not the image's own shape — so object-fit: cover had to crop
   away most of a ~2:1 image to fill a much taller box. align-self: start
   opts this item out of that stretch so aspect-ratio can actually govern
   its height. With the box's own ratio now matching the image, cover has
   under 1% left to crop — object-position is not needed. */
.project-card__media { align-self: start; width: 100%; aspect-ratio: 2 / 1; overflow: hidden; border-radius: 4px; border: 1px solid rgba(255, 255, 255, 0.2); }
.project-card__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.project-card__eyebrow { font-family: var(--font-heading); font-weight: 700; font-size: 14px; letter-spacing: 0.02em; color: var(--text-on-dark-body); margin: 0 0 22px; }
.project-card__wake-note { font-size: 13px; color: var(--text-subtle); margin: 10px 0 0; }
.project-card__wake-note--on-dark { color: var(--text-on-dark-body); }
.project-card__title { font-family: var(--font-heading); font-weight: 800; font-size: clamp(26px, 2.8vw, 34px); margin: 0 0 12px; letter-spacing: -0.015em; line-height: 1.15; text-wrap: pretty; color: #FFFFFF; }
.project-card__text { font-size: 16px; color: var(--text-on-dark-body); margin: 0 0 22px; }
.project-card__text strong { color: #FFFFFF; }
.project-card__links { display: flex; flex-wrap: wrap; gap: 12px; }
.project-card__links .button { padding: 11px 20px; font-size: 14px; }
.project-card__links--split { flex-wrap: nowrap; }
.project-card__links--split .button { flex: 1 1 0; text-align: center; }
/* min-width: 0 overrides the grid item default of min-width: auto, which
   otherwise refuses to shrink a column below its content's min-content
   width — at narrow viewports that held a stat number like "22,834" (at
   .stat__value's fixed 34px) wider than its 1fr track, overflowing the
   viewport. clamp() on .stat__value then lets the number's own size (not
   just its column) shrink to fit, rather than wrapping mid-number. See
   CLAUDE.md / scripts/check-overflow.mjs, "no horizontal overflow from
   320px up verified by measurement". */
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; list-style: none; padding: 0; margin: 0 0 22px; }
.stat { min-width: 0; }
.stat__value { font-family: var(--font-heading); font-weight: 900; font-size: clamp(22px, 6.5vw, 34px); color: var(--neon-blue); letter-spacing: -0.02em; }
.stat__label { font-size: 15px; font-weight: 700; color: #FFFFFF; margin-top: 4px; }
.stat__note { font-size: 12px; color: var(--text-on-dark-body); margin-top: 3px; }
.button--on-dark-primary { background: #FFFFFF; color: var(--accent-label); border-radius: 999px; font-family: var(--font-heading); font-weight: 700; padding: 11px 20px; font-size: 14px; text-decoration: none; }
.button--on-dark-primary:hover, .button--on-dark-primary:focus-visible, .button--on-dark-primary:active { color: var(--accent-label); }
.button--on-dark-secondary { background: transparent; color: #FFFFFF; border: 1.5px solid rgba(255, 255, 255, 0.55); border-radius: 999px; font-family: var(--font-heading); font-weight: 700; padding: 9.5px 20px; font-size: 14px; text-decoration: none; }
.button--on-dark-secondary:hover { background: rgba(255, 255, 255, 0.1); }
.button--on-dark-secondary:hover, .button--on-dark-secondary:focus-visible, .button--on-dark-secondary:active { color: #FFFFFF; }
@media (max-width: 860px) {
  .project-card { grid-template-columns: minmax(0, 1fr); padding: 0; }
  #work .project-band:nth-of-type(even) .project-card__media,
  #work .project-band:nth-of-type(even) .project-card__body { order: initial; }
  .project-band__inner { padding: 48px 20px 56px; }
}

/* ---------- Portfolio-specific: quote highlight (dark) ---------- */
.quote-highlight { background: var(--bg-dark); color: var(--text-on-dark); padding: 72px 32px; }
.quote-highlight__inner { max-width: 900px; margin: 0 auto; border-left: 4px solid var(--accent-highlight); padding-left: 32px; }
.quote-highlight blockquote {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: clamp(22px, 2.8vw, 32px);
  line-height: 1.4;
  margin: 0 0 16px;
  text-wrap: pretty;
}
.quote-highlight cite { font-size: 15px; color: var(--text-on-dark-body); font-style: normal; }

/* ---------- Portfolio-specific: how I work ---------- */
.how-i-work { background: var(--bg-section); padding: 88px 32px; }
.how-i-work__title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(32px, 3.6vw, 46px);
  letter-spacing: -0.01em;
  margin: 0 0 48px;
  max-width: 700px;
}
.how-i-work-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; list-style: none; margin: 0; padding: 0; max-width: 1240px; margin: 0 auto; }
.how-i-work-item {
  background: var(--bg-card);
  border-radius: 6px;
  padding: 32px 28px;
  border: 1px solid var(--border);
  transition: transform 200ms ease, box-shadow 200ms ease, border-color 200ms ease;
}
.how-i-work-item:hover {
  transform: translateY(-4px);
  box-shadow: 0 10px 24px rgba(27, 59, 111, 0.14);
  border-color: var(--accent);
}
.how-i-work-item:hover .how-i-work-item__number { color: var(--neon-blue); text-shadow: 0 0 10px rgba(79, 195, 255, 0.5); }
.how-i-work-item__number {
  font-family: var(--font-heading);
  font-weight: 900;
  font-size: 14px;
  color: var(--accent);
  letter-spacing: 0.06em;
  display: block;
  margin-bottom: 14px;
  transition: color 200ms ease, text-shadow 200ms ease;
}
.how-i-work-item h3 { font-family: var(--font-heading); font-weight: 700; font-size: 18px; margin: 0 0 10px; }
.how-i-work-item p { font-size: 15.5px; color: var(--text-muted); margin: 0; }
@media (max-width: 860px) { .how-i-work-grid { grid-template-columns: minmax(0, 1fr); } }

/* ---------- Portfolio-specific: tooling bar (looping ticker with pause control) ---------- */
.tooling-bar { overflow: hidden; background: var(--bg-dark); padding: 26px 0; display: flex; align-items: center; gap: 20px; }
.tooling-bar__viewport { flex: 1; overflow: hidden; }
.tooling-bar__track {
  --tooling-gap: 56px;
  display: flex;
  gap: var(--tooling-gap);
  white-space: nowrap;
  width: max-content;
  font-family: ui-monospace, 'SF Mono', 'Cascadia Mono', 'Menlo', 'Consolas', monospace;
  font-size: 19px;
  letter-spacing: 0.02em;
}
/* The scroll animation only runs once script.js has cloned the track's
   real content into an identical second half (see the HTML comment
   above #tooling-track) — without that clone, translateX(-50%) would
   scroll past the single copy into empty space. No-JS and
   reduced-motion visitors get the same static, fully-readable list
   with no animation at all, same as [data-reveal] elsewhere on this
   page. */
html.motion-ready .tooling-bar__track { animation: tooling-scroll 20s linear infinite; }
.tooling-bar__track.is-paused { animation-play-state: paused; }
.tooling-bar__item {
  color: var(--neon-blue);
  text-shadow: 0 0 6px rgba(79, 195, 255, 0.65), 0 0 16px rgba(79, 195, 255, 0.35);
}
.tooling-bar__sep { color: rgba(231, 238, 246, 0.3); text-shadow: none; }
@keyframes tooling-scroll {
  from { transform: translateX(0); }
  /* Not a plain -50%: with an odd total gap count (2N-1 gaps across two
     N-child halves), -50% of the track's total width only carries half
     of the one boundary gap past the viewport, undershooting a true
     one-half-width-plus-one-gap shift by exactly half a gap — every
     time, regardless of list length. Subtracting half of the same
     --tooling-gap this rule's own `gap` uses keeps the correction tied
     to that one value, so it can't drift out of sync with it either. */
  to { transform: translateX(calc(-50% - (var(--tooling-gap) / 2))); }
}
.tooling-bar__toggle {
  flex: none;
  width: 38px;
  height: 38px;
  margin-right: 24px;
  border-radius: 50%;
  border: 1px solid rgba(231, 238, 246, 0.35);
  background: transparent;
  color: var(--text-on-dark);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.tooling-bar__toggle:hover { border-color: var(--neon-blue); color: var(--neon-blue); }
@media (prefers-reduced-motion: reduce) {
  .tooling-bar__track { animation: none; }
}

/* ---------- Portfolio-specific: contact email chip + copy control ---------- */
.email-chip {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 6px 6px 16px;
  margin: 0 0 20px;
  max-width: 100%;
}
.email-chip__address {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 15px;
  color: var(--accent);
  text-decoration: none;
  letter-spacing: normal;
  /* Overrides the flex item default of min-width: auto, which otherwise
     refuses to shrink the address below the full email's unbroken
     min-content width — at narrow viewports that overflowed the chip
     (and the viewport) rather than wrapping. See CLAUDE.md /
     scripts/check-overflow.mjs, "no horizontal overflow from 320px up
     verified by measurement". */
  min-width: 0;
  overflow-wrap: anywhere;
}
.email-chip__address:hover, .email-chip__address:focus-visible, .email-chip__address:active { color: var(--accent); text-decoration: underline; }
.copy-button {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 12px;
  color: var(--bg-card);
  background: var(--accent);
  border: none;
  border-radius: 999px;
  padding: 7px 14px;
  cursor: pointer;
}
.copy-button:hover { box-shadow: 0 2px 8px rgba(31, 78, 95, 0.3); }
.copy-button:hover, .copy-button:focus-visible, .copy-button:active { color: var(--bg-card); }
.copy-button__status { font-size: 13px; color: var(--text-muted); display: block; margin: -8px 0 32px; }

/* ---------- Footer ---------- */
.site-footer { padding: 96px 32px 64px; }
.site-footer__title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(28px, 3vw, 38px);
  letter-spacing: -0.02em;
  margin: 0 0 36px;
  text-wrap: pretty;
}
.site-footer__actions { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 44px; }
.site-footer__cv-date { font-size: 13px; color: var(--text-subtle); margin: -30px 0 44px; }
.site-footer__meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  border-top: 1px solid var(--border);
  padding-top: 28px;
  font-size: 14px;
  color: var(--text-subtle);
}
.site-footer__status { display: flex; align-items: center; gap: 10px; }
.site-footer__dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: var(--accent); }

/* ---------- Back to top ---------- */
.back-to-top {
  position: fixed;
  bottom: 32px;
  right: 32px;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--bg-card);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 14px rgba(32, 28, 23, 0.25);
  border: none;
  cursor: pointer;
  z-index: 40;
  opacity: 0;
  transform: translateY(16px);
  pointer-events: none;
  transition: opacity 300ms ease, transform 300ms ease;
}
.back-to-top.is-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
.back-to-top:focus-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }

/* ---------- Scroll-reveal (progressive enhancement, from Terroir) ---------- */
html.motion-ready [data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 700ms ease-out, transform 700ms ease-out;
}
html.motion-ready [data-reveal].is-visible { opacity: 1; transform: translateY(0); }

@media (max-width: 640px) {
  .site-header { flex-wrap: wrap; row-gap: 12px; padding-top: 24px; }
  .site-header__nav { gap: 6px 16px; font-size: 14px; flex-wrap: wrap; width: 100%; }
  .site-header__subtitle { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .back-to-top { transition: none; }
  html.motion-ready [data-reveal] { opacity: 1; transform: none; transition: none; }
}
